Skip to content

Instantly share code, notes, and snippets.

@sroddy
Last active December 26, 2018 10:38
Show Gist options
  • Save sroddy/12e2ff63eb4ebe9f17f858edff438fc0 to your computer and use it in GitHub Desktop.
Save sroddy/12e2ff63eb4ebe9f17f858edff438fc0 to your computer and use it in GitHub Desktop.
Flutter Simple Cryptor
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");
}
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()
}
}
}
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
}
@i-Captain
Copy link

Struggled with passing arguments through invokeMethod from dart to swift. This gist was very helpful 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment