Last active
December 26, 2018 10:38
-
-
Save sroddy/12e2ff63eb4ebe9f17f858edff438fc0 to your computer and use it in GitHub Desktop.
Flutter Simple Cryptor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
abstract class SimpleCryptor { | |
Future<String> generateRandomKey(); | |
Future<String> encrypt(String data, String key); | |
Future<String> decrypt(String data, String key); | |
} | |
class PlatformSimpleCryptor implements SimpleCryptor { | |
static const MethodChannel _channel = | |
const MethodChannel('simple_encryption'); | |
@override | |
Future<String> decrypt(String data, String key) => | |
_channel.invokeMethod("decrypt", { | |
"data": data, | |
"key": key, | |
}); | |
@override | |
Future<String> encrypt(String data, String key) => | |
_channel.invokeMethod("encrypt", { | |
"data": data, | |
"key": key, | |
}); | |
@override | |
Future<String> generateRandomKey() => | |
_channel.invokeMethod("generate_random_key"); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.bendingspoons.simpleencryption | |
import com.tozny.crypto.android.AesCbcWithIntegrity.* | |
import io.flutter.plugin.common.MethodChannel | |
import io.flutter.plugin.common.MethodChannel.MethodCallHandler | |
import io.flutter.plugin.common.MethodChannel.Result | |
import io.flutter.plugin.common.MethodCall | |
import io.flutter.plugin.common.PluginRegistry.Registrar | |
class SimpleEncryptionPlugin() : MethodCallHandler { | |
companion object { | |
@JvmStatic | |
fun registerWith(registrar: Registrar) { | |
val channel = MethodChannel(registrar.messenger(), "simple_encryption") | |
channel.setMethodCallHandler(SimpleEncryptionPlugin()) | |
} | |
} | |
override fun onMethodCall(call: MethodCall, result: Result) { | |
when (call.method) { | |
"decrypt" -> { | |
val data = call.argument<String>("data") | |
val keyString = call.argument<String>("key") | |
val civ = CipherTextIvMac(data) | |
val decrypted = decryptString(civ, keys(keyString)) | |
result.success(decrypted) | |
} | |
"encrypt" -> { | |
val data = call.argument<String>("data") | |
val keyString = call.argument<String>("key") | |
val encrypted = encrypt(data, keys(keyString)) | |
result.success(encrypted.toString()) | |
} | |
"generate_random_key" -> { | |
val key = generateKey() | |
val keyString = keyString(key) | |
result.success(keyString) | |
} | |
else -> result.notImplemented() | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Flutter | |
import UIKit | |
import SCrypto | |
public class SwiftSimpleEncryptionPlugin: NSObject, FlutterPlugin { | |
public static func register(with registrar: FlutterPluginRegistrar) { | |
let channel = FlutterMethodChannel(name: "simple_encryption", binaryMessenger: registrar.messenger()) | |
let instance = SwiftSimpleEncryptionPlugin() | |
registrar.addMethodCallDelegate(instance, channel: channel) | |
} | |
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { | |
switch call.method { | |
case "decrypt": | |
guard let args = call.arguments as? [String: String] else { | |
fatalError("args are formatted badly") | |
} | |
let data = args["data"]! | |
let keyString = args["key"]! | |
let civ = CipherIvMac(base64IvAndCiphertext: data) | |
let keys = AESHMACKeys(base64AESAndHMAC: keyString) | |
do { | |
let decrypted = try keys.decryptToString(data: civ) | |
result(decrypted) | |
} catch (CryptoError.macMismatch) { | |
result(FlutterError(code: "mac_mismatch", message: "mac mismatch", details: nil)) | |
} catch { | |
fatalError("\(error)") | |
} | |
case "encrypt": | |
guard let args = call.arguments as? [String: String] else { | |
fatalError("args are formatted badly") | |
} | |
let data = args["data"]! | |
let keyString = args["key"]! | |
let keys = AESHMACKeys(base64AESAndHMAC: keyString) | |
let encrypted = keys.encrypt(string: data) | |
result(encrypted.base64EncodedString) | |
case "generate_random_key": | |
let key = AESHMACKeys.random() | |
let keyString = key.base64EncodedString | |
result(keyString) | |
default: result(FlutterMethodNotImplemented) | |
} | |
} | |
} | |
struct CipherIvMac { | |
let iv: Data | |
let mac: Data | |
let cipher: Data | |
var base64EncodedString: String { | |
let ivString = self.iv.base64EncodedString() | |
let cipherString = self.cipher.base64EncodedString() | |
let macString = self.mac.base64EncodedString() | |
return "\(ivString):\(macString):\(cipherString)" | |
} | |
init(iv: Data, mac: Data, cipher: Data) { | |
self.iv = iv | |
self.mac = mac | |
self.cipher = cipher | |
} | |
init(base64IvAndCiphertext: String) { | |
let civArray = base64IvAndCiphertext.split(separator: ":") | |
guard civArray.count == 3 else { | |
fatalError("Cannot parse iv:ciphertext:mac") | |
} | |
self.iv = Data(base64Encoded: String(civArray[0]))! | |
self.mac = Data(base64Encoded: String(civArray[1]))! | |
self.cipher = Data(base64Encoded: String(civArray[2]))! | |
} | |
static func ivCipherConcat(iv: Data, cipher: Data) -> Data { | |
var copy = iv | |
copy.append(cipher) | |
return copy | |
} | |
var ivCipherConcat: Data { | |
return CipherIvMac.ivCipherConcat(iv: self.iv, cipher: self.cipher) | |
} | |
} | |
struct AESHMACKeys { | |
static let aesKeyLengthBits = 128 | |
static let ivLengthBytes = 16 | |
static let hmacKeyLengthBits = 256 | |
let aes: Data | |
let hmac: Data | |
init(base64AESAndHMAC: String) { | |
let array = base64AESAndHMAC.split(separator: ":") | |
self.aes = Data(base64Encoded: String(array[0]))! | |
self.hmac = Data(base64Encoded: String(array[1]))! | |
} | |
init(aes: Data, hmac: Data) { | |
self.aes = aes | |
self.hmac = hmac | |
} | |
static func random() -> AESHMACKeys { | |
let aes = try! Data.random(AESHMACKeys.aesKeyLengthBits / 8) | |
let hmac = try! Data.random(AESHMACKeys.hmacKeyLengthBits / 8) | |
return .init(aes: aes, hmac: hmac) | |
} | |
func encrypt(string: String) -> CipherIvMac { | |
let data = string.data(using: .utf8)! | |
return self.encrypt(data: data) | |
} | |
func encrypt(data: Data) -> CipherIvMac { | |
let iv = try! Data.random(AESHMACKeys.ivLengthBytes) | |
let cipher = try! data.encrypt(.aes, options: .PKCS7Padding, key: self.aes, iv: iv) | |
let concat = CipherIvMac.ivCipherConcat(iv: iv, cipher: cipher) | |
let integrity = concat.hmac(.sha256, key: self.hmac) | |
return CipherIvMac(iv: iv, mac: integrity, cipher: cipher) | |
} | |
func decrypt(data: CipherIvMac) throws -> Data { | |
let concat = data.ivCipherConcat | |
let hmac = concat.hmac(.sha256, key: self.hmac) | |
// TODO: undestand if this is a constant time equality check | |
if hmac != data.mac { | |
throw CryptoError.macMismatch | |
} | |
let decrypted = try data.cipher.decrypt(.aes, options: .PKCS7Padding, key: self.aes, iv: data.iv) | |
return decrypted | |
} | |
func decryptToString(data: CipherIvMac, encoding: String.Encoding = .utf8) throws -> String { | |
let data = try self.decrypt(data: data) | |
return String(data: data, encoding: encoding)! | |
} | |
var base64EncodedString: String { | |
let aesString = self.aes.base64EncodedString() | |
let hmacString = self.hmac.base64EncodedString() | |
return "\(aesString):\(hmacString)" | |
} | |
} | |
enum CryptoError: Error { | |
case macMismatch | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Struggled with passing arguments through invokeMethod from dart to swift. This gist was very helpful 👍