Skip to content

How to implement 256bit AES encryption with Cipher Block Chaining (CBC) using Kotlin

I recently wanted to figure out how to implement 256bit AES encryption in Kotlin. It turned out to be a bit tricker than you'd expect. You see, the AES Cipher that comes natively with Java has a maximum key length of 128 bits due to US export regulations. Bless.

Whilst it is technically possibly to download policy files and overcome this limitation, I found it was better to use the "Bouncy Castle" provider instead. It was written by some fellow Australian types, and Australia being a colony of law direspecting convicts has no such ethical conundrums about exporting strong encryption to the world.

So, here's a basic Kotlin class that will do the job. Note, you'll need to add the following into your .pom file if you're using Maven.


<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>


And here's the class:


package uk.co.chapmandigital.crypto

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import uk.co.chapmandigital.exceptions.DomainException
import uk.co.chapmandigital.helpers.StringHelper
import org.apache.commons.codec.binary.Base64
import javax.crypto.spec.SecretKeySpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.SecretKeyFactory
import org.bouncycastle.crypto.params.ParametersWithIV
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.crypto.engines.AESEngine
import org.bouncycastle.crypto.modes.CBCBlockCipher
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher
import org.bouncycastle.crypto.paddings.PKCS7Padding
import java.nio.charset.Charset
import java.security.Security

class AESCrypter {
constructor() {
Security.addProvider(BouncyCastleProvider())
}

fun generateIV(): String {
val stringHelper = StringHelper()
return stringHelper.generateRandomString(16, true)
}

fun generateSalt(): String {
val stringHelper = StringHelper()
return stringHelper.generateRandomString(32, true)
}

fun encrypt(iv: String, key: String, salt: String, message: String): String {
val output = processData(true, iv, key, salt, message)
return Base64.encodeBase64String(output)
}

fun decrypt(iv: String, key: String, salt: String, message: String): String {
val output = processData(false, iv, key, salt, message)
return String(output, Charset.forName("UTF-8"))
}

private fun processData(encrypt: Boolean, iv: String, key: String, salt: String, message: String): ByteArray {
if (iv.length != 16) {
throw DomainException("AESCrypter - Invalid IV size: ${iv.length}! Must be 128 bit / 16 bytes")
}

// Force 256 bit encryption by using a 256 bit key
if (key.length != 32) {
throw DomainException("AESCrypter - Invalid key size: ${key.length}! Must be 256 bit / 32 bytes")
}

if (salt.length != 32) {
throw DomainException("AESCrypter - Invalid salt size: ${salt.length}! 256 bit / 32 bytes")
}

// get raw key from password and salt
val pbeKeySpec = PBEKeySpec(key.toCharArray(), salt.toByteArray(), 50, 256)
val keyFactory = SecretKeyFactory.getInstance("PBEWithSHA256And256BitAES-CBC-BC")
val secretKey = SecretKeySpec(keyFactory.generateSecret(pbeKeySpec).encoded, "AES")

val keyParam = KeyParameter(secretKey.encoded)
val params = ParametersWithIV(keyParam, iv.toByteArray())

val cipher = PaddedBufferedBlockCipher(CBCBlockCipher(AESEngine()), PKCS7Padding())
cipher.reset()
cipher.init(encrypt, params)

var messageData: ByteArray

if (encrypt) {
messageData = message.toByteArray()
} else {
messageData = Base64.decodeBase64(message)
}

val buffer = ByteArray(cipher.getOutputSize(messageData.size))
var outputLength = cipher.processBytes(messageData, 0, messageData.size, buffer, 0)
outputLength += cipher.doFinal(buffer, outputLength)

// remove padding
val output = ByteArray(outputLength)
System.arraycopy(buffer, 0, output, 0, outputLength)

return output
}
}


And finally, here's a small Junit test to provide it works and show how to use it:


package uk.co.chapmandigital.crypto.jwt

import org.junit.Test
import uk.co.chapmandigital.crypto.AESCrypter
import kotlin.test.assertEquals

class AESCrypterTest {
@Test
fun AESCrypterGeneratesIVCorrectly() {
val aesCrypter = AESCrypter()
val iv = aesCrypter.generateIV()

assertEquals(16, iv.length)
}

@Test
fun AESCrypterGeneratesSaltCorrectly() {
val aesCrypter = AESCrypter()
val iv = aesCrypter.generateSalt()

assertEquals(32, iv.length)
}

@Test
fun AESCrypterEncryptsStringCorrectly() {
val aesCrypter = AESCrypter()
val iv = aesCrypter.generateIV()
val salt = aesCrypter.generateSalt()
val key = "The Hover Craft Is Full Of Eels!"

val inputMessages = ArrayList()
inputMessages.add("God damn right it's a beautiful day, aha!")
inputMessages.add("Hi there!")
inputMessages.add("\n" +
"\n" +
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris faucibus neque eu sapien malesuada, non hendrerit tellus elementum. Nunc odio ex, tempor ut imperdiet sit amet, luctus non magna. Morbi consequat, justo efficitur aliquet consequat, risus lorem feugiat nisi, vel varius libero ipsum at ligula. Sed consectetur ornare dui ut interdum. Donec congue nulla a fermentum pretium. Nullam eget efficitur augue. Pellentesque rutrum sapien vitae nunc porta sagittis. Donec luctus metus vitae felis venenatis ornare.\n" +
"\n" +
"Aliquam bibendum sem et iaculis consectetur. Quisque eu tortor a elit lobortis molestie at nec ante. Nullam quis ligula sed nisi bibendum facilisis. Duis nec lorem dui. Maecenas sed augue vel sem mollis auctor. Morbi eu dictum nisi. Aliquam id nisl massa.\n" +
"\n" +
"Integer eleifend enim sed augue eleifend dignissim. Aliquam eget ultricies diam, sit amet sollicitudin lectus. Aliquam ullamcorper gravida vulputate. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vivamus in faucibus urna, vel fringilla ipsum. Aenean enim diam, finibus ut dictum quis, imperdiet nec leo. Integer laoreet commodo dolor. Suspendisse ut nibh dapibus, congue lectus dapibus, scelerisque eros. Mauris eros massa, cursus vel faucibus in, tincidunt vel diam. Aliquam et auctor elit. Maecenas porta convallis lectus sit amet elementum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed hendrerit leo interdum, laoreet dui eget, finibus leo. Praesent id egestas lacus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec efficitur non sapien at rutrum.\n" +
"\n" +
"Donec eget turpis sed nunc facilisis viverra. Morbi bibendum, elit eget lobortis interdum, lorem erat suscipit leo, vitae vehicula nisl turpis non nunc. Praesent turpis ex, iaculis a auctor nec, rutrum vel enim. Etiam volutpat nisi ut felis tincidunt congue. Donec cursus justo at lacus ultrices, non aliquam neque congue. Vivamus lacinia magna fermentum, feugiat quam non, efficitur tortor. Donec nec blandit lacus. Phasellus a diam augue. Mauris pellentesque nibh erat, vel ornare leo egestas ac. Morbi ultrices magna ligula, ac maximus felis sodales ac. Maecenas sed leo eu enim fermentum pulvinar.\n" +
"\n" +
"Sed faucibus viverra tortor, quis dapibus lectus malesuada ac. Phasellus eget molestie ipsum. Vivamus varius leo ut diam finibus luctus. Proin cursus euismod lobortis. Aliquam sodales, leo in sollicitudin pretium, leo turpis efficitur urna, in bibendum dui nisl aliquam dui. Nulla egestas odio et felis pulvinar aliquet. Ut pharetra justo in leo varius, eu placerat tellus aliquam. Cras viverra faucibus laoreet. Mauris at lectus vel ipsum sagittis vestibulum id eget lectus. In semper ultricies odio eget vestibulum. Donec sit amet nunc faucibus, convallis sem id, consectetur nibh. Praesent quis arcu enim. Sed elementum malesuada volutpat. Praesent suscipit, felis nec ullamcorper tristique, nulla nulla varius erat, vitae vestibulum magna eros ut odio. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. ")


for(message in inputMessages) {
val encryptedMessage = aesCrypter.encrypt(iv, key, salt, message)
val decryptedMessage = aesCrypter.decrypt(iv, key, salt, encryptedMessage)
assertEquals(message, decryptedMessage)
}
}
}