Clean swift plugin main file
This commit is contained in:
parent
9007fded56
commit
f402e8bdf9
@ -5,9 +5,6 @@
|
|||||||
// Author: Hugo Pointcheval
|
// Author: Hugo Pointcheval
|
||||||
//
|
//
|
||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
|
||||||
import CommonCrypto
|
|
||||||
import Security
|
|
||||||
|
|
||||||
extension FlutterStandardTypedData {
|
extension FlutterStandardTypedData {
|
||||||
var uint8Array: Array<UInt8> {
|
var uint8Array: Array<UInt8> {
|
||||||
@ -20,276 +17,117 @@ extension FlutterStandardTypedData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 10.0, *)
|
|
||||||
func generateKeypair() {
|
|
||||||
var publicKeySec, privateKeySec: SecKey?
|
|
||||||
let keyattribute = [
|
|
||||||
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
||||||
kSecAttrKeySizeInBits as String : 256
|
|
||||||
] as CFDictionary
|
|
||||||
SecKeyGeneratePair(keyattribute, &publicKeySec, &privateKeySec)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func crypt(operation: Int, algorithm: Int, options: Int, key: Data,
|
|
||||||
initializationVector: Data, dataIn: Data) -> Data? {
|
|
||||||
return key.withUnsafeBytes { keyUnsafeRawBufferPointer in
|
|
||||||
return dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in
|
|
||||||
return initializationVector.withUnsafeBytes { ivUnsafeRawBufferPointer in
|
|
||||||
// Give the data out some breathing room for PKCS7's padding.
|
|
||||||
let dataOutSize: Int = dataIn.count + kCCBlockSizeAES128*2
|
|
||||||
let dataOut = UnsafeMutableRawPointer.allocate(byteCount: dataOutSize,
|
|
||||||
alignment: 1)
|
|
||||||
defer { dataOut.deallocate() }
|
|
||||||
var dataOutMoved: Int = 0
|
|
||||||
let status = CCCrypt(CCOperation(operation), CCAlgorithm(algorithm),
|
|
||||||
CCOptions(options),
|
|
||||||
keyUnsafeRawBufferPointer.baseAddress, key.count,
|
|
||||||
ivUnsafeRawBufferPointer.baseAddress,
|
|
||||||
dataInUnsafeRawBufferPointer.baseAddress, dataIn.count,
|
|
||||||
dataOut, dataOutSize, &dataOutMoved)
|
|
||||||
guard status == kCCSuccess else { return nil }
|
|
||||||
return Data(bytes: dataOut, count: dataOutMoved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pbkdf2(hash: CCPBKDFAlgorithm, password: String, salt: String, keyByteCount: Int, rounds: Int) -> Data? {
|
|
||||||
let passwordData = password.data(using: .utf8)!
|
|
||||||
let saltData = salt.data(using: .utf8)!
|
|
||||||
var derivedKeyData = Data(repeating: 0, count: keyByteCount)
|
|
||||||
|
|
||||||
var localDerivedKeyData = derivedKeyData
|
|
||||||
|
|
||||||
let derivationStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
|
|
||||||
saltData.withUnsafeBytes { saltBytes in
|
|
||||||
|
|
||||||
CCKeyDerivationPBKDF(
|
|
||||||
CCPBKDFAlgorithm(kCCPBKDF2),
|
|
||||||
password, passwordData.count,
|
|
||||||
saltBytes, saltData.count,
|
|
||||||
hash,
|
|
||||||
UInt32(rounds),
|
|
||||||
derivedKeyBytes, localDerivedKeyData.count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (derivationStatus != kCCSuccess) {
|
|
||||||
print("Error: \(derivationStatus)")
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
return derivedKeyData
|
|
||||||
}
|
|
||||||
|
|
||||||
func pbkdf2sha512(password: String, salt: String, keyByteCount: Int, rounds: Int) -> Data? {
|
|
||||||
return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pbkdf2sha256(password: String, salt: String, keyByteCount: Int, rounds: Int) -> Data? {
|
|
||||||
return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pbkdf2sha1(password: String, salt: String, keyByteCount: Int, rounds: Int) -> Data? {
|
|
||||||
return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func randomGenerateBytes(count: Int) -> Data? {
|
|
||||||
let bytes = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: 1)
|
|
||||||
defer { bytes.deallocate() }
|
|
||||||
let status = CCRandomGenerateBytes(bytes, count)
|
|
||||||
guard status == kCCSuccess else { return nil }
|
|
||||||
return Data(bytes: bytes, count: count)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Data {
|
|
||||||
/// Encrypts for you with all the good options turned on: CBC, an IV, PKCS7
|
|
||||||
/// padding (so your input data doesn't have to be any particular length).
|
|
||||||
/// Key can be 128, 192, or 256 bits.
|
|
||||||
/// Generates a fresh IV for you each time, and prefixes it to the
|
|
||||||
/// returned ciphertext.
|
|
||||||
func encryptAES256_CBC_PKCS7_IV(key: Data) -> Data? {
|
|
||||||
guard let iv = randomGenerateBytes(count: kCCBlockSizeAES128) else { return nil }
|
|
||||||
// No option is needed for CBC, it is on by default.
|
|
||||||
guard let ciphertext = crypt(operation: kCCEncrypt,
|
|
||||||
algorithm: kCCAlgorithmAES,
|
|
||||||
options: kCCOptionPKCS7Padding,
|
|
||||||
key: key,
|
|
||||||
initializationVector: iv,
|
|
||||||
dataIn: self) else { return nil }
|
|
||||||
return iv + ciphertext
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts self, where self is the IV then the ciphertext.
|
|
||||||
/// Key can be 128/192/256 bits.
|
|
||||||
func decryptAES256_CBC_PKCS7_IV(key: Data) -> Data? {
|
|
||||||
guard count > kCCBlockSizeAES128 else { return nil }
|
|
||||||
let iv = prefix(kCCBlockSizeAES128)
|
|
||||||
let ciphertext = suffix(from: kCCBlockSizeAES128)
|
|
||||||
return crypt(operation: kCCDecrypt, algorithm: kCCAlgorithmAES,
|
|
||||||
options: kCCOptionPKCS7Padding, key: key, initializationVector: iv,
|
|
||||||
dataIn: ciphertext)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Algorithm {
|
|
||||||
case sha256
|
|
||||||
|
|
||||||
var digestLength: Int {
|
|
||||||
switch self {
|
|
||||||
case .sha256: return Int(CC_SHA256_DIGEST_LENGTH)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hash(for algorithm: Algorithm) -> Data {
|
|
||||||
let hashBytes = UnsafeMutablePointer<UInt8>.allocate(capacity: algorithm.digestLength)
|
|
||||||
defer { hashBytes.deallocate() }
|
|
||||||
switch algorithm {
|
|
||||||
case .sha256:
|
|
||||||
withUnsafeBytes { (buffer) -> Void in
|
|
||||||
CC_SHA256(buffer.baseAddress!, CC_LONG(buffer.count), hashBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Data(bytes: hashBytes, count: algorithm.digestLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SwiftNativeCryptoPlugin: NSObject, FlutterPlugin {
|
public class SwiftNativeCryptoPlugin: NSObject, FlutterPlugin {
|
||||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
let channel = FlutterMethodChannel(name: "native.crypto.helper", binaryMessenger: registrar.messenger())
|
let channel = FlutterMethodChannel(name: "native.crypto", binaryMessenger: registrar.messenger())
|
||||||
let instance = SwiftNativeCryptoPlugin()
|
let instance = SwiftNativeCryptoPlugin()
|
||||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
switch call.method {
|
switch call.method {
|
||||||
|
case "digest":
|
||||||
|
let args = call.arguments as! NSDictionary
|
||||||
|
|
||||||
|
let message = (args["message"] as! FlutterStandardTypedData).data
|
||||||
|
let algo = args["algorithm"] as! String
|
||||||
|
|
||||||
|
let algorithm : HashAlgorithm? = HashAlgorithm.init(rawValue: algo)
|
||||||
|
|
||||||
|
let hash = Hash().digest(data: message, algorithm: algorithm!)
|
||||||
|
|
||||||
|
if hash != nil {
|
||||||
|
result(FlutterStandardTypedData.init(bytes: hash!))
|
||||||
|
} else {
|
||||||
|
result(FlutterError(code: "DIGESTERROR",
|
||||||
|
message: "DIGEST IS NIL.",
|
||||||
|
details: nil)
|
||||||
|
)
|
||||||
|
}
|
||||||
case "pbkdf2":
|
case "pbkdf2":
|
||||||
let args = call.arguments as! NSDictionary
|
let args = call.arguments as! NSDictionary
|
||||||
|
|
||||||
let password = args["password"] as! String
|
let password = args["password"] as! String
|
||||||
let salt = args["salt"] as! String
|
let salt = args["salt"] as! String
|
||||||
let keyLength = args["keyLength"] as! NSNumber
|
let keyLength = args["keyLength"] as! NSNumber
|
||||||
let iteration = args["iteration"] as! NSNumber
|
let iteration = args["iteration"] as! NSNumber
|
||||||
let algo = args["algorithm"] as! String
|
let algo = args["algorithm"] as! String
|
||||||
|
|
||||||
var keyBytes: Data?
|
let algorithm : HashAlgorithm? = HashAlgorithm.init(rawValue: algo)
|
||||||
|
|
||||||
if (algo == "sha1") {
|
let key = KeyDerivation().pbkdf2(password: password, salt: salt, keyLength: keyLength.intValue, iteration: iteration.intValue, algorithm: algorithm!)
|
||||||
keyBytes = pbkdf2sha1(password: password, salt: salt, keyByteCount: keyLength.intValue, rounds: iteration.intValue)
|
|
||||||
} else if (algo == "sha256"){
|
|
||||||
keyBytes = pbkdf2sha256(password: password, salt: salt, keyByteCount: keyLength.intValue, rounds: iteration.intValue)
|
|
||||||
} else if (algo == "sha512"){
|
|
||||||
keyBytes = pbkdf2sha512(password: password, salt: salt, keyByteCount: keyLength.intValue, rounds: iteration.intValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyBytes != nil {
|
if key != nil {
|
||||||
result(FlutterStandardTypedData.init(bytes: keyBytes!))
|
result(FlutterStandardTypedData.init(bytes: key!))
|
||||||
} else {
|
} else {
|
||||||
result(FlutterError(code: "PBKDF2ERROR",
|
result(FlutterError(code: "PBKDF2ERROR",
|
||||||
message: "PBKDF2 KEY IS NIL.",
|
message: "PBKDF2 KEY IS NIL.",
|
||||||
details: nil))
|
details: nil)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
case "keygen":
|
||||||
|
|
||||||
case "symKeygen":
|
|
||||||
let args = call.arguments as! NSDictionary
|
let args = call.arguments as! NSDictionary
|
||||||
let keySize = args["size"] as! NSNumber
|
|
||||||
|
|
||||||
let keyBytes = symKeygen(keySize: keySize)
|
let size = args["size"] as! NSNumber
|
||||||
|
|
||||||
if keyBytes != nil {
|
let key = KeyGeneration().keygen(size: size)
|
||||||
result(FlutterStandardTypedData.init(bytes: keyBytes!))
|
|
||||||
|
if key != nil {
|
||||||
|
result(FlutterStandardTypedData.init(bytes: key!))
|
||||||
} else {
|
} else {
|
||||||
result(FlutterError(code: "SYMKEYGENERROR",
|
result(FlutterError(code: "KEYGENERROR",
|
||||||
message: "GENERATED KEY IS NIL.",
|
message: "GENERATED KEY IS NIL.",
|
||||||
details: nil))
|
details: nil))
|
||||||
}
|
}
|
||||||
|
case "encrypt":
|
||||||
case "symEncrypt":
|
|
||||||
let args = call.arguments as! NSDictionary
|
let args = call.arguments as! NSDictionary
|
||||||
let payload = (args["payload"] as! FlutterStandardTypedData).data
|
|
||||||
let aesKey = (args["aesKey"] as! FlutterStandardTypedData).data
|
|
||||||
|
|
||||||
let encryptedPayloadIV = symEncrypt(payload: payload, aesKey: aesKey)
|
let data = (args["data"] as! FlutterStandardTypedData).data
|
||||||
|
let key = (args["key"] as! FlutterStandardTypedData).data
|
||||||
|
let algo = args["algorithm"] as! String
|
||||||
|
let mode = args["mode"] as! String
|
||||||
|
let padding = args["padding"] as! String
|
||||||
|
|
||||||
result(encryptedPayloadIV)
|
let algorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algo)
|
||||||
|
let modeEnum : BlockCipherMode? = BlockCipherMode.init(rawValue: mode)
|
||||||
|
let paddingEnum : Padding? = Padding.init(rawValue: padding)
|
||||||
|
|
||||||
case "symDecrypt":
|
let ciphertext = Cipher().encrypt(data: data, key: key, algorithm: algorithm!, mode: modeEnum!, padding: paddingEnum!)
|
||||||
|
|
||||||
|
if ciphertext != nil {
|
||||||
|
result(ciphertext)
|
||||||
|
} else {
|
||||||
|
result(FlutterError(code: "ENCRYPTIONERROR",
|
||||||
|
message: "ENCRYPTED PAYLOAD IS EMPTY.",
|
||||||
|
details: nil))
|
||||||
|
}
|
||||||
|
case "decrypt":
|
||||||
let args = call.arguments as! NSDictionary
|
let args = call.arguments as! NSDictionary
|
||||||
|
|
||||||
let payload = args["payload"] as! NSArray
|
let payload = args["payload"] as! NSArray
|
||||||
|
let key = (args["key"] as! FlutterStandardTypedData).data
|
||||||
|
let algo = args["algorithm"] as! String
|
||||||
|
let mode = args["mode"] as! String
|
||||||
|
let padding = args["padding"] as! String
|
||||||
|
|
||||||
let encrypted = (payload[0] as! FlutterStandardTypedData).data
|
let encrypted = (payload[0] as! FlutterStandardTypedData).data
|
||||||
let iv = (payload[1] as! FlutterStandardTypedData).data
|
let iv = (payload[1] as! FlutterStandardTypedData).data
|
||||||
let encryptedPayload = [encrypted, iv]
|
let encryptedPayload = [encrypted, iv]
|
||||||
|
|
||||||
let aesKey = (args["aesKey"] as! FlutterStandardTypedData).data
|
let algorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algo)
|
||||||
|
let modeEnum : BlockCipherMode? = BlockCipherMode.init(rawValue: mode)
|
||||||
|
let paddingEnum : Padding? = Padding.init(rawValue: padding)
|
||||||
|
|
||||||
let decryptedPayload = symDecrypt(payload: encryptedPayload, aesKey: aesKey)
|
let decrypted = Cipher().decrypt(payload: encryptedPayload, key: key, algorithm: algorithm!, mode: modeEnum!, padding: paddingEnum!)
|
||||||
|
|
||||||
if decryptedPayload != nil {
|
if decrypted != nil {
|
||||||
result(FlutterStandardTypedData.init(bytes: decryptedPayload!))
|
result(FlutterStandardTypedData.init(bytes: decrypted!))
|
||||||
} else {
|
} else {
|
||||||
result(FlutterError(code: "DECRYPTIONERROR",
|
result(FlutterError(code: "DECRYPTIONERROR",
|
||||||
message: "DECRYPTED PAYLOAD IS NIL. MAYBE VERIFICATION MAC IS UNVALID.",
|
message: "DECRYPTED PAYLOAD IS NIL. MAYBE VERIFICATION MAC IS UNVALID.",
|
||||||
details: nil))
|
details: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
default: result(FlutterMethodNotImplemented)
|
default: result(FlutterMethodNotImplemented)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func digest(input : Data) -> Data {
|
|
||||||
let hashed = input.hash(for: .sha256)
|
|
||||||
return hashed
|
|
||||||
}
|
|
||||||
|
|
||||||
func symKeygen(keySize : NSNumber) -> Data? {
|
|
||||||
var bytes = [Int8](repeating: 0, count: keySize.intValue / 8)
|
|
||||||
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
|
|
||||||
|
|
||||||
if status == errSecSuccess { // Always test the status.
|
|
||||||
let keyBytes = bytes.withUnsafeBytes {return Data(Array($0))}
|
|
||||||
return keyBytes
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func symEncrypt(payload : Data, aesKey : Data) -> [Data] {
|
|
||||||
let mac = digest(input: aesKey + payload)
|
|
||||||
let dataToEncrypt = mac + payload
|
|
||||||
var encrypted = dataToEncrypt.encryptAES256_CBC_PKCS7_IV(key: aesKey)!
|
|
||||||
|
|
||||||
// Create a range based on the length of data to return
|
|
||||||
let range = 0..<16
|
|
||||||
|
|
||||||
// Get a new copy of data
|
|
||||||
let iv = encrypted.subdata(in: range)
|
|
||||||
|
|
||||||
encrypted.removeSubrange(range)
|
|
||||||
|
|
||||||
return [encrypted, iv]
|
|
||||||
}
|
|
||||||
|
|
||||||
func symDecrypt(payload : [Data], aesKey : Data) -> Data? {
|
|
||||||
let encrypted = payload[1] + payload[0]
|
|
||||||
var decrypted = encrypted.decryptAES256_CBC_PKCS7_IV(key: aesKey)!
|
|
||||||
|
|
||||||
// Create a range based on the length of data to return
|
|
||||||
let range = 0..<32
|
|
||||||
|
|
||||||
// Get a new copy of data
|
|
||||||
let mac = decrypted.subdata(in: range)
|
|
||||||
decrypted.removeSubrange(range)
|
|
||||||
|
|
||||||
let verificationMac = digest(input: aesKey + decrypted)
|
|
||||||
|
|
||||||
if (mac.base64EncodedData() == verificationMac.base64EncodedData()) {
|
|
||||||
return decrypted
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user