perf(ios): optimize swift code
This commit is contained in:
parent
142dd17ad2
commit
2ed8aab69f
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -6,3 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol Key {
|
||||
var bytes: Data { get set }
|
||||
}
|
||||
|
@ -6,3 +6,8 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol KeyDerivation {
|
||||
var algorithm : KdfAlgorithm { get }
|
||||
func derive() throws -> SecretKey
|
||||
}
|
||||
|
@ -6,3 +6,13 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum CipherAlgorithm : String {
|
||||
case aes = "aes"
|
||||
|
||||
var getCipher: Cipher {
|
||||
switch self {
|
||||
case .aes: return AESCipher()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -6,3 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum KdfAlgorithm {
|
||||
case pbkdf2
|
||||
}
|
||||
|
@ -6,3 +6,13 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum NativeCryptoError : Error {
|
||||
case decryptionError
|
||||
case encryptionError
|
||||
case messageDigestError
|
||||
case pbkdf2Error
|
||||
case cipherError
|
||||
case resultError
|
||||
case exceptionError
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user