Changeset View
Changeset View
Standalone View
Standalone View
native/expo-modules/aes-crypto/ios/AESCryptoModule.swift
import ExpoModulesCore | import ExpoModulesCore | ||||
import CryptoKit | import CryptoKit | ||||
private let KEY_SIZE = 32 // bytes | private let KEY_SIZE = 32 // bytes | ||||
private let IV_LENGTH = 12 // bytes, IV - unique Initialization Vector (nonce) | |||||
private let TAG_LENGTH = 16 // bytes - GCM auth tag | |||||
public class AESCryptoModule: Module { | public class AESCryptoModule: Module { | ||||
public func definition() -> ModuleDefinition { | public func definition() -> ModuleDefinition { | ||||
Name("AESCrypto") | Name("AESCrypto") | ||||
Function("generateKey", generateKey) | Function("generateKey", generateKey) | ||||
Function("encrypt", encrypt) | |||||
} | } | ||||
} | } | ||||
// MARK: - Function implementations | // MARK: - Function implementations | ||||
private func generateKey(destination: Uint8Array) throws { | private func generateKey(destination: Uint8Array) throws { | ||||
guard destination.byteLength == KEY_SIZE else { | guard destination.byteLength == KEY_SIZE else { | ||||
throw InvalidKeyLengthException() | throw InvalidKeyLengthException() | ||||
} | } | ||||
let key = SymmetricKey(size: .bits256) | let key = SymmetricKey(size: .bits256) | ||||
key.withUnsafeBytes { bytes in | key.withUnsafeBytes { bytes in | ||||
let _ = bytes.copyBytes(to: destination.rawBufferPtr()) | let _ = bytes.copyBytes(to: destination.rawBufferPtr()) | ||||
} | } | ||||
} | } | ||||
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 sealedData = encryptionResult.combined else { | |||||
// this happens only if Nonce/IV != 12 bytes long | |||||
throw EncryptionFailedException("Incorrect AES configuration") | |||||
} | |||||
guard sealedData.count == destination.byteLength else { | |||||
throw EncryptionFailedException("Encrypted data has unexpected length") | |||||
} | |||||
sealedData.copyBytes(to: destination.rawBufferPtr()) | |||||
} | |||||
// MARK: - Utilities | // MARK: - Utilities | ||||
extension TypedArray { | extension TypedArray { | ||||
func data() -> Data { | |||||
Data(bytes: self.rawPointer, count: self.byteLength) | |||||
} | |||||
func rawBufferPtr() -> UnsafeMutableRawBufferPointer { | func rawBufferPtr() -> UnsafeMutableRawBufferPointer { | ||||
UnsafeMutableRawBufferPointer(start: self.rawPointer, | UnsafeMutableRawBufferPointer(start: self.rawPointer, | ||||
count: self.byteLength) | count: self.byteLength) | ||||
} | } | ||||
} | } | ||||
// MARK: - Exception definitions | // MARK: - Exception definitions | ||||
private class InvalidKeyLengthException: Exception { | private class InvalidKeyLengthException: Exception { | ||||
override var reason: String { | override var reason: String { | ||||
"The AES key has invalid length" | "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<String> { | |||||
override var reason: String { | |||||
"Failed to encrypt data: \(param)" | |||||
} | |||||
} |