From 842c9ecf4fb4f6a328394df6225f59abd5e77171 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Tue, 14 Apr 2020 17:13:08 +0200 Subject: [PATCH] Add plugin sources --- android/.gitignore | 8 ++ android/build.gradle | 44 ++++++ android/gradle.properties | 4 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle | 1 + android/src/main/AndroidManifest.xml | 3 + .../native_crypto/NativeCryptoPlugin.kt | 135 ++++++++++++++++++ ios/.gitignore | 37 +++++ ios/Assets/.gitkeep | 0 ios/Classes/NativeCryptoPlugin.h | 4 + ios/Classes/NativeCryptoPlugin.m | 15 ++ ios/Classes/SwiftNativeCryptoPlugin.swift | 14 ++ ios/native_crypto.podspec | 23 +++ lib/native_crypto.dart | 39 +++++ 14 files changed, 332 insertions(+) create mode 100644 android/.gitignore create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/kotlin/fr/pointcheval/native_crypto/NativeCryptoPlugin.kt create mode 100644 ios/.gitignore create mode 100644 ios/Assets/.gitkeep create mode 100644 ios/Classes/NativeCryptoPlugin.h create mode 100644 ios/Classes/NativeCryptoPlugin.m create mode 100644 ios/Classes/SwiftNativeCryptoPlugin.swift create mode 100644 ios/native_crypto.podspec create mode 100644 lib/native_crypto.dart diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..3aa57e5 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,44 @@ +group 'fr.pointcheval.native_crypto' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..01a286e --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..430c95a --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'native_crypto' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fcd47dc --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/kotlin/fr/pointcheval/native_crypto/NativeCryptoPlugin.kt b/android/src/main/kotlin/fr/pointcheval/native_crypto/NativeCryptoPlugin.kt new file mode 100644 index 0000000..b05f8c0 --- /dev/null +++ b/android/src/main/kotlin/fr/pointcheval/native_crypto/NativeCryptoPlugin.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020 + * Author: Hugo Pointcheval + */ + +package fr.pointcheval.native_crypto + +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +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.PluginRegistry.Registrar +import java.security.MessageDigest +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + + +/** NativeCryptoPlugin */ +public class NativeCryptoPlugin : FlutterPlugin, MethodCallHandler { + // CRYPTO CONSTS + private val HASH_FUNC = "SHA-256" + private val SYM_CRYPTO_METHOD = "AES" + private val SYM_CRYPTO_PADDING = "AES/CBC/PKCS5PADDING" + private val SYM_CRYPTO_BITS = 256 + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "native.crypto.helper") + channel.setMethodCallHandler(NativeCryptoPlugin()); + } + + companion object { + @JvmStatic + fun registerWith(registrar: Registrar) { + val channel = MethodChannel(registrar.messenger(), "native.crypto.helper") + channel.setMethodCallHandler(NativeCryptoPlugin()) + } + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + if (call.method == "symKeygen") { + val aesKey = symKeygen() // Collection + + if (aesKey.isNotEmpty()) { + result.success(aesKey) + } else { + result.error("KeygenError", "Key generation failed.", null) + } + } else if (call.method == "symEncrypt") { + val payload = call.argument("payload") // ByteArray + val aesKey = call.argument("aesKey") // ByteArray + + val encryptedPayload = symEncrypt(payload!!, aesKey!!) // Collection + + if (encryptedPayload.isNotEmpty()) { + result.success(encryptedPayload) + } else { + result.error("EncryptionError", "Encryption failed.", null) + } + } else if (call.method == "symDecrypt") { + val payload = call.argument>("payload") // Collection + val aesKey = call.argument("aesKey") // ByteArray + + val decryptedPayload = symDecrypt(payload!!, aesKey!!) // ByteArray + + if (decryptedPayload != null && decryptedPayload.isNotEmpty()) { + result.success(decryptedPayload) + } else { + result.error("DecryptionError", "Decryption failed.", null) + } + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + } + + // CRYPTO NATIVE FUNCTIONS + + private fun digest(obj: ByteArray?): ByteArray { + val md = MessageDigest.getInstance(HASH_FUNC) + return md.digest(obj) + } + + private fun symKeygen(): ByteArray { + + val secureRandom = SecureRandom() + val keyGenerator = KeyGenerator.getInstance(SYM_CRYPTO_METHOD) + keyGenerator?.init(SYM_CRYPTO_BITS, secureRandom) + val skey = keyGenerator?.generateKey() + + return skey!!.encoded + } + + + private fun symEncrypt(payload: ByteArray, aesKey: ByteArray): Collection { + + val mac = digest(aesKey + payload) + val key: SecretKey = SecretKeySpec(aesKey, SYM_CRYPTO_METHOD) + + val cipher = Cipher.getInstance(SYM_CRYPTO_PADDING) + cipher.init(Cipher.ENCRYPT_MODE, key) + + val encryptedBytes = cipher.doFinal(mac + payload) + val iv = cipher.iv + + return listOf(encryptedBytes, iv); + } + + private fun symDecrypt(payload: Collection, aesKey: ByteArray): ByteArray? { + + val key: SecretKey = SecretKeySpec(aesKey, SYM_CRYPTO_METHOD) + val cipher = Cipher.getInstance(SYM_CRYPTO_PADDING); + val iv = payload.last(); + val ivSpec = IvParameterSpec(iv) + cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); + + val decryptedBytes = cipher.doFinal(payload.first()); + + val mac = decryptedBytes.copyOfRange(0, 32) + val decryptedContent = decryptedBytes.copyOfRange(32, decryptedBytes.size) + val verificationMac = digest(aesKey + decryptedContent) + + if (mac.contentEquals(verificationMac)) return decryptedContent + + return null; + } + +} diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..aa479fd --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ios/Classes/NativeCryptoPlugin.h b/ios/Classes/NativeCryptoPlugin.h new file mode 100644 index 0000000..0f584e1 --- /dev/null +++ b/ios/Classes/NativeCryptoPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface NativeCryptoPlugin : NSObject +@end diff --git a/ios/Classes/NativeCryptoPlugin.m b/ios/Classes/NativeCryptoPlugin.m new file mode 100644 index 0000000..c674cf7 --- /dev/null +++ b/ios/Classes/NativeCryptoPlugin.m @@ -0,0 +1,15 @@ +#import "NativeCryptoPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "native_crypto-Swift.h" +#endif + +@implementation NativeCryptoPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftNativeCryptoPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/ios/Classes/SwiftNativeCryptoPlugin.swift b/ios/Classes/SwiftNativeCryptoPlugin.swift new file mode 100644 index 0000000..d24db65 --- /dev/null +++ b/ios/Classes/SwiftNativeCryptoPlugin.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +public class SwiftNativeCryptoPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "native_crypto", binaryMessenger: registrar.messenger()) + let instance = SwiftNativeCryptoPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + result("iOS " + UIDevice.current.systemVersion) + } +} diff --git a/ios/native_crypto.podspec b/ios/native_crypto.podspec new file mode 100644 index 0000000..0ff9ea5 --- /dev/null +++ b/ios/native_crypto.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint native_crypto.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'native_crypto' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.swift_version = '5.0' +end diff --git a/lib/native_crypto.dart b/lib/native_crypto.dart new file mode 100644 index 0000000..d515a4a --- /dev/null +++ b/lib/native_crypto.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2020 +// Author: Hugo Pointcheval +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; + +class NativeCrypto { + static const MethodChannel _channel = + const MethodChannel('native.crypto.helper'); + + Future sumKeygen() async { + final Uint8List aesKey = await _channel.invokeMethod('symKeygen'); + + return aesKey; + } + + Future> symEncrypt( + Uint8List payloadbytes, Uint8List aesKey) async { + final List encyptedPayload = + await _channel.invokeListMethod('symEncrypt', { + 'payload': payloadbytes, + 'aesKey': aesKey, + }); + + return encyptedPayload; + } + + Future symDecrypt( + List payloadbytes, Uint8List aesKey) async { + final Uint8List decryptedPayload = + await _channel.invokeMethod('symDecrypt', { + 'payload': payloadbytes, + 'aesKey': aesKey, + }); + + return decryptedPayload; + } +}