diff --git a/native/expo-modules/aes-crypto/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt b/native/expo-modules/aes-crypto/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt --- a/native/expo-modules/aes-crypto/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt +++ b/native/expo-modules/aes-crypto/android/src/main/java/app/comm/android/aescrypto/AESCryptoModule.kt @@ -8,6 +8,7 @@ import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey +import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.SecretKeySpec private const val ALGORITHM_AES = "AES" @@ -24,6 +25,7 @@ Function("generateKey", this@AESCryptoModule::generateKey) Function("encrypt", this@AESCryptoModule::encrypt) + Function("decrypt", this@AESCryptoModule::decrypt) } // region Function implementations @@ -75,6 +77,33 @@ destination.write(ciphertext, position = IV_LENGTH, size = ciphertext.size) } + /** + * Decrypts given [ciphertext] using provided key and stores decrypted + * plaintext in the [destination] array. + * + * @param rawKey AES-256 key bytes. Must be of length [KEY_SIZE] + * @param ciphertext Typed array consisting of 12-byte IV, followed by + * actual ciphertext content and ending with 16-byte GCM tag. + * @param destination should be of ciphertext content length + */ + private fun decrypt( + rawKey: Uint8Array, + ciphertext: Uint8Array, + destination: Uint8Array + ) { + if (ciphertext.byteLength <= IV_LENGTH + TAG_LENGTH || + destination.byteLength < ciphertext.byteLength - IV_LENGTH - TAG_LENGTH) { + throw InvalidDataLengthException() + } + val key = rawKey.toAESSecretKey() + val input = ciphertext.toDirectBuffer() + val iv = ByteArray(IV_LENGTH).also(input::get) + val ciphertextBytes = ByteArray(input.remaining()).also(input::get) + val plaintext = decryptAES(ciphertextBytes, key, iv) + + destination.write(plaintext, position = 0, size = plaintext.size) + } + // endregion } @@ -99,6 +128,22 @@ return Pair(iv, ciphertext) } +/** + * Does the reverse of the [encryptAES] function. + * Decrypts the [ciphertext] with given [key] and [iv] + */ +private fun decryptAES( + ciphertext: ByteArray, + key: SecretKey, + iv: ByteArray +): ByteArray { + val spec = GCMParameterSpec(TAG_LENGTH * 8, iv) + val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION_NAME).apply { + init(Cipher.DECRYPT_MODE, key, spec) + } + return cipher.doFinal(ciphertext) +} + // endregion // region Utility extension functions diff --git a/native/utils/aes-crypto-module.js b/native/utils/aes-crypto-module.js --- a/native/utils/aes-crypto-module.js +++ b/native/utils/aes-crypto-module.js @@ -1,7 +1,6 @@ // @flow import { requireNativeModule } from 'expo-modules-core'; -import invariant from 'invariant'; const KEY_SIZE = 32; const IV_LENGTH = 12; @@ -34,7 +33,6 @@ } export function decrypt(key: Uint8Array, data: Uint8Array): Uint8Array { - invariant(AESCryptoModule.decrypt, 'AESCrypto.decrypt is not implemented'); const plaintext = new Uint8Array(data.length - IV_LENGTH - TAG_LENGTH); AESCryptoModule.decrypt(key, data, plaintext); return plaintext;