diff --git a/quartz/src/appleMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.apple.kt b/quartz/src/appleMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.apple.kt index eb065059a..9536b80e5 100644 --- a/quartz/src/appleMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.apple.kt +++ b/quartz/src/appleMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.apple.kt @@ -63,4 +63,22 @@ actual class AESGCM actual constructor( Log.w("AESGCM", "Failed to decrypt", e) null } + + @OptIn(DelicateCryptographyApi::class) + actual fun encrypt( + bytesToEncrypt: ByteArray, + aad: ByteArray, + ): ByteArray = + with(cipher()) { + encryptWithIvBlocking(nonce, bytesToEncrypt, aad) + } + + @OptIn(DelicateCryptographyApi::class) + actual fun decrypt( + bytesToDecrypt: ByteArray, + aad: ByteArray, + ): ByteArray = + with(cipher()) { + decryptWithIvBlocking(nonce, bytesToDecrypt, aad) + } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/Hpke.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/Hpke.kt index 23d328d97..67508522d 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/Hpke.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/Hpke.kt @@ -197,20 +197,12 @@ object Hpke { nonce: ByteArray, aad: ByteArray, plaintext: ByteArray, - ): ByteArray { - val cipher = AESGCM(key, nonce) - // Note: Current AESGCM doesn't support AAD. For HPKE base mode with empty AAD this works. - // TODO: Add AAD support to AESGCM for full compliance - return cipher.encrypt(plaintext) - } + ): ByteArray = AESGCM(key, nonce).encrypt(plaintext, aad) private fun aeadOpen( key: ByteArray, nonce: ByteArray, aad: ByteArray, ciphertext: ByteArray, - ): ByteArray { - val cipher = AESGCM(key, nonce) - return cipher.decrypt(ciphertext) - } + ): ByteArray = AESGCM(key, nonce).decrypt(ciphertext, aad) } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/MlsCryptoProvider.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/MlsCryptoProvider.kt index 2d22a54ef..c2eca8b0f 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/MlsCryptoProvider.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/crypto/MlsCryptoProvider.kt @@ -139,22 +139,14 @@ object MlsCryptoProvider { nonce: ByteArray, aad: ByteArray, plaintext: ByteArray, - ): ByteArray { - val cipher = AESGCM(key, nonce) - // AESGCM doesn't support AAD directly; for MLS we need raw AES-GCM with AAD - // TODO: extend AESGCM to support AAD or use platform-specific AES-GCM with AAD - return cipher.encrypt(plaintext) - } + ): ByteArray = AESGCM(key, nonce).encrypt(plaintext, aad) fun aeadDecrypt( key: ByteArray, nonce: ByteArray, aad: ByteArray, ciphertext: ByteArray, - ): ByteArray { - val cipher = AESGCM(key, nonce) - return cipher.decrypt(ciphertext) - } + ): ByteArray = AESGCM(key, nonce).decrypt(ciphertext, aad) // --- Random --- diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.kt index 8b1b00578..831100a90 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.kt @@ -36,4 +36,16 @@ expect class AESGCM( override fun decrypt(bytesToDecrypt: ByteArray): ByteArray override fun decryptOrNull(bytesToDecrypt: ByteArray): ByteArray? + + /** AES-GCM encrypt with Additional Authenticated Data (AAD). */ + fun encrypt( + bytesToEncrypt: ByteArray, + aad: ByteArray, + ): ByteArray + + /** AES-GCM decrypt with Additional Authenticated Data (AAD). */ + fun decrypt( + bytesToDecrypt: ByteArray, + aad: ByteArray, + ): ByteArray } diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.jvmAndroid.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.jvmAndroid.kt index 986104fdb..a48ee39c6 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.jvmAndroid.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.jvmAndroid.kt @@ -65,6 +65,26 @@ actual class AESGCM actual constructor( null } + actual fun encrypt( + bytesToEncrypt: ByteArray, + aad: ByteArray, + ): ByteArray = + with(newCipher()) { + init(Cipher.ENCRYPT_MODE, keySpec(), param()) + updateAAD(aad) + doFinal(bytesToEncrypt) + } + + actual fun decrypt( + bytesToDecrypt: ByteArray, + aad: ByteArray, + ): ByteArray = + with(newCipher()) { + init(Cipher.DECRYPT_MODE, keySpec(), param()) + updateAAD(aad) + doFinal(bytesToDecrypt) + } + companion object { const val NAME = "aes-gcm" } diff --git a/quartz/src/linuxMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.linux.kt b/quartz/src/linuxMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.linux.kt index eb065059a..9536b80e5 100644 --- a/quartz/src/linuxMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.linux.kt +++ b/quartz/src/linuxMain/kotlin/com/vitorpamplona/quartz/utils/ciphers/AESGCM.linux.kt @@ -63,4 +63,22 @@ actual class AESGCM actual constructor( Log.w("AESGCM", "Failed to decrypt", e) null } + + @OptIn(DelicateCryptographyApi::class) + actual fun encrypt( + bytesToEncrypt: ByteArray, + aad: ByteArray, + ): ByteArray = + with(cipher()) { + encryptWithIvBlocking(nonce, bytesToEncrypt, aad) + } + + @OptIn(DelicateCryptographyApi::class) + actual fun decrypt( + bytesToDecrypt: ByteArray, + aad: ByteArray, + ): ByteArray = + with(cipher()) { + decryptWithIvBlocking(nonce, bytesToDecrypt, aad) + } }