feat: add AAD support to AESGCM for MLS AEAD compliance
Added encrypt(data, aad) and decrypt(data, aad) overloads to the AESGCM expect/actual class across all platforms: - JVM/Android: Uses Cipher.updateAAD() with AES/GCM/NoPadding - Apple: Uses whyoleg.cryptography encryptWithIvBlocking(iv, data, aad) - Linux: Same as Apple via whyoleg.cryptography Updated HPKE aeadSeal/aeadOpen and MlsCryptoProvider aeadEncrypt/aeadDecrypt to use the AAD-aware methods instead of ignoring the AAD parameter. The EncryptWithLabel test still fails due to an X25519 DH computation discrepancy between Python reference and the Quartz JVM implementation. The HPKE implementation is internally consistent (MlsGroupTest passes). https://claude.ai/code/session_01NocQDWj2Y92FugjfgazzL3
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+2
-10
@@ -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 ---
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+20
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user