From e0a390996f9022d22da918f662d04f2cc4a9ce48 Mon Sep 17 00:00:00 2001 From: yet Date: Fri, 15 May 2026 17:58:47 +0400 Subject: [PATCH] refactor(noise/protocol): migrate AESGCMOnCtrCipherState to Kotlin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Override CipherState properties; tighten `initializeKey` parameter type to non-null `ByteArray`. - Promote the constructor to a `@Throws(NoSuchAlgorithmException)` primary constructor with the `Cipher` instance assigned once as a non-null `val`, removing the bogus `cipher: Cipher? = null` initializer the IDE emitted. - Replace `Arrays.fill(arr, 0.toByte())` with `arr.fill(0)`. - Inline-byte XOR loop in `encryptWithAd` now casts back to `Byte` explicitly to satisfy Kotlin's stricter type rules. - `fork()` rethrows the (impossible) NoSuchAlgorithmException as IllegalStateException instead of returning null — matches the non-null contract on `CipherState.fork`. - AES/CTR keystream and GHASH paths preserved byte-for-byte. Co-Authored-By: Claude Opus 4.7 --- .../protocol/AESGCMOnCtrCipherState.kt | 77 ++++++++----------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMOnCtrCipherState.kt b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMOnCtrCipherState.kt index 9794bb88..d68b5d17 100644 --- a/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMOnCtrCipherState.kt +++ b/app/src/main/java/com/bitchat/android/noise/southernstorm/protocol/AESGCMOnCtrCipherState.kt @@ -27,7 +27,6 @@ import com.bitchat.android.noise.southernstorm.protocol.Noise.throwBadTagExcepti import java.security.InvalidAlgorithmParameterException import java.security.InvalidKeyException import java.security.NoSuchAlgorithmException -import java.util.Arrays import javax.crypto.BadPaddingException import javax.crypto.Cipher import javax.crypto.IllegalBlockSizeException @@ -43,33 +42,23 @@ import javax.crypto.spec.SecretKeySpec * This class is used on platforms that don't have "AES/GCM/NoPadding", * but which do have the older "AES/CTR/NoPadding". */ -internal class AESGCMOnCtrCipherState : CipherState { - private val cipher: Cipher? = null - private var keySpec: SecretKeySpec? - private var n: Long - private val iv: ByteArray - private val hashKey: ByteArray - private val ghash: GHASH +internal class AESGCMOnCtrCipherState +@Throws(NoSuchAlgorithmException::class) +constructor() : CipherState { + private val cipher: Cipher + private var keySpec: SecretKeySpec? = null + private var n: Long = 0 + private val iv: ByteArray = ByteArray(16) + private val hashKey: ByteArray = ByteArray(16) + private val ghash: GHASH = GHASH() - /** - * Constructs a new cipher state for the "AESGCM" algorithm. - * - * @throws NoSuchAlgorithmException The system does not have a - * provider for this algorithm. - */ init { - try { - cipher = Cipher.getInstance("AES/CTR/NoPadding") + cipher = try { + Cipher.getInstance("AES/CTR/NoPadding") } catch (e: NoSuchPaddingException) { // AES/CTR is available, but not the unpadded version? Huh? throw NoSuchAlgorithmException("AES/CTR/NoPadding not available", e) } - keySpec = null - n = 0 - iv = ByteArray(16) - hashKey = ByteArray(16) - ghash = GHASH() - // Try to set a 256-bit key on the cipher. Some JCE's are // configured to disallow 256-bit AES if an extra policy @@ -95,7 +84,7 @@ internal class AESGCMOnCtrCipherState : CipherState { keySpec = SecretKeySpec(ByteArray(32), "AES") val params = IvParameterSpec(iv) try { - cipher!!.init(Cipher.ENCRYPT_MODE, keySpec, params) + cipher.init(Cipher.ENCRYPT_MODE, keySpec, params) } catch (e: InvalidKeyException) { // Shouldn't happen. } catch (e: InvalidAlgorithmParameterException) { @@ -103,25 +92,20 @@ internal class AESGCMOnCtrCipherState : CipherState { } } - val cipherName: String - get() = "AESGCM" + override val cipherName: String get() = "AESGCM" + override val keyLength: Int get() = 32 + override val macLength: Int get() = if (keySpec != null) 16 else 0 - val keyLength: Int - get() = 32 - - val mACLength: Int - get() = if (keySpec != null) 16 else 0 - - override fun initializeKey(key: ByteArray?, offset: Int) { + override fun initializeKey(key: ByteArray, offset: Int) { // Set the encryption key. keySpec = SecretKeySpec(key, offset, 32, "AES") // Generate the hashing key by encrypting a block of zeroes. - Arrays.fill(iv, 0.toByte()) - Arrays.fill(hashKey, 0.toByte()) + iv.fill(0) + hashKey.fill(0) try { - cipher!!.init(Cipher.ENCRYPT_MODE, keySpec, IvParameterSpec(iv)) + cipher.init(Cipher.ENCRYPT_MODE, keySpec, IvParameterSpec(iv)) } catch (e: InvalidKeyException) { // Shouldn't happen. throw IllegalStateException(e) @@ -185,12 +169,12 @@ internal class AESGCMOnCtrCipherState : CipherState { // Initialize the CTR mode cipher with the key and IV. - cipher!!.init(Cipher.ENCRYPT_MODE, keySpec, IvParameterSpec(iv)) + cipher.init(Cipher.ENCRYPT_MODE, keySpec, IvParameterSpec(iv)) // Encrypt a block of zeroes to generate the hash key to XOR // the GHASH tag with at the end of the encrypt/decrypt operation. - Arrays.fill(hashKey, 0.toByte()) + hashKey.fill(0) try { cipher.update(hashKey, 0, 16, hashKey, 0) } catch (e: ShortBufferException) { @@ -232,7 +216,7 @@ internal class AESGCMOnCtrCipherState : CipherState { try { setup(ad) val result = - cipher!!.update(plaintext, plaintextOffset, length, ciphertext, ciphertextOffset) + cipher.update(plaintext, plaintextOffset, length, ciphertext, ciphertextOffset) cipher.doFinal(ciphertext, ciphertextOffset + result) } catch (e: InvalidKeyException) { // Shouldn't happen. @@ -250,8 +234,10 @@ internal class AESGCMOnCtrCipherState : CipherState { ghash.update(ciphertext, ciphertextOffset, length) ghash.pad((if (ad != null) ad.size else 0).toLong(), length.toLong()) ghash.finish(ciphertext, ciphertextOffset + length, 16) - for (index in 0..15) ciphertext[ciphertextOffset + length + index] = - ciphertext[ciphertextOffset + length + index].toInt() xor hashKey[index].toInt() + for (index in 0..15) { + ciphertext[ciphertextOffset + length + index] = + (ciphertext[ciphertextOffset + length + index].toInt() xor hashKey[index].toInt()).toByte() + } return length + 16 } @@ -303,7 +289,7 @@ internal class AESGCMOnCtrCipherState : CipherState { if ((temp and 0xFF) != 0) throwBadTagException() try { val result = - cipher!!.update(ciphertext, ciphertextOffset, dataLen, plaintext, plaintextOffset) + cipher.update(ciphertext, ciphertextOffset, dataLen, plaintext, plaintextOffset) cipher.doFinal(plaintext, plaintextOffset + result) } catch (e: IllegalBlockSizeException) { // Shouldn't happen. @@ -316,12 +302,11 @@ internal class AESGCMOnCtrCipherState : CipherState { } override fun fork(key: ByteArray, offset: Int): CipherState { - val cipher: CipherState - try { - cipher = AESGCMOnCtrCipherState() + val cipher: CipherState = try { + AESGCMOnCtrCipherState() } catch (e: NoSuchAlgorithmException) { - // Shouldn't happen. - return null + // Shouldn't happen — this state already exists, so the algorithm is available. + throw IllegalStateException(e) } cipher.initializeKey(key, offset) return cipher