AES-Kotlin
¶
A pure Kotlin implementation of the AES block cipher algorithm and all common modes of operation (CBC, CFB, CTR, ECB, and OFB).
Features¶
- Pure Kotlin (with no dependencies, or optional BouncyCastle for advanced use cases)
- Supports all key sizes (128-bit, 192-bit, and 256-bit)
- Supports all common modes of operation (CBC, CFB, CTR, ECB, and OFB)
- Works on the JVM and Android
Installation¶
Gradle (Kotlin DSL)¶
Add the following to your build.gradle.kts file:
dependencies {
implementation("io.github.niyajali:aes-kotlin:1.0.1")
}
Usage¶
Keys¶
All keys must be 128 bits (16 bytes), 192 bits (24 bytes), or 256 bits (32 bytes) long. You can generate keys from passwords using a key derivation function like PBKDF2.
import javax.crypto.spec.SecretKeySpec
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
// Generate a 256-bit key from a password
val password = "myPassword".toCharArray()
val salt = "mySalt".toByteArray()
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val spec = PBEKeySpec(password, salt, 65536, 256)
val key = factory.generateSecret(spec).encoded
val secretKey = SecretKeySpec(key, "AES")
Common Modes of Operation¶
CTR - Counter (Recommended)¶
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
val key = "0123456789abcdef".toByteArray() // 128-bit key
val iv = "1234567890abcdef".toByteArray() // 16-byte initialization vector
val cipher = Cipher.getInstance("AES/CTR/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val plaintext = "Hello, Kotlin AES!".toByteArray()
val encrypted = cipher.doFinal(plaintext)
println("Encrypted: ${encrypted.joinToString("") { "%02x".format(it) }}")
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val decrypted = cipher.doFinal(encrypted)
println("Decrypted: ${String(decrypted)}")
CBC - Cipher-Block Chaining (Recommended)¶
val key = "0123456789abcdef".toByteArray() // 128-bit key
val iv = "1234567890abcdef".toByteArray() // 16-byte initialization vector
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val plaintext = "Hello, Kotlin AES!".toByteArray()
val encrypted = cipher.doFinal(plaintext)
println("Encrypted: ${encrypted.joinToString("") { "%02x".format(it) }}")
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val decrypted = cipher.doFinal(encrypted)
println("Decrypted: ${String(decrypted)}")
CFB - Cipher Feedback¶
val key = "0123456789abcdef".toByteArray() // 128-bit key
val iv = "1234567890abcdef".toByteArray() // 16-byte initialization vector
val cipher = Cipher.getInstance("AES/CFB/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val plaintext = "Hello, Kotlin AES!".toByteArray()
val encrypted = cipher.doFinal(plaintext)
println("Encrypted: ${encrypted.joinToString("") { "%02x".format(it) }}")
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val decrypted = cipher.doFinal(encrypted)
println("Decrypted: ${String(decrypted)}")
OFB - Output Feedback¶
val key = "0123456789abcdef".toByteArray() // 128-bit key
val iv = "1234567890abcdef".toByteArray() // 16-byte initialization vector
val cipher = Cipher.getInstance("AES/OFB/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val plaintext = "Hello, Kotlin AES!".toByteArray()
val encrypted = cipher.doFinal(plaintext)
println("Encrypted: ${encrypted.joinToString("") { "%02x".format(it) }}")
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val decrypted = cipher.doFinal(encrypted)
println("Decrypted: ${String(decrypted)}")
ECB - Electronic Codebook (NOT Recommended)¶
val key = "0123456789abcdef".toByteArray() // 128-bit key
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"))
val plaintext = "Hello, Kotlin AES!".toByteArray()
val encrypted = cipher.doFinal(plaintext)
println("Encrypted: ${encrypted.joinToString("") { "%02x".format(it) }}")
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"))
val decrypted = cipher.doFinal(encrypted)
println("Decrypted: ${String(decrypted)}")
Block Cipher (Direct Usage)¶
If you need to use the block cipher directly (e.g., for custom modes of operation), you can use the following:
import javax.crypto.Cipher
val key = "0123456789abcdef".toByteArray() // 128-bit key
val secretKey = SecretKeySpec(key, "AES")
val cipher = Cipher.getInstance("AES")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val plaintext = "ABlockIs16Bytes!".toByteArray()
val encrypted = cipher.doFinal(plaintext)
println("Encrypted: ${encrypted.joinToString("") { "%02x".format(it) }}")
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, secretKey)
val decrypted = cipher.doFinal(encrypted)
println("Decrypted: ${String(decrypted)}")
Notes¶
What is a Key?¶
A key is essentially a “password” for encryption. However, AES requires the key to be a specific length: 128 bits (16 bytes), 192 bits (24 bytes), or 256 bits (32 bytes). If you have a password of arbitrary length, you should use a Password-Based Key Derivation Function (PBKDF) like PBKDF2 to generate a key of the correct length.
Example:
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
val password = "myPassword".toCharArray()
val salt = "mySalt".toByteArray()
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val spec = PBEKeySpec(password, salt, 65536, 256) // 256-bit key
val key = factory.generateSecret(spec).encoded
Performance¶
For most use cases, the built-in javax.crypto implementation is sufficient and highly optimized. If you need additional performance or features, consider using BouncyCastle.
Testing¶
To test your implementation, you can use known test vectors from sources like NIST.
License¶
This project is licensed under the MIT License. See the LICENSE file for details.