From 8a7afdf794e0cc858432c12d06f05040d1117e1a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 01:53:57 +0000 Subject: [PATCH] fix: use 12-byte GCM IV length in KeyStoreEncryption.decrypt Marmot MLS groups were being wiped on every app restart. The encrypted state was written with a 12-byte GCM IV (from cipher.iv after ENCRYPT_MODE init), but decrypt was reading cipher.blockSize (= 16, the AES block size) bytes as the IV. Every decrypt therefore failed, MlsGroupManager.restoreAll caught the exception and deleted the "corrupt" group directory, and the Marmot group list came up empty after each restart. Use a 12-byte IV constant in both encrypt and decrypt so the round-trip works. This matches the sibling KeyStoreEncryption in commons/androidMain (which already hardcodes 12). --- .../amethyst/model/preferences/KeyStoreEncryption.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/KeyStoreEncryption.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/KeyStoreEncryption.kt index f73d02dc5..2a0500560 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/KeyStoreEncryption.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/preferences/KeyStoreEncryption.kt @@ -38,6 +38,7 @@ class KeyStoreEncryption { private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING" private const val PURPOSE = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT private const val KEY_ALIAS = "AMETHYST_AES_KEY" + private const val GCM_IV_LENGTH = 12 } private val cipher = Cipher.getInstance(TRANSFORMATION) @@ -93,9 +94,11 @@ class KeyStoreEncryption { } fun decrypt(bytes: ByteArray): ByteArray? { - // Extracts IV and decrypts the data - val iv = bytes.copyOfRange(0, cipher.blockSize) - val data = bytes.copyOfRange(cipher.blockSize, bytes.size) + // Extracts IV and decrypts the data. GCM mode uses a 12-byte IV, + // which is what cipher.iv returns in encrypt() — not the AES block + // size (16), which is what cipher.blockSize would return. + val iv = bytes.copyOfRange(0, GCM_IV_LENGTH) + val data = bytes.copyOfRange(GCM_IV_LENGTH, bytes.size) cipher.init(Cipher.DECRYPT_MODE, getKey(), IvParameterSpec(iv)) return cipher.doFinal(data) }