この記事では、KotlinでAES GCMモードの暗号化と復号を行うサンプルコードを紹介します。
このサンプルコードは以下の機能を持ちます。
- バイナリデータの暗号化/復号
- Base64テキストの暗号化/復号
- Hexテキストの暗号化/復号
実際に利用できるクラスのソースコード全文と一般的な使用方法を記載していますので、ご自身のプロジェクトでご活用いただければ幸いです。
また、暗号の結果が正しいことを検証するために使用したテストコードも載せています。
ここでは AES-GCM 自体の解説は行いません。
GCMモードの仕組みについて興味がある方は以下の記事も一緒にご覧下さい。
-
AES-GCM 仕組みの解説とJavaサンプルコード
この記事では、共通鍵暗号のデファクトスタンダードとなっている、AES-GCMモードについての解説をしています。 AES-GCMとは、近年様々な用途で用いられているAESの暗号モードの一つです。主な利用 ...
続きを見る
それでは行ってみましょう!
暗号化/復号クラス
公開メソッド | 機能 |
---|---|
encrypt | バイナリデータの暗号化 |
decrypt | バイナリデータの復号 |
encryptAsBase64 | Base64テキストの暗号化 |
decryptAsBase64 | Base64テキストの復号 |
encryptAsHex | Hex(16進)テキストの暗号化 |
decryptAsHex | Hex(16進)テキストの復号 |
import org.apache.commons.codec.binary.Hex
import java.security.SecureRandom
import java.util.*
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
class AesGcmCipher {
private val GCM_CIPHER_MODE = "AES/GCM/NoPadding" // Cipher mode
private val GCM_NONCE_LENGTH = 12 // Nonce length
private val GCM_AAD_MAX_LENGTH = 64 * 1024 // AAD Max length
private val key: SecretKey
private val tagBitLen: Int
private val aad: ByteArray?
private val random = SecureRandom()
constructor(key: ByteArray, tagBitLen: Int = 128, aad: ByteArray? = null) {
assert(listOf(128, 192, 256).contains(key.size * 8)) // Must be one of {128, 192, 256} bit len
assert(listOf(128, 120, 112, 104, 96).contains(tagBitLen)) // Must be one of {128, 120, 112, 104, 96} bit len
aad?.let { assert(it.size < GCM_AAD_MAX_LENGTH) } // Must be less than 64KiB
this.key = SecretKeySpec(key, "AES")
this.tagBitLen = tagBitLen
this.aad = aad
}
fun encrypt(plainData: ByteArray): ByteArray {
// Generate Cipher Instance
val cipher = generateCipher(Cipher.ENCRYPT_MODE)
// Perform Encryption
val encryptData = cipher.doFinal(plainData)
// Return nonce + Encrypt Data
return cipher.iv + encryptData
}
fun decrypt(cipherData: ByteArray): ByteArray {
val nonce = cipherData.copyOfRange(0, GCM_NONCE_LENGTH)
val encryptData = cipherData.copyOfRange(GCM_NONCE_LENGTH, cipherData.size)
// Generate Cipher Instance
val cipher = generateCipher(Cipher.DECRYPT_MODE, nonce)
// Perform Decryption
return cipher.doFinal(encryptData)
}
fun encryptAsBase64(plainTextBase64: String): String {
return Base64.getEncoder().encodeToString(
encrypt(Base64.getDecoder().decode(plainTextBase64))
)
}
fun decryptAsBase64(cipherTextBase64: String): String {
return Base64.getEncoder().encodeToString(
decrypt(Base64.getDecoder().decode(cipherTextBase64))
)
}
fun encryptAsHex(plainTextHex: String): String {
return Hex.encodeHexString(
encrypt(Hex.decodeHex(plainTextHex))
)
}
fun decryptAsHex(cipherTextHex: String): String {
return Hex.encodeHexString(
decrypt(Hex.decodeHex(cipherTextHex))
)
}
private fun generateCipher(mode: Int, nonceToDecrypt: ByteArray? = null): Cipher {
// Get Cipher Instance
val cipher = Cipher.getInstance(GCM_CIPHER_MODE)
// Get nonce
val nonce = when (mode) {
Cipher.ENCRYPT_MODE -> {
// Generate nonce
val nonceToEncrypt = ByteArray(GCM_NONCE_LENGTH)
random.nextBytes(nonceToEncrypt)
nonceToEncrypt
}
Cipher.DECRYPT_MODE -> {
nonceToDecrypt ?: throw IllegalArgumentException()
}
else -> throw IllegalArgumentException()
}
// Create GCMParameterSpec
val gcmParameterSpec = GCMParameterSpec(tagBitLen, nonce)
// Initialize Cipher with mode/key/iv
cipher.init(mode, key, gcmParameterSpec)
aad?.let {
// Update AAD to do additional auth (Optional)
cipher.updateAAD(it)
}
return cipher
}
}
使用方法
val plainText = "This is a plain text."
val keyBase64 = "XkbPC5uQWTF6UWFx/FeRjlZPaqQtQqRKLt6lbZsbQf4="
// Generate Cipher instance
val key = Base64.getDecoder().decode(keyBase64) // key as binary data
val cipher = AesGcmCipher(key) // tagLen is default 128bit, no AAD
// Encryption
val encryptData = cipher.encrypt(plainText.toByteArray())
// Decryption
val decryptData = cipher.decrypt(encryptData)
println("Decrypt Text: ${String(decryptData)}")
// Decrypt Text: This is a plain text.
テストコード
import org.apache.commons.codec.binary.Hex
import java.security.SecureRandom
import java.util.*
import kotlin.test.Test
class AesGcmCipherTest {
private val secureRandom = SecureRandom()
@Test
fun test() {
repeat(IntRange(1, 100).count()) {
// Randomize key size
val key = ByteArray(intArrayOf(16, 24, 32).random())
// Make random key
secureRandom.nextBytes(key)
// Make cipher instance
val aesGcmCipher = AesGcmCipher(key)
// Make random plain
val plain = ByteArray(secureRandom.nextInt(65535))
secureRandom.nextBytes(plain)
// encrypt
val encrypted = aesGcmCipher.encrypt(plain)
val decrypted = aesGcmCipher.decrypt(encrypted)
assert(plain contentEquals decrypted)
// encrypt as Base64
val plainBase64 = Base64.getEncoder().encodeToString(plain)
val encryptedBase64 = aesGcmCipher.encryptAsBase64(plainBase64)
val decryptedBase64 = aesGcmCipher.decryptAsBase64(encryptedBase64)
assert(plainBase64 contentEquals decryptedBase64)
// encrypt as Hex string
val plainHex = Hex.encodeHexString(plain)
val encryptedHex = aesGcmCipher.encryptAsHex(plainHex)
val decryptedHex = aesGcmCipher.decryptAsHex(encryptedHex)
assert(plainHex contentEquals decryptedHex)
}
}
@Test
fun test2() {
repeat(IntRange(1, 100).count()) {
// Randomize key size
val key = ByteArray(intArrayOf(16, 24, 32).random())
// Make random key
secureRandom.nextBytes(key)
// Randomize tag length
val tagBitLen = listOf(128, 120, 112, 104, 96).random()
// Randomize AAD
val aad = ByteArray(secureRandom.nextInt(64 * 1024))
secureRandom.nextBytes(aad)
// Make cipher instance
val aesGcmCipher = AesGcmCipher(key, tagBitLen, aad)
// Make random plain
val plain = ByteArray(secureRandom.nextInt(65535))
secureRandom.nextBytes(plain)
// encrypt
val encrypted = aesGcmCipher.encrypt(plain)
val decrypted = aesGcmCipher.decrypt(encrypted)
assert(plain contentEquals decrypted)
}
}
}
まとめ
Kotlinで AES GCMモード 暗号化する例を紹介しました。
GCMモードは、IVを毎回必ず乱数にしなければならないという仕様があり、今回のサンプルコードにも盛り込んでいます。
また、TagやAADなど従来の暗号モードには無い機能が追加されており、仕組みを知らないと理解するのが少し難しいと思います。
以下の記事には、GCMモードのNonce、TagやAADに関しても詳しく解説していますので、より深く知りたい方は是非ご覧下さい。
-
AES-GCM 仕組みの解説とJavaサンプルコード
この記事では、共通鍵暗号のデファクトスタンダードとなっている、AES-GCMモードについての解説をしています。 AES-GCMとは、近年様々な用途で用いられているAESの暗号モードの一つです。主な利用 ...
続きを見る
本ブログでは、他にも 暗号に関する記事 を多数投稿していますので、興味があれば読んでみて下さい。
それでは、また他の記事でお会いしましょう!