perf(ios): optimize swift code

This commit is contained in:
Hugo Pointcheval 2022-05-25 15:43:12 +02:00
parent 142dd17ad2
commit 2ed8aab69f
Signed by: hugo
GPG Key ID: A9E8E9615379254F
12 changed files with 370 additions and 96 deletions

View File

@ -3,93 +3,144 @@ import UIKit
@available(iOS 13.0, *)
public class SwiftNativeCryptoIosPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "plugins.hugop.cl/native_crypto", binaryMessenger: registrar.messenger())
let instance = SwiftNativeCryptoIosPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "digest":
let args : NSDictionary = call.arguments as! NSDictionary
let data : Data = (args["data"] as! FlutterStandardTypedData).data
let algo : String = args["algorithm"] as! String
let algorithm : HashAlgorithm? = HashAlgorithm.init(rawValue: algo)
// TODO(hpcl): check if algorithm is null
// TODO(hpcl): check if digest is null
result(FlutterStandardTypedData.init(bytes: Hash.digest(data: data, algorithm: algorithm!)))
case "generateSecretKey":
let args : NSDictionary = call.arguments as! NSDictionary
let bitsCount : NSNumber = args["bitsCount"] as! NSNumber
// TODO(hpcl): check if secure random is null
result(FlutterStandardTypedData.init(bytes: Key.fromSecureRandom(bitsCount: bitsCount.intValue)))
case "generateKeyPair":
result(FlutterStandardTypedData.init(bytes: KeyPair.fromCurve()))
case "pbkdf2":
let args : NSDictionary = call.arguments as! NSDictionary
let password : String = args["password"] as! String
let salt : String = args["salt"] as! String
let keyBytesCount : NSNumber = args["keyBytesCount"] as! NSNumber
let iterations : NSNumber = args["iterations"] as! NSNumber
let algo : String = args["algorithm"] as! String
let algorithm : HashAlgorithm? = HashAlgorithm.init(rawValue: algo)
// TODO(hpcl): check if algorithm is null
// TODO(hpcl): check if derivation is null
result(FlutterStandardTypedData.init(bytes: Key.fromPBKDF2(password: password, salt: salt, keyBytesCount: keyBytesCount.intValue, iterations: iterations.intValue, algorithm: algorithm!)!))
case "encrypt":
let args : NSDictionary = call.arguments as! NSDictionary
let data : Data = (args["data"] as! FlutterStandardTypedData).data
let key : Data = (args["key"] as! FlutterStandardTypedData).data
let algo : String = args["algorithm"] as! String
// TODO(hpcl): check if algorithm is null
// TODO(hpcl): check if ciphertext is null
var ciphertext : Data
switch algo {
case "aes":
ciphertext = AESCipher.encrypt(plaintext: data, key: key)!
case "chachapoly":
ciphertext = CHACHACipher.encrypt(plaintext: data, key: key)!
default:
ciphertext = Data.init();
}
result(FlutterStandardTypedData.init(bytes: ciphertext))
case "decrypt":
let args : NSDictionary = call.arguments as! NSDictionary
let data : Data = (args["data"] as! FlutterStandardTypedData).data
let key : Data = (args["key"] as! FlutterStandardTypedData).data
let algo : String = args["algorithm"] as! String
// TODO(hpcl): check if algorithm is null
// TODO(hpcl): check if ciphertext is null
var ciphertext : Data
switch algo {
case "aes":
ciphertext = AESCipher.decrypt(ciphertext: data, key: key)!
case "chachapoly":
ciphertext = CHACHACipher.decrypt(ciphertext: data, key: key)!
default:
ciphertext = Data.init();
}
result(FlutterStandardTypedData.init(bytes: ciphertext))
case "generateSharedSecretKey":
let args : NSDictionary = call.arguments as! NSDictionary
let salt : Data = (args["salt"] as! FlutterStandardTypedData).data
let keyBytesCount : NSNumber = args["keyBytesCount"] as! NSNumber
let ephemeralPrivateKey : Data = (args["ephemeralPrivateKey"] as! FlutterStandardTypedData).data
let otherPublicKey : Data = (args["otherPublicKey"] as! FlutterStandardTypedData).data
let hkdfAlgorithm : String = args["hkdfAlgorithm"] as! String
let algorithm : HashAlgorithm? = HashAlgorithm.init(rawValue: hkdfAlgorithm)
// TODO(hpcl): check if algorithm is null
// TODO(hpcl): check if generated key is null
result(FlutterStandardTypedData.init(bytes: ECDH.generateSharedSecretKey(salt: salt, hash: algorithm!, keyBytesCount: keyBytesCount.intValue, privateKey: ephemeralPrivateKey, publicKey: otherPublicKey)!))
static let name: String = "plugins.hugop.cl/native_crypto"
default: result(FlutterMethodNotImplemented)
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: name, binaryMessenger: registrar.messenger())
let instance = SwiftNativeCryptoIosPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "digest": _call(task: handleDigest(call: call), result: result)
case "generateSecretKey": _call(task: handleGenerateSecretKey(call: call), result: result)
case "pbkdf2": _call(task: handlePbkdf2(call: call), result: result)
case "encryptAsList": _call(task: handleEncryptAsList(call: call), result: result)
case "decryptAsList": _call(task: handleDecryptAsList(call: call), result: result)
case "encrypt": _call(task: handleCrypt(call: call, forEncryption: true), result: result)
case "decrypt": _call(task: handleCrypt(call: call, forEncryption: false), result: result)
default: result(FlutterMethodNotImplemented)
}
}
private func _call<T>(task: Task<T>, result: @escaping FlutterResult) {
task.call()
task.finalize(callback: {(task: Task<T>) in
if (task.isSuccessful()) {
result(task.getResult()!)
} else {
let exception: Error = task.getException()
let message = exception.localizedDescription
result(FlutterError(code: "native_crypto", message: message, details: nil))
}
})
}
private func handleDigest(call: FlutterMethodCall) -> Task<FlutterStandardTypedData> {
return Task(task: {
let args : NSDictionary = call.arguments as! NSDictionary
let data : Data = (args["data"] as! FlutterStandardTypedData).data
let algorithm : String = args["algorithm"] as! String
return FlutterStandardTypedData.init(bytes: try HashAlgorithm.digest(data: data, algorithm: algorithm))
})
}
private func handleGenerateSecretKey(call: FlutterMethodCall) -> Task<FlutterStandardTypedData> {
return Task(task: {
let args : NSDictionary = call.arguments as! NSDictionary
let bitsCount : NSNumber = args["bitsCount"] as! NSNumber
return FlutterStandardTypedData.init(bytes: SecretKey(fromSecureRandom: bitsCount.intValue).bytes)
})
}
private func handlePbkdf2(call: FlutterMethodCall) -> Task<FlutterStandardTypedData> {
return Task(task: {
let args : NSDictionary = call.arguments as! NSDictionary
let password : String = args["password"] as! String
let salt : String = args["salt"] as! String
let keyBytesCount : NSNumber = args["keyBytesCount"] as! NSNumber
let iterations : NSNumber = args["iterations"] as! NSNumber
let algorithm : String = args["algorithm"] as! String
let pbkdf2 : Pbkdf2 = Pbkdf2(keyBytesCount: keyBytesCount.intValue, iterations: iterations.intValue)
pbkdf2.hash = HashAlgorithm.init(rawValue: algorithm) ?? pbkdf2.hash
pbkdf2.initialize(password: password, salt: salt)
return FlutterStandardTypedData.init(bytes: try pbkdf2.derive().bytes)
})
}
private func handleEncryptAsList(call: FlutterMethodCall) -> Task<Array<Data>> {
return Task(task: {
let args : NSDictionary = call.arguments as! NSDictionary
let data : Data = (args["data"] as! FlutterStandardTypedData).data
let key : Data = (args["key"] as! FlutterStandardTypedData).data
let algorithm : String = args["algorithm"] as! String
let cipherAlgorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algorithm)
var cipher : Cipher
if (cipherAlgorithm != nil) {
cipher = cipherAlgorithm!.getCipher
} else {
throw NativeCryptoError.cipherError
}
return try cipher.encryptAsList(data: data, key: key)
})
}
private func handleDecryptAsList(call: FlutterMethodCall) -> Task<FlutterStandardTypedData> {
return Task(task: {
let args : NSDictionary = call.arguments as! NSDictionary
let data = args["data"] as! NSArray
let key : Data = (args["key"] as! FlutterStandardTypedData).data
let algorithm : String = args["algorithm"] as! String
let iv = (data[0] as! FlutterStandardTypedData).data
let encrypted = (data[1] as! FlutterStandardTypedData).data
let cipherAlgorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algorithm)
var cipher : Cipher
if (cipherAlgorithm != nil) {
cipher = cipherAlgorithm!.getCipher
} else {
throw NativeCryptoError.cipherError
}
return FlutterStandardTypedData.init(bytes: try cipher.decryptAsList(data: [iv, encrypted], key: key))
})
}
private func handleCrypt(call: FlutterMethodCall, forEncryption: Bool) -> Task<FlutterStandardTypedData> {
return Task(task: {
let args : NSDictionary = call.arguments as! NSDictionary
let data : Data = (args["data"] as! FlutterStandardTypedData).data
let key : Data = (args["key"] as! FlutterStandardTypedData).data
let algorithm : String = args["algorithm"] as! String
let cipherAlgorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algorithm)
var cipher : Cipher
if (cipherAlgorithm != nil) {
cipher = cipherAlgorithm!.getCipher
} else {
throw NativeCryptoError.cipherError
}
if (forEncryption) {
return FlutterStandardTypedData.init(bytes: try cipher.encrypt(data: data, key: key))
} else {
return FlutterStandardTypedData.init(bytes: try cipher.decrypt(data: data, key: key))
}
})
}
}

