diff --git a/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift b/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift --- a/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift +++ b/native/expo-modules/aes-crypto/ios/AESCryptoModule.swift @@ -1,11 +1,15 @@ import ExpoModulesCore import CryptoKit +private let IV_LENGTH = 12 // bytes +private let TAG_LENGTH = 16 // bytes + public class AESCryptoModule: Module { public func definition() -> ModuleDefinition { Name("AESCrypto") Function("generateKey", generateKey) + Function("encrypt", encrypt) } } @@ -22,9 +26,35 @@ } } +private func encrypt(rawKey: Uint8Array, + plaintext: Uint8Array, + destination: Uint8Array) throws { + guard destination.byteLength >= plaintext.byteLength + IV_LENGTH + TAG_LENGTH + else { + throw InvalidDataLengthException() + } + + let key = SymmetricKey(data: rawKey.data()) + let iv = AES.GCM.Nonce() + let encryptionResult = try AES.GCM.seal(plaintext.data(), + using: key, + nonce: iv) + + // 'combined' returns concatenated: iv || ciphertext || tag + guard let ciphertext = encryptionResult.combined else { + // this happens only if Nonce/IV != 12 bytes long + throw EncryptionFailedException("Incorrect AES configuration") + } + ciphertext.copyBytes(to: destination.rawBufferPtr()) +} + // MARK: - Utilities extension TypedArray { + func data() -> Data { + Data(bytes: self.rawPointer, count: self.byteLength) + } + func rawBufferPtr() -> UnsafeMutableRawBufferPointer { UnsafeMutableRawBufferPointer(start: self.rawPointer, count: self.byteLength) @@ -38,3 +68,15 @@ "The AES key has invalid length" } } + +private class InvalidDataLengthException: Exception { + override var reason: String { + "Source or destination array has invalid length" + } +} + +private class EncryptionFailedException: GenericException { + override var reason: String { + "Failed to encrypt data: \(param)" + } +} 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,11 +1,19 @@ // @flow import { requireNativeModule } from 'expo-modules-core'; +import invariant from 'invariant'; const KEY_SIZE = 32; +const IV_LENGTH = 12; +const TAG_LENGTH = 16; const AESCryptoModule: { +generateKey: (destination: Uint8Array) => void, + +encrypt: ( + key: Uint8Array, + data: Uint8Array, + destination: Uint8Array, + ) => void, } = requireNativeModule('AESCrypto'); export function generateKey(): Uint8Array { @@ -13,3 +21,10 @@ AESCryptoModule.generateKey(key); return key; } + +export function encrypt(key: Uint8Array, data: Uint8Array): Uint8Array { + invariant(AESCryptoModule.encrypt, 'AESCrypto.encrypt is not implemented'); + const ciphertext = new Uint8Array(data.length + IV_LENGTH + TAG_LENGTH); + AESCryptoModule.encrypt(key, data, ciphertext); + return ciphertext; +}