View File

@ -1,28 +1,57 @@
//
// AES.swift
// AESCipher.swift
// native_crypto_ios
//
// Created by Hugo Pointcheval on 25/05/2022.
//
import Foundation
import CryptoKit
class AES : Cipher {
class AESCipher : Cipher {
var algorithm: CipherAlgorithm = CipherAlgorithm.aes
/// Encrypts plaintext with key using AES GCM
@available(iOS 13.0, *)
static func encrypt(plaintext: Data, key: Data) -> Data? {
let symmetricKey = SymmetricKey.init(data: key)
let encrypted = try? AES.GCM.seal(plaintext, using: symmetricKey)
return encrypted?.combined
func encrypt(data: Data, key: Data) throws -> Data {
let symmetricKey : SymmetricKey = SymmetricKey.init(data: key)
let encrypted : AES.GCM.SealedBox? = try? AES.GCM.seal(data, using: symmetricKey)
let encryptedData : Data? = encrypted?.combined
if (encryptedData == nil) {
throw NativeCryptoError.encryptionError
}
return encryptedData!
}
/// Decrypts ciphertext with key using AES GCM
@available(iOS 13.0, *)
static func decrypt(ciphertext: Data, key: Data) -> Data? {
func decrypt(data: Data, key: Data) throws -> Data {
let symmetricKey = SymmetricKey.init(data: key)
let sealedBox = try? AES.GCM.SealedBox(combined: ciphertext)
if (sealedBox == nil) { return nil }
let sealedBox = try? AES.GCM.SealedBox(combined: data)
if (sealedBox == nil) { return Data.init() }
let decryptedData = try? AES.GCM.open(sealedBox!, using: symmetricKey)
if (decryptedData == nil) {
throw NativeCryptoError.decryptionError
}
return decryptedData!
}
func encryptAsList(data: Data, key: Data) throws -> [Data] {
let encryptedData = try encrypt(data: data, key: key)
let iv = encryptedData.prefix(12)
let data = encryptedData.suffix(from: 12)
return [iv, data]
}
func decryptAsList(data: [Data], key: Data) throws -> Data {
var encryptedData = data.first!
let data = data.last!
encryptedData.append(data)
let decryptedData = try decrypt(data: encryptedData, key: key)
return decryptedData
}
}

View File

@ -6,3 +6,58 @@
//
import Foundation
import CommonCrypto
class Pbkdf2 : KeyDerivation {
var algorithm: KdfAlgorithm = KdfAlgorithm.pbkdf2
var keyBytesCount: Int
var iterations: Int
var hash: HashAlgorithm = HashAlgorithm.HashSHA256
var password: String? = nil
var salt: String? = nil
init(keyBytesCount: Int, iterations: Int) {
self.keyBytesCount = keyBytesCount
self.iterations = iterations
}
func initialize(password: String, salt: String) {
self.password = password
self.salt = salt
}
func derive() throws -> SecretKey {
if (password == nil || salt == nil) {
throw NativeCryptoError.pbkdf2Error
}
let passwordData = password!.data(using: .utf8)!
let saltData = salt!.data(using: .utf8)!
var derivedKeyData = Data(repeating: 0, count: keyBytesCount)
let localDerivedKeyData = derivedKeyData
let status = derivedKeyData.withUnsafeMutableBytes { (derivedKeyBytes: UnsafeMutableRawBufferPointer) in
saltData.withUnsafeBytes { (saltBytes: UnsafeRawBufferPointer) in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password,
passwordData.count,
saltBytes.bindMemory(to: UInt8.self).baseAddress,
saltData.count,
hash.pbkdf2identifier,
UInt32(iterations),
derivedKeyBytes.bindMemory(to: UInt8.self).baseAddress,
localDerivedKeyData.count)
}
}
if (status != kCCSuccess) {
throw NativeCryptoError.pbkdf2Error
}
return SecretKey(derivedKeyData)
}
}

View File

@ -6,3 +6,20 @@
//
import Foundation
import CryptoKit
class SecretKey : Key {
var bytes: Data
init(_ bytes: Data) {
self.bytes = bytes
}
init(fromSecureRandom bitsCount: Int) {
let symmetricKey = SymmetricKey.init(size: SymmetricKeySize(bitCount: bitsCount))
bytes = symmetricKey.withUnsafeBytes
{
return Data(Array($0))
}
}
}

View File

@ -6,3 +6,11 @@
//
import Foundation
protocol Cipher {
var algorithm: CipherAlgorithm { get }
func encrypt(data: Data, key: Data) throws -> Data
func decrypt(data: Data, key: Data) throws -> Data
func encryptAsList(data: Data, key: Data) throws -> [Data]
func decryptAsList(data: [Data], key: Data) throws-> Data
}

View File

@ -6,3 +6,7 @@
//
import Foundation
protocol Key {
var bytes: Data { get set }
}

View File

@ -6,3 +6,8 @@
//
import Foundation
protocol KeyDerivation {
var algorithm : KdfAlgorithm { get }
func derive() throws -> SecretKey
}

View File

@ -6,3 +6,13 @@
//
import Foundation
enum CipherAlgorithm : String {
case aes = "aes"
var getCipher: Cipher {
switch self {
case .aes: return AESCipher()
}
}
}

View File

@ -6,3 +6,40 @@
//
import Foundation
import CommonCrypto
import CryptoKit
enum HashAlgorithm: String {
case HashSHA256 = "sha256"
case HashSHA384 = "sha384"
case HashSHA512 = "sha512"
var pbkdf2identifier: UInt32 {
switch self {
case .HashSHA256: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256)
case .HashSHA384: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA384)
case .HashSHA512: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512)
}
}
@available(iOS 13.0, *)
func digest(data: Data) -> Data {
switch self {
case .HashSHA256:
return Data(SHA256.hash(data: data))
case .HashSHA384:
return Data(SHA384.hash(data: data))
case .HashSHA512:
return Data(SHA512.hash(data: data))
}
}
@available(iOS 13.0, *)
static func digest(data: Data, algorithm: String) throws -> Data {
let algo = HashAlgorithm.init(rawValue: algorithm)
if (algo == nil) {
throw NativeCryptoError.messageDigestError
}
return algo!.digest(data: data)
}
}

View File

@ -6,3 +6,7 @@
//
import Foundation
enum KdfAlgorithm {
case pbkdf2
}

View File

@ -6,3 +6,13 @@
//
import Foundation
enum NativeCryptoError : Error {
case decryptionError
case encryptionError
case messageDigestError
case pbkdf2Error
case cipherError
case resultError
case exceptionError
}

View File

@ -6,3 +6,47 @@
//
import Foundation
class Task<T> {
var task: () throws -> T
private var successful: Bool = false
private var result: T? = nil
private var exception: Error? = nil
init(task: @escaping () throws -> T) {
self.task = task
}
func isSuccessful() -> Bool {
return successful
}
func getResult() -> T? {
return result
}
func getException() -> Error {
if (exception != nil) {
return exception!
} else {
return NativeCryptoError.exceptionError
}
}
func call() {
do {
result = try task()
exception = nil
successful = true
} catch {
exception = error
result = nil
successful = false
}
}
func finalize(callback: (_ task: Task<T>) -> Void) {
callback(self)
}
}