Compare commits

..

No commits in common. "9e0d921564b41f6a209e9beedace6c9f7d210595" and "9160338e7ac38e48ad5596a8eab2fb8d413c65f5" have entirely different histories.

202 changed files with 3387 additions and 3340 deletions

View File

@ -1,7 +1,107 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,dart,flutter,intellij+all,kotlin,linux,swift,windows # Created by https://www.gitignore.io/api/windows,visualstudiocode,android,androidstudio,cocoapods,dart,flutter,intellij+all,kotlin,macos,swift,xcode,xcodeinjection
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,dart,flutter,intellij+all,kotlin,linux,swift,windows # Edit at https://www.gitignore.io/?templates=windows,visualstudiocode,android,androidstudio,cocoapods,dart,flutter,intellij+all,kotlin,macos,swift,xcode,xcodeinjection
### Android ###
# Built application files
*.apk
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
### Android Patch ###
gen-external-apklibs
output.json
# Replacement of .externalNativeBuild directories introduced
# with Android Studio 3.5.
.cxx/
### CocoaPods ###
## CocoaPods GitIgnore Template
# CocoaPods - Only use to conserve bandwidth / Save time on Pushing
# - Also handy if you have a large number of dependant pods
# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE
Pods/
### Dart ### ### Dart ###
# See https://www.dartlang.org/guides/libraries/private-files # See https://www.dartlang.org/guides/libraries/private-files
@ -9,7 +109,6 @@
# Files and directories created by pub # Files and directories created by pub
.dart_tool/ .dart_tool/
.packages .packages
build/
# If you're building an application, you may want to check-in your pubspec.lock # If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock pubspec.lock
@ -17,9 +116,6 @@ pubspec.lock
# If you don't generate documentation locally you can remove this line. # If you don't generate documentation locally you can remove this line.
doc/api/ doc/api/
# dotenv environment variables file
.env*
# Avoid committing generated Javascript files: # Avoid committing generated Javascript files:
*.dart.js *.dart.js
*.info.json # Produced by the --dump-info flag. *.info.json # Produced by the --dump-info flag.
@ -29,25 +125,13 @@ doc/api/
*.js.deps *.js.deps
*.js.map *.js.map
.flutter-plugins
.flutter-plugins-dependencies
### Dart Patch ###
# dotenv environment variables file
.env
### Flutter ### ### Flutter ###
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
.fvm/ .flutter-plugins
.flutter-plugins-dependencies
.pub-cache/ .pub-cache/
.pub/ .pub/
coverage/
lib/generated_plugin_registrant.dart
# For library packages, dont commit the pubspec.lock file.
# Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies.
# See https://dart.dev/guides/libraries/private-files#pubspeclock
#pubspec.lock
# Android related # Android related
**/android/**/gradle-wrapper.jar **/android/**/gradle-wrapper.jar
@ -55,7 +139,6 @@ lib/generated_plugin_registrant.dart
**/android/captures/ **/android/captures/
**/android/gradlew **/android/gradlew
**/android/gradlew.bat **/android/gradlew.bat
**/android/key.properties
**/android/local.properties **/android/local.properties
**/android/**/GeneratedPluginRegistrant.java **/android/**/GeneratedPluginRegistrant.java
@ -76,15 +159,12 @@ lib/generated_plugin_registrant.dart
**/ios/**/profile **/ios/**/profile
**/ios/**/xcuserdata **/ios/**/xcuserdata
**/ios/.generated/ **/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework **/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig **/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx **/ios/Flutter/app.flx
**/ios/Flutter/app.zip **/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json **/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.* **/ios/Runner/GeneratedPluginRegistrant.*
@ -96,7 +176,7 @@ lib/generated_plugin_registrant.dart
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
### Intellij+all ### ### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff # User-specific stuff
@ -106,9 +186,6 @@ lib/generated_plugin_registrant.dart
.idea/**/dictionaries .idea/**/dictionaries
.idea/**/shelf .idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files # Generated files
.idea/**/contentModel.xml .idea/**/contentModel.xml
@ -129,9 +206,6 @@ lib/generated_plugin_registrant.dart
# When using Gradle or Maven with auto-import, you should exclude module files, # When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using # since they will be recreated, and may cause churn. Uncomment if using
# auto-import. # auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml # .idea/modules.xml
# .idea/*.iml # .idea/*.iml
# .idea/modules # .idea/modules
@ -148,7 +222,6 @@ cmake-build-*/
*.iws *.iws
# IntelliJ # IntelliJ
out/
# mpeltonen/sbt-idea plugin # mpeltonen/sbt-idea plugin
.idea_modules/ .idea_modules/
@ -179,7 +252,6 @@ fabric.properties
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml modules.xml
.idea/misc.xml .idea/misc.xml
*.ipr *.ipr
@ -189,10 +261,8 @@ modules.xml
### Kotlin ### ### Kotlin ###
# Compiled class file # Compiled class file
*.class
# Log file # Log file
*.log
# BlueJ files # BlueJ files
*.ctxt *.ctxt
@ -212,21 +282,6 @@ modules.xml
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ### ### macOS ###
# General # General
.DS_Store .DS_Store
@ -236,7 +291,6 @@ hs_err_pid*
# Icon must end with two \r # Icon must end with two \r
Icon Icon
# Thumbnails # Thumbnails
._* ._*
@ -261,16 +315,10 @@ Temporary Items
# #
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings ## Build generated
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
DerivedData/ DerivedData/
*.moved-aside
## Various settings
*.pbxuser *.pbxuser
!default.pbxuser !default.pbxuser
*.mode1v3 *.mode1v3
@ -279,11 +327,15 @@ DerivedData/
!default.mode2v3 !default.mode2v3
*.perspectivev3 *.perspectivev3
!default.perspectivev3 !default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific ## Obj-C/Swift specific
*.hmap *.hmap
## App packaging
*.ipa *.ipa
*.dSYM.zip *.dSYM.zip
*.dSYM *.dSYM
@ -297,12 +349,9 @@ playground.xcworkspace
# Packages/ # Packages/
# Package.pins # Package.pins
# Package.resolved # Package.resolved
# *.xcodeproj
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/ .build/
# Add this line if you want to avoid checking in Xcode SPM integration.
# .swiftpm/xcode
# CocoaPods # CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However # We recommend against adding the Pods directory to your .gitignore. However
@ -316,22 +365,19 @@ playground.xcworkspace
# Add this line if you want to avoid checking in source code from Carthage dependencies. # Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts # Carthage/Checkouts
Carthage/Build/ Carthage/Build
# Accio dependency management # Accio dependency management
Dependencies/ Dependencies/
.accio/ .accio/
# fastlane # fastlane
# It is recommended to not store the screenshots in the git repo. # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# Instead, use fastlane to re-generate the screenshots whenever they are needed. # screenshots whenever they are needed.
# For more information about the recommended setup visit: # For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control # https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection # Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject # After new code Injection tools there's a generated folder /iOSInjectionProject
@ -340,23 +386,16 @@ fastlane/test_output
iOSInjectionProject/ iOSInjectionProject/
### VisualStudioCode ### ### VisualStudioCode ###
.vscode
.vscode/* .vscode/*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
### VisualStudioCode Patch ### ### VisualStudioCode Patch ###
# Ignore all local history of files # Ignore all local history of files
.history .history
.ionide
# Support for Project snippet scope
!.vscode/*.code-snippets
### Windows ### ### Windows ###
# Windows thumbnail cache files # Windows thumbnail cache files
@ -384,7 +423,122 @@ $RECYCLE.BIN/
# Windows shortcuts # Windows shortcuts
*.lnk *.lnk
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,dart,flutter,intellij+all,kotlin,linux,swift,windows ### Xcode ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
## Xcode Patch
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
### Xcode Patch ###
**/xcshareddata/WorkspaceSettings.xcsettings
### XcodeInjection ###
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
### AndroidStudio ###
# Covers files to be ignored for android development using Android Studio.
# Built application files
# Files for the ART/Dalvik VM
# Java class files
# Generated files
# Gradle files
.gradle
# Signing files
.signing/
# Local configuration file (sdk path, etc)
# Proguard folder generated by Eclipse
# Log Files
# Android Studio
/*/build/
/*/local.properties
/*/out
/*/*/build
/*/*/production
*~
*.swp
# Android Patch
# External native build folder generated in Android Studio 2.2 and later
# NDK
obj/
# IntelliJ IDEA
/out/
# User-specific configurations
.idea/caches/
.idea/libraries/
.idea/shelf/
.idea/.name
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/encodings.xml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
.idea/jsLibraryMappings.xml
.idea/datasources.xml
.idea/dataSources.ids
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# OS-specific files
.DS_Store?
# Legacy Eclipse project files
.classpath
.project
.cproject
.settings/
# Mobile Tools for Java (J2ME)
# Package Files #
# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
## Plugin-specific files:
# mpeltonen/sbt-idea plugin
# JIRA plugin
# Mongo Explorer plugin
.idea/mongoSettings.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
### AndroidStudio Patch ###
!/gradle/wrapper/gradle-wrapper.jar
# End of https://www.gitignore.io/api/windows,visualstudiocode,android,androidstudio,cocoapods,dart,flutter,intellij+all,kotlin,macos,swift,xcode,xcodeinjection
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: cf4400006550b70f28e4b4af815151d1e74846c6 revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3
channel: stable channel: stable
project_type: plugin project_type: plugin

25
CHANGELOG.md Normal file
View File

@ -0,0 +1,25 @@
## 0.0.6
**WIP...**
## 0.0.5
* New API
* Add digest support
* Clean platform specific code base
## 0.0.4
* Improve AES
## 0.0.3
* Add PBKDF2 support
* Add exceptions
* Improve documentation
## 0.0.2
* Add different key size support
* Improve performances
## 0.0.1
* First AES cross-platform encryption & decryption implementation.

View File

@ -1,4 +1,4 @@
NativeCrypto - iOS Implementation native_crypto
MIT License MIT License

170
README.md Normal file
View File

@ -0,0 +1,170 @@
# NativeCrypto for Flutter
![NativeCrypto Logo](/assets/native_crypto.png)
---
Fast and powerful cryptographic functions thanks to **javax.crypto** and **CommonCrypto**.
## 📝 Table of Contents
- [About](#about)
- [Getting Started](#getting_started)
- [Example](#example)
- [Usage](#usage)
- [Built Using](#built_using)
- [TODOS](#todos)
- [Authors](#authors)
## 🧐 About <a name = "about"></a>
The goal of this plugin is to provide simple access to fast and powerful cryptographic functions by calling native libraries. So on **Android** the plugin uses *javax.crypto* and on **iOS** it uses *CommonCrypto*.
I started this project because using **Pointy Castle** I faced big performance issues on smartphone. It's quite simple, an encryption of 1MB of data in AES256 on an Android device takes **20s** with Pointy Castle against **27ms** using NativeCrypto.
![Pointy Castle Benchmark](/assets/benchmark_pointycastle.png)
> We also notice on this benchmark that the AES encryption time does not even increase linearly with size.
As for NativeCrypto, here is a benchmark realized on an Android device, Huawei P30 Pro.
| Size (kB) | NativeCrypto **encryption** time (ms) |
|-----------|---------------------------------------|
| 1 mB | 27 ms
| 2 mB | 43 ms
| 3 mB | 78 ms
| 4 mB | 93 ms
| 5 mB | 100 ms
| 10 mB | 229 ms
| 50 mB | 779 ms
> Less than 1s for 50 mB.
In short, **NativeCrypto** is incomparable to **Pointy Castle** in terms of performance.
## 🏁 Getting Started <a name = "getting_started"></a>
### Prerequisites
You'll need:
- Flutter
### Installing
Add these lines in your **pubspec.yaml**:
```yaml
native_crypto:
git:
url: https://gogs.pointcheval.fr/hugo/native-crypto-flutter.git
ref: v0.0.x
```
> Replace "x" with the current version!
Then in your code:
```dart
import 'package:native_crypto/native_crypto.dart';
```
## 🔍 Example <a name="example"></a>
Look in **example/lib/** for an example app.
## 🎈 Usage <a name="usage"></a>
To derive a key with **PBKDF2**.
```dart
PBKDF2 _pbkdf2 = PBKDF2(keyLength: 32, iteration: 1000, hash: HashAlgorithm.SHA512);
await _pbkdf2.derive(password: "password123", salt: 'salty');
SecretKey key = _pbkdf2.key;
```
To generate a key, and create an **AES Cipher** instance.
```dart
AESCipher aes = await AESCipher.generate(
AESKeySize.bits256,
CipherParameters(
BlockCipherMode.CBC,
PlainTextPadding.PKCS5,
),
);
```
You can also generate key, then create **AES Cipher**.
```dart
SecretKey _key = await SecretKey.generate(256, CipherAlgorithm.AES);
AESCipher aes = AESCipher(
_key,
CipherParameters(
BlockCipherMode.CBC,
PlainTextPadding.PKCS5,
),
);
```
Then you can encrypt/decrypt data with this cipher.
```dart
CipherText cipherText = await aes.encrypt(data);
Uint8List plainText = await aes.decrypt(cipherText);
```
You can easely get encrypted bytes and IV from a CipherText
```dart
Uint8List bytes = cipherText.bytes;
Uint8List iv = cipherText.iv;
```
To create a cipher text with custom data.
```dart
CipherText cipherText = AESCipherText(bytes, iv);
```
To create a hashed message
```dart
MessageDigest md = MessageDigest.getInstance("sha256");
Uint8List hash = await md.digest(message);
```
## ⛏️ Built Using <a name = "built_using"></a>
- [Dart](https://dart.dev)
- [Flutter](https://flutter.dev) - Framework
- [Kotlin](https://kotlinlang.org) - Android Specific code
- [Swift](https://www.apple.com/fr/swift/) - iOS Specific code
## 🚀 TODOS <a name = "todos">
Here you can check major changes, roadmap and todos.
Once the **BlowFish algorithm** is exposed on Android and iOS, I plan to deal with asymmetric cryptography with the implementation of a Key Encapsulation Mechanism.
- [x] Add PBKDF2 support.
- [x] Implement working cross platform AES encryption/decryption.
- [x] Add Different key sizes support.
- [x] Add exceptions.
- [x] Clean platform specific code.
- [x] Add digest.
- [x] Rework exposed API.
- [ ] Implement BlowFish.
- [ ] Add KeyPair generation.
- [ ] Add KEM.
- [ ] Porting NativeCrypto to other platforms...
You can contribute to this project.
## ✍️ Authors <a name = "authors"></a>
- [Hugo Pointcheval](https://github.com/hugo-pcl) - Idea & Initial work

8
android/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures

44
android/build.gradle Normal file
View File

@ -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 29
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"
}

View File

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

View File

@ -1,6 +1,5 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

1
android/settings.gradle Normal file
View File

@ -0,0 +1 @@
rootProject.name = 'native_crypto'

View File

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.pointcheval.native_crypto">
</manifest>

View File

@ -0,0 +1,93 @@
package fr.pointcheval.native_crypto
import java.lang.Exception
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
enum class CipherAlgorithm(val spec: String) {
AES("AES"),
}
enum class BlockCipherMode(val instance: String) {
CBC("CBC"),
GCM("GCM"),
}
enum class Padding(val instance: String) {
PKCS5("PKCS5Padding"),
None("NoPadding")
}
class CipherParameters(private val mode: BlockCipherMode, private val padding: Padding) {
override fun toString(): String {
return mode.instance + "/" + padding.instance
}
}
class Cipher {
fun getCipherAlgorithm(dartAlgorithm: String) : CipherAlgorithm {
return when (dartAlgorithm) {
"aes" -> CipherAlgorithm.AES
else -> CipherAlgorithm.AES
}
}
fun getInstance(mode : String, padding : String) : CipherParameters {
val m = when (mode) {
"cbc" -> BlockCipherMode.CBC
"gcm" -> BlockCipherMode.GCM
else -> throw Exception()
}
val p = when (padding) {
"pkcs5" -> Padding.PKCS5
else -> Padding.None
}
return CipherParameters(m,p)
}
fun encrypt(data: ByteArray, key: ByteArray, algorithm: String, mode: String, padding: String) : List<ByteArray> {
val algo = getCipherAlgorithm(algorithm)
val params = getInstance(mode, padding)
val keySpecification = algo.spec + "/" + params.toString()
val mac = Hash().digest(key + data)
val sk: SecretKey = SecretKeySpec(key, algo.spec)
val cipher = Cipher.getInstance(keySpecification)
cipher.init(Cipher.ENCRYPT_MODE, sk)
val encryptedBytes = cipher.doFinal(mac + data)
val iv = cipher.iv
return listOf(encryptedBytes, iv);
}
fun decrypt(payload: Collection<ByteArray>, key: ByteArray, algorithm: String, mode: String, padding: String) : ByteArray? {
val algo = getCipherAlgorithm(algorithm)
val params = getInstance(mode, padding)
val keySpecification = algo.spec + "/" + params.toString()
val sk: SecretKey = SecretKeySpec(key, algo.spec)
val cipher = Cipher.getInstance(keySpecification);
val iv = payload.last();
val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, sk, ivSpec);
val decryptedBytes = cipher.doFinal(payload.first());
val mac = decryptedBytes.copyOfRange(0, 32)
val decryptedContent : ByteArray = decryptedBytes.copyOfRange(32, decryptedBytes.size)
val verificationMac = Hash().digest(key + decryptedContent)
return if (mac.contentEquals(verificationMac)) {
decryptedContent
} else {
null;
}
}
}

View File

@ -0,0 +1,42 @@
package fr.pointcheval.native_crypto
import java.security.MessageDigest
enum class HashAlgorithm(val length : Int) {
SHA1(160),
SHA224(224),
SHA256(256),
SHA384(384),
SHA512(512);
}
class Hash() {
fun digest(data: ByteArray?, algorithm: HashAlgorithm): ByteArray {
val func : String = when (algorithm) {
HashAlgorithm.SHA1 -> "SHA-1"
HashAlgorithm.SHA224 -> "SHA-224"
HashAlgorithm.SHA256 -> "SHA-256"
HashAlgorithm.SHA384 -> "SHA-384"
HashAlgorithm.SHA512 -> "SHA-512"
}
val md = MessageDigest.getInstance(func)
return md.digest(data)
}
fun digest(data: ByteArray?, algorithm: String): ByteArray {
val func : HashAlgorithm = when (algorithm) {
"sha1" -> HashAlgorithm.SHA1
"sha224" -> HashAlgorithm.SHA224
"sha256" -> HashAlgorithm.SHA256
"sha384" -> HashAlgorithm.SHA384
"sha512" -> HashAlgorithm.SHA512
else -> HashAlgorithm.SHA256
}
return digest(data, func)
}
fun digest(data: ByteArray?): ByteArray {
return digest(data, HashAlgorithm.SHA256)
}
}

View File

@ -0,0 +1,35 @@
package fr.pointcheval.native_crypto
import android.os.Build
import java.lang.IllegalArgumentException
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
class KeyDerivation {
fun pbkdf2(password: String, salt: String, keyLength: Int, iteration: Int, algorithm: String): ByteArray {
val chars: CharArray = password.toCharArray()
val availableHashAlgorithm: Map<String, String> = mapOf(
"sha1" to "PBKDF2withHmacSHA1",
"sha224" to "PBKDF2withHmacSHA224",
"sha256" to "PBKDF2WithHmacSHA256",
"sha384" to "PBKDF2withHmacSHA384",
"sha512" to "PBKDF2withHmacSHA512"
)
if (Build.VERSION.SDK_INT >= 26) {
// SHA-1 and SHA-2 implemented
val spec = PBEKeySpec(chars, salt.toByteArray(), iteration, keyLength * 8)
val skf: SecretKeyFactory = SecretKeyFactory.getInstance(availableHashAlgorithm[algorithm]);
return skf.generateSecret(spec).encoded
} else if (Build.VERSION.SDK_INT >= 10) {
// Only SHA-1 is implemented
if (!algorithm.equals("sha1")) {
throw PlatformVersionException("Only SHA1 is implemented on this SDK version!")
}
val spec = PBEKeySpec(chars, salt.toByteArray(), iteration, keyLength * 8)
val skf: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
return skf.generateSecret(spec).encoded
}
throw PlatformVersionException("Invalid SDK version!")
}
}

View File

@ -0,0 +1,32 @@
package fr.pointcheval.native_crypto
import java.security.KeyPairGenerator
import java.security.SecureRandom
import javax.crypto.KeyGenerator
class KeyGeneration {
fun keygen(size : Int) : ByteArray {
val secureRandom = SecureRandom()
val keyGenerator = if (size in listOf<Int>(128,192,256)) {
KeyGenerator.getInstance("AES")
} else {
KeyGenerator.getInstance("BLOWFISH")
}
keyGenerator.init(size, secureRandom)
val sk = keyGenerator.generateKey()
return sk!!.encoded
}
fun rsaKeypairGen(size : Int) : List<ByteArray> {
val secureRandom = SecureRandom()
val keyGenerator = KeyPairGenerator.getInstance("RSA")
keyGenerator.initialize(size, secureRandom)
val keypair = keyGenerator.genKeyPair()
val res : List<ByteArray> = listOf(keypair.public.encoded, keypair.private.encoded)
return res
}
}

View File

@ -0,0 +1,142 @@
/*
* 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
/** NativeCryptoPlugin */
class NativeCryptoPlugin : FlutterPlugin, MethodCallHandler {
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "native.crypto")
channel.setMethodCallHandler(NativeCryptoPlugin());
}
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "native.crypto")
channel.setMethodCallHandler(NativeCryptoPlugin())
}
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"digest" -> {
val message = call.argument<ByteArray>("message")
val algorithm = call.argument<String>("algorithm")
try {
val d = Hash().digest(message, algorithm!!)
if (d.isNotEmpty()) {
result.success(d)
} else {
result.error("DIGESTERROR", "DIGEST IS NULL.", null)
}
} catch (e : Exception) {
result.error("DIGESTEXCEPTION", e.message, null)
}
}
"pbkdf2" -> {
val password = call.argument<String>("password")
val salt = call.argument<String>("salt")
val keyLength = call.argument<Int>("keyLength")
val iteration = call.argument<Int>("iteration")
val algorithm = call.argument<String>("algorithm")
try {
val key = KeyDerivation().pbkdf2(password!!, salt!!, keyLength!!, iteration!!, algorithm!!)
if (key.isNotEmpty()) {
result.success(key)
} else {
result.error("PBKDF2ERROR", "PBKDF2 KEY IS NULL.", null)
}
} catch (e : Exception) {
result.error("PBKDF2EXCEPTION", e.message, null)
}
}
"keygen" -> {
val size = call.argument<Int>("size") // 128, 192, 256
try {
val key = KeyGeneration().keygen(size!!)
if (key.isNotEmpty()) {
result.success(key)
} else {
result.error("KEYGENERROR", "GENERATED KEY IS NULL.", null)
}
} catch (e : Exception) {
result.error("KEYGENEXCEPTION", e.message, null)
}
}
"rsaKeypairGen" -> {
val size = call.argument<Int>("size")
try {
val keypair = KeyGeneration().rsaKeypairGen(size!!)
if (keypair.isNotEmpty()) {
result.success(keypair)
} else {
result.error("KEYPAIRGENERROR", "GENERATED KEYPAIR IS EMPTY.", null)
}
} catch (e : Exception) {
result.error("KEYPAIRGENEXCEPTION", e.message, null)
}
}
"encrypt" -> {
val data = call.argument<ByteArray>("data")
val key = call.argument<ByteArray>("key")
val algorithm = call.argument<String>("algorithm")
val mode = call.argument<String>("mode")
val padding = call.argument<String>("padding")
try {
val payload = Cipher().encrypt(data!!, key!!, algorithm!!, mode!!, padding!!)
if (payload.isNotEmpty()) {
result.success(payload)
} else {
result.error("ENCRYPTIONERROR", "ENCRYPTED PAYLOAD IS EMPTY.", null)
}
} catch (e: Exception) {
result.error("ENCRYPTIONEXCEPTION", e.message, null)
}
}
"decrypt" -> {
val payload = call.argument<Collection<ByteArray>>("payload") // Collection<ByteArray>
val key = call.argument<ByteArray>("key")
val algorithm = call.argument<String>("algorithm")
val mode = call.argument<String>("mode")
val padding = call.argument<String>("padding")
var decryptedPayload : ByteArray? = null
try {
decryptedPayload = Cipher().decrypt(payload!!, key!!, algorithm!!, mode!!, padding!!)
if (decryptedPayload != null && decryptedPayload.isNotEmpty()) {
result.success(decryptedPayload)
} else {
result.error("DECRYPTIONERROR", "DECRYPTED PAYLOAD IS NULL. MAYBE VERIFICATION MAC IS UNVALID.", null)
}
} catch (e : Exception) {
result.error("DECRYPTIONEXCEPTION", e.message, null)
}
}
else -> result.notImplemented()
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
}
}

View File

@ -0,0 +1,5 @@
package fr.pointcheval.native_crypto
import java.lang.Exception
class PlatformVersionException(message : String) : Exception(message)

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
assets/native_crypto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -22,7 +22,6 @@
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/ .dart_tool/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
@ -34,13 +33,5 @@
# Web related # Web related
lib/generated_plugin_registrant.dart lib/generated_plugin_registrant.dart
# Symbolication related # Exceptions to above rules.
app.*.symbols !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: cf4400006550b70f28e4b4af815151d1e74846c6 revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3
channel: stable channel: stable
project_type: app project_type: app

7
example/android/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java

View File

@ -26,28 +26,24 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion flutter.compileSdkVersion compileSdkVersion 28
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
lintOptions {
disable 'InvalidPackage'
}
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "fr.pointcheval.native_crypto_example" applicationId "fr.pointcheval.native_crypto_example"
minSdkVersion flutter.minSdkVersion minSdkVersion 16
targetSdkVersion flutter.targetSdkVersion targetSdkVersion 28
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@ -65,4 +61,7 @@ flutter {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
} }

View File

@ -1,25 +1,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.pointcheval.native_crypto_example"> package="fr.pointcheval.native_crypto_example">
<application <!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="native_crypto_example" android:label="native_crypto_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>

View File

@ -0,0 +1,12 @@
package fr.pointcheval.native_crypto_example
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

View File

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View File

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@ -2,11 +2,11 @@ buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.3.50'
repositories { repositories {
google() google()
mavenCentral() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
@ -14,7 +14,7 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
mavenCentral() jcenter()
} }
} }

View File

@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,6 @@
#Fri Dec 18 22:37:36 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

View File

@ -0,0 +1,15 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@ -1,4 +1,3 @@
**/dgph
*.mode1v3 *.mode1v3
*.mode2v3 *.mode2v3
*.moved-aside *.moved-aside
@ -19,7 +18,6 @@ Flutter/App.framework
Flutter/Flutter.framework Flutter/Flutter.framework
Flutter/Flutter.podspec Flutter/Flutter.podspec
Flutter/Generated.xcconfig Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx Flutter/app.flx
Flutter/app.zip Flutter/app.zip
Flutter/flutter_assets/ Flutter/flutter_assets/

View File

@ -0,0 +1 @@
2f8ea5763cdbee83dd665ad298f0e380

View File

@ -3,7 +3,7 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>App</string> <string>App</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>9.0</string> <string>8.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

90
example/ios/Podfile Normal file
View File

@ -0,0 +1,90 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end
target 'Runner' do
use_frameworks!
use_modular_headers!
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
end
# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
install! 'cocoapods', :disable_input_output_paths => true
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

22
example/ios/Podfile.lock Normal file
View File

@ -0,0 +1,22 @@
PODS:
- Flutter (1.0.0)
- native_crypto (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- native_crypto (from `.symlinks/plugins/native_crypto/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
native_crypto:
:path: ".symlinks/plugins/native_crypto/ios"
SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
native_crypto: 33b8108e3fcc10052862b69863efc2304c59cb2f
PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83
COCOAPODS: 1.9.3

View File

@ -3,17 +3,17 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 50; objectVersion = 46;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
20AC864B3037BB896BD381B1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 056766980834B5FAC1C4D331 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D294241BEFD2D3EF500C0577 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6CB4DB73769DFCBF23FCC12 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -30,11 +30,9 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
056766980834B5FAC1C4D331 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1180301722C337B6C625E46F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2A0F0FD6D80A80663D317892 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; 2DBF05A146610778D425B9D9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -46,7 +44,9 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
EC40AF9C4AC2A38A0B8CC0E6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; C6CB4DB73769DFCBF23FCC12 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D3EC88B33A6F35C67BAC8349 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
FB0023B75BB4BD74C9F8D280 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -54,22 +54,19 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
20AC864B3037BB896BD381B1 /* Pods_Runner.framework in Frameworks */, D294241BEFD2D3EF500C0577 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
3024D600AF0E0DED3BB44E03 /* Pods */ = { 8C27A9C4EB5E71366C317BF8 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EC40AF9C4AC2A38A0B8CC0E6 /* Pods-Runner.debug.xcconfig */, C6CB4DB73769DFCBF23FCC12 /* Pods_Runner.framework */,
2A0F0FD6D80A80663D317892 /* Pods-Runner.release.xcconfig */,
1180301722C337B6C625E46F /* Pods-Runner.profile.xcconfig */,
); );
name = Pods; name = Frameworks;
path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
@ -89,8 +86,8 @@
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
3024D600AF0E0DED3BB44E03 /* Pods */, 9F5A6E791DB57C1B2E0BF33E /* Pods */,
F589250F1A900F4BC6E4BB56 /* Frameworks */, 8C27A9C4EB5E71366C317BF8 /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -109,6 +106,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */, 97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@ -117,12 +115,22 @@
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
F589250F1A900F4BC6E4BB56 /* Frameworks */ = { 97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
056766980834B5FAC1C4D331 /* Pods_Runner.framework */,
); );
name = Frameworks; name = "Supporting Files";
sourceTree = "<group>";
};
9F5A6E791DB57C1B2E0BF33E /* Pods */ = {
isa = PBXGroup;
children = (
FB0023B75BB4BD74C9F8D280 /* Pods-Runner.debug.xcconfig */,
2DBF05A146610778D425B9D9 /* Pods-Runner.release.xcconfig */,
D3EC88B33A6F35C67BAC8349 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
/* End PBXGroup section */ /* End PBXGroup section */
@ -132,14 +140,14 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
CE2C3E978245C5FF80E431AC /* [CP] Check Pods Manifest.lock */, D72ACB84F79B4AD23991D136 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
ED05A5A0A8AF0FA8F71C2C2B /* [CP] Embed Pods Frameworks */, FF811A27CCC4B4D5EBD756CE /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -156,8 +164,8 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1300; LastUpgradeCheck = 1020;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1; CreatedOnToolsVersion = 7.3.1;
@ -166,7 +174,7 @@
}; };
}; };
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3"; compatibilityVersion = "Xcode 3.2";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
@ -226,7 +234,7 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
}; };
CE2C3E978245C5FF80E431AC /* [CP] Check Pods Manifest.lock */ = { D72ACB84F79B4AD23991D136 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@ -248,17 +256,15 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
ED05A5A0A8AF0FA8F71C2C2B /* [CP] Embed Pods Frameworks */ = { FF811A27CCC4B4D5EBD756CE /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
@ -340,7 +346,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -356,14 +362,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6Z5P8GG96U;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; FRAMEWORK_SEARCH_PATHS = (
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "$(PROJECT_DIR)/Flutter",
); );
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoIosExample; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoExample;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -418,7 +428,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -467,12 +477,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
@ -485,14 +494,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6Z5P8GG96U;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; FRAMEWORK_SEARCH_PATHS = (
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "$(PROJECT_DIR)/Flutter",
); );
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoIosExample; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoExample;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -508,14 +521,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6Z5P8GG96U;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; FRAMEWORK_SEARCH_PATHS = (
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "$(PROJECT_DIR)/Flutter",
); );
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoIosExample; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoExample;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1300" LastUpgradeVersion = "1020"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -27,6 +27,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@ -36,8 +38,8 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<Testables> <AdditionalOptions>
</Testables> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -59,6 +61,8 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"

View File

@ -4,8 +4,6 @@
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Native Crypto</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -42,6 +40,6 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<true/> <false/>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

69
example/lib/main.dart Normal file
View File

@ -0,0 +1,69 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'package:flutter/material.dart';
import 'package:native_crypto_example/pages/kemPage.dart';
import 'pages/benchmarkPage.dart';
import 'pages/cipherPage.dart';
import 'pages/hashKeyDerivationPage.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _currentIndex = 0;
final List<Widget> _children = [
HashKeyDerivationPage(),
CipherPage(),
KemPage(),
BenchmarkPage(),
];
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Native Crypto'),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.black,
showUnselectedLabels: true,
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: [
BottomNavigationBarItem(
icon: Icon(Icons.vpn_key),
label: 'Key',
),
BottomNavigationBarItem(
icon: Icon(Icons.lock),
label: 'Encryption',
),
BottomNavigationBarItem(
icon: Icon(Icons.connect_without_contact),
label: 'KEM',
),
BottomNavigationBarItem(
icon: Icon(Icons.timer),
label: 'Benchmark',
),
],
),
),
);
}
}

View File

@ -0,0 +1,144 @@
// Copyright (c) 2021
// Author: Hugo Pointcheval
import 'dart:developer';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:native_crypto/native_crypto.dart';
import '../session.dart';
import '../widgets/button.dart';
import '../widgets/output.dart';
class BenchmarkPage extends StatefulWidget {
const BenchmarkPage({key}) : super(key: key);
@override
_BenchmarkPageState createState() => _BenchmarkPageState();
}
class _BenchmarkPageState extends State<BenchmarkPage> {
final Output keyContent = Output(
textEditingController: TextEditingController(),
);
final Output benchmarkStatus = Output(
textEditingController: TextEditingController(),
large: true,
);
Future<void> _benchmark() async {
if (Session.secretKey == null || Session.secretKey.isEmpty) {
benchmarkStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
return;
} else if (!Session.aesCipher.isInitialized) {
benchmarkStatus.print(
'Cipher not initialized!\nGo in Key tab and generate or derive one.');
return;
}
benchmarkStatus.print("Benchmark 2/4/8/16/32/64/128/256MB\n");
List<int> testedSizes = [2, 4, 8, 16, 32, 64, 128, 256];
String csv =
"size;encryption time;encode time;decryption time;crypto time\n";
var beforeBench = DateTime.now();
for (int size in testedSizes) {
var bigFile = Uint8List(size * 1000000);
csv += "${size * 1000000};";
var cryptoTime = 0;
// Encryption
var before = DateTime.now();
var encryptedBigFile = await Session.aesCipher.encrypt(bigFile);
var after = DateTime.now();
var benchmark =
after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n');
csv += "$benchmark;";
cryptoTime += benchmark;
// Encoding
before = DateTime.now();
encryptedBigFile.encode();
after = DateTime.now();
benchmark = after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.append('[$size MB] Encoding took $benchmark ms\n');
csv += "$benchmark;";
// Decryption
before = DateTime.now();
await Session.aesCipher.decrypt(encryptedBigFile);
after = DateTime.now();
benchmark = after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.append('[$size MB] Decryption took $benchmark ms\n');
csv += "$benchmark;";
cryptoTime += benchmark;
csv += "$cryptoTime\n";
}
var afterBench = DateTime.now();
var benchmark =
afterBench.millisecondsSinceEpoch - beforeBench.millisecondsSinceEpoch;
var sum = testedSizes.reduce((a, b) => a + b);
benchmarkStatus.append(
'Benchmark finished.\nGenerated, encrypted and decrypted $sum MB in $benchmark ms');
log(csv, name: "Benchmark");
}
void _clear() {
benchmarkStatus.clear();
}
@override
void initState() {
super.initState();
if (Session.secretKey != null) {
keyContent.print(Session.secretKey.encoded.toString());
Session.aesCipher = AESCipher(
Session.secretKey,
CipherParameters(
BlockCipherMode.CBC,
PlainTextPadding.PKCS5,
),
);
}
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
onPressed: _benchmark,
label: "Launch benchmark",
),
Button(
onPressed: _clear,
label: "Clear",
),
],
),
benchmarkStatus,
],
),
),
);
}
}

View File

@ -0,0 +1,205 @@
// Copyright (c) 2021
// Author: Hugo Pointcheval
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:native_crypto/native_crypto.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/button.dart';
import '../widgets/output.dart';
class CipherPage extends StatefulWidget {
const CipherPage({key}) : super(key: key);
@override
_CipherPageState createState() => _CipherPageState();
}
class _CipherPageState extends State<CipherPage> {
final Output keyContent = Output(
textEditingController: TextEditingController(),
);
final Output encryptionStatus = Output(
textEditingController: TextEditingController(),
);
final Output decryptionStatus = Output(
textEditingController: TextEditingController(),
);
final Output cipherExport = Output(
textEditingController: TextEditingController(),
large: true,
editable: true,
);
final TextEditingController _plainTextController = TextEditingController();
CipherText cipherText;
void _encrypt() async {
final plainText = _plainTextController.text.trim();
if (Session.secretKey == null || Session.secretKey.isEmpty) {
encryptionStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
} else if (!Session.aesCipher.isInitialized) {
encryptionStatus.print(
'Cipher not initialized!\nGo in Key tab and generate or derive one.');
} else if (plainText.isEmpty) {
encryptionStatus.print('Entry is empty');
} else {
var stringToBytes = TypeHelper.stringToBytes(plainText);
cipherText = await Session.aesCipher.encrypt(stringToBytes);
encryptionStatus.print('String successfully encrypted.\n');
encryptionStatus.append("IV: " +
cipherText.iv.toString() +
"\nCipherText: " +
cipherText.bytes.toString());
}
}
void _alter() async {
if (cipherText == null || cipherText.bytes.isEmpty) {
decryptionStatus.print('Encrypt before altering CipherText!');
} else {
// Add 1 to the first byte
List<Uint8List> _altered = cipherText.bytes;
_altered[0][0] += 1;
// Recreate cipher text with altered data
cipherText = AESCipherText.from(_altered, cipherText.iv);
encryptionStatus.print('String successfully encrypted.\n');
encryptionStatus.append("IV: " +
cipherText.iv.toString() +
"\nCipherText: " +
cipherText.bytes.toString());
decryptionStatus.print('CipherText altered!\nDecryption will fail.');
}
}
void _decrypt() async {
if (Session.secretKey == null || Session.secretKey.isEmpty) {
decryptionStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
} else if (!Session.aesCipher.isInitialized) {
decryptionStatus.print(
'Cipher not initialized!\nGo in Key tab and generate or derive one.');
} else if (cipherText == null || cipherText.bytes.isEmpty) {
decryptionStatus.print('Encrypt before decrypting!');
} else {
try {
Uint8List plainText = await Session.aesCipher.decrypt(cipherText);
var bytesToString = TypeHelper.bytesToString(plainText);
decryptionStatus
.print('String successfully decrypted:\n\n$bytesToString');
} on DecryptionException catch (e) {
decryptionStatus.print(e.message);
}
}
}
void _export() async {
if (cipherText == null) {
decryptionStatus.print('Encrypt data before export!');
} else {
// TODO: fix export format to support chunks !
Uint8List payload = cipherText.encode();
String data = TypeHelper.bytesToBase64(payload);
decryptionStatus.print('CipherText successfully exported');
cipherExport.print(data);
}
}
void _import() async {
final String data = cipherExport.read();
if (data.isEmpty) {
encryptionStatus.print('CipherText import failed');
} else {
Uint8List payload = TypeHelper.base64ToBytes(data);
cipherText = AESCipherText.empty();
cipherText.decode(payload);
encryptionStatus.print('CipherText successfully imported\n');
encryptionStatus.append("IV: " +
cipherText.iv.toString() +
"\nCipherText: " +
cipherText.bytes.toString());
}
}
@override
void initState() {
super.initState();
if (Session.secretKey != null) {
keyContent.print(Session.secretKey.encoded.toString());
Session.aesCipher = AESCipher(
Session.secretKey,
CipherParameters(
BlockCipherMode.CBC,
PlainTextPadding.PKCS5,
),
);
}
}
@override
void dispose() {
super.dispose();
_plainTextController.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
TextField(
controller: _plainTextController,
decoration: InputDecoration(
hintText: 'Plain text',
),
),
Button(
onPressed: _encrypt,
label: "Encrypt",
),
encryptionStatus,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
onPressed: _alter,
label: "Alter cipher",
),
Button(
onPressed: _decrypt,
label: "Decrypt",
),
],
),
decryptionStatus,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
onPressed: _export,
label: "Export cipher",
),
Button(
onPressed: _import,
label: "Import cipher",
),
],
),
cipherExport
],
),
),
);
}
}

View File

@ -0,0 +1,180 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:native_crypto/native_crypto.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/button.dart';
import '../widgets/output.dart';
class HashKeyDerivationPage extends StatefulWidget {
const HashKeyDerivationPage({key}) : super(key: key);
@override
_HashKeyDerivationPageState createState() => _HashKeyDerivationPageState();
}
class _HashKeyDerivationPageState extends State<HashKeyDerivationPage> {
final Output keyContent = Output(
textEditingController: TextEditingController(),
);
final Output keyStatus = Output(
textEditingController: TextEditingController(),
);
final Output keyExport = Output(
textEditingController: TextEditingController(),
large: true,
editable: true,
);
final Output pbkdf2Status = Output(
textEditingController: TextEditingController(),
);
final Output hashStatus = Output(
textEditingController: TextEditingController(),
);
final TextEditingController _pwdTextController = TextEditingController();
final TextEditingController _messageTextController = TextEditingController();
final TextEditingController _keyTextController = TextEditingController();
void _generate() async {
try {
Session.secretKey = await SecretKey.generate(256, CipherAlgorithm.AES);
keyContent.print(Session.secretKey.encoded.toString());
keyStatus.print(
"Secret Key successfully generated.\nLength: ${Session.secretKey.encoded.length} bytes");
} catch (e) {
keyStatus.print(e.message);
}
}
void _pbkdf2() async {
final password = _pwdTextController.text.trim();
if (password.isEmpty) {
pbkdf2Status.print('Password is empty');
} else {
PBKDF2 _pbkdf2 =
PBKDF2(keyLength: 32, iteration: 1000, hash: HashAlgorithm.SHA512);
await _pbkdf2.derive(password: password, salt: 'salty');
SecretKey key = _pbkdf2.key;
pbkdf2Status.print('Key successfully derived.');
Session.secretKey = key;
keyContent.print(Session.secretKey.encoded.toString());
}
}
void _export() async {
if (Session.secretKey == null || Session.secretKey.isEmpty) {
keyStatus
.print('No SecretKey!\nGenerate or derive one before exporting!');
} else {
String key = TypeHelper.bytesToBase64(Session.secretKey.encoded);
keyStatus.print('Key successfully exported');
keyExport.print(key);
}
}
void _import() async {
final String key = keyExport.read();
if (key.isEmpty) {
keyStatus.print('Key import failed');
} else {
Uint8List keyBytes = TypeHelper.base64ToBytes(key);
Session.secretKey =
SecretKey.fromBytes(keyBytes, algorithm: CipherAlgorithm.AES);
keyStatus.print('Key successfully imported');
keyContent.print(Session.secretKey.encoded.toString());
}
}
void _hash() async {
final message = _messageTextController.text.trim();
if (message.isEmpty) {
hashStatus.print('Message is empty');
} else {
MessageDigest md = MessageDigest.getInstance("sha256");
Uint8List hash = await md.digest(TypeHelper.stringToBytes(message));
hashStatus.print('Message successfully hashed.\n' + hash.toString());
}
}
@override
void initState() {
super.initState();
if (Session.secretKey != null) {
keyContent.print(Session.secretKey.encoded.toString());
}
}
@override
void dispose() {
super.dispose();
_pwdTextController.dispose();
_messageTextController.dispose();
_keyTextController.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
Button(
onPressed: _generate,
label: "Generate key",
),
keyStatus,
TextField(
controller: _pwdTextController,
decoration: InputDecoration(
hintText: 'Password',
),
),
Button(
onPressed: _pbkdf2,
label: "Apply PBKDF2",
),
pbkdf2Status,
TextField(
controller: _messageTextController,
decoration: InputDecoration(
hintText: 'Message',
),
),
Button(
onPressed: _hash,
label: "Hash",
),
hashStatus,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
onPressed: _export,
label: "Export key",
),
Button(
onPressed: _import,
label: "Import key",
),
],
),
keyExport
],
),
),
);
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'package:flutter/material.dart';
class KemPage extends StatefulWidget {
KemPage({Key key}) : super(key: key);
@override
_KemPageState createState() => _KemPageState();
}
class _KemPageState extends State<KemPage> {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("Not implemented."),
),
);
}
}

9
example/lib/session.dart Normal file
View File

@ -0,0 +1,9 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'package:native_crypto/native_crypto.dart';
class Session {
static SecretKey secretKey;
static AESCipher aesCipher;
}

30
example/lib/utils.dart Normal file
View File

@ -0,0 +1,30 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'dart:typed_data';
import 'dart:convert';
/// Contains some useful functions.
class TypeHelper {
/// Returns bytes [Uint8List] from a [String].
static Uint8List stringToBytes(String source) {
var list = source.runes.toList();
var bytes = Uint8List.fromList(list);
return bytes;
}
/// Returns a [String] from bytes [Uint8List].
static String bytesToString(Uint8List bytes) {
var string = String.fromCharCodes(bytes);
return string;
}
/// Returns a `base64` [String] from bytes [Uint8List].
static String bytesToBase64(Uint8List bytes) {
return base64.encode(bytes);
}
/// Returns a [Uint8List] from a `base64` [String].
static Uint8List base64ToBytes(String encoded) {
return base64.decode(encoded);
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'package:flutter/material.dart';
class Button extends StatelessWidget {
const Button({Key key, this.onPressed, this.label}) : super(key: key);
final void Function() onPressed;
final String label;
@override
Widget build(BuildContext context) {
return Container(
child: FlatButton(
onPressed: onPressed,
color: Colors.blue,
child: Text(
label,
style: TextStyle(color: Colors.white),
),
),
);
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'dart:developer';
import 'package:flutter/material.dart';
class Output extends StatelessWidget {
const Output(
{Key key,
this.textEditingController,
this.large: false,
this.editable: false})
: super(key: key);
final TextEditingController textEditingController;
final bool large;
final bool editable;
void print(String message) {
log(message, name: "NativeCrypto Example");
textEditingController.text = message;
}
void append(String message) {
log(message, name: "NativeCrypto Example");
textEditingController.text += message;
}
void appendln(String message) {
log(message, name: "NativeCrypto Example");
textEditingController.text += message + "\n";
}
void clear() {
textEditingController.clear();
}
String read() {
return textEditingController.text;
}
@override
Widget build(BuildContext context) {
return Container(
child: TextField(
enableInteractiveSelection: true,
readOnly: editable ? false : true,
minLines: large ? 3 : 1,
maxLines: large ? 500 : 5,
decoration: InputDecoration(border: OutlineInputBorder()),
controller: textEditingController,
),
);
}
}

View File

@ -1,45 +1,25 @@
name: native_crypto_example name: native_crypto_example
description: Demonstrates how to use the native_crypto plugin. description: Demonstrates how to use the native_crypto plugin.
version: 1.0.0+1
# The following line prevents the package from being accidentally published to publish_to: 'none'
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment: environment:
sdk: ">=2.15.0 <3.0.0" sdk: ">=2.1.0 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
native_crypto:
# When depending on this package from a real application you should use:
# native_crypto: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^0.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to native_crypto:
# encourage good coding practices. The lint set provided by the package is path: ../
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@ -54,8 +34,8 @@ flutter:
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware. # https://flutter.dev/assets-and-images/#resolution-aware.

View File

@ -13,13 +13,13 @@ import 'package:native_crypto_example/main.dart';
void main() { void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async { testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame. // Build our app and trigger a frame.
await tester.pumpWidget(const MyApp()); await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved. // Verify that platform version is retrieved.
expect( expect(
find.byWidgetPredicate( find.byWidgetPredicate(
(Widget widget) => widget is Text && (Widget widget) => widget is Text &&
widget.data!.startsWith('Running on:'), widget.data.startsWith('Running on:'),
), ),
findsOneWidget, findsOneWidget,
); );

View File

@ -34,5 +34,4 @@ Icon?
.tags* .tags*
/Flutter/Generated.xcconfig /Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh /Flutter/flutter_export_environment.sh

135
ios/Classes/Cipher.swift Normal file
View File

@ -0,0 +1,135 @@
//
// Cipher.swift
//
// NativeCryptoPlugin
//
// Copyright (c) 2020
// Author: Hugo Pointcheval
//
import Foundation
import CommonCrypto
enum CipherAlgorithm: String {
case AES = "aes"
case BlowFish = "blowfish"
var instance: Int {
switch self {
case .AES: return kCCAlgorithmAES
case .BlowFish: return kCCAlgorithmBlowfish
}
}
}
enum BlockCipherMode: String {
case ECB = "ecb"
case CBC = "cbc"
var instance: Int {
switch self {
case .CBC: return 0
case .ECB: return kCCOptionECBMode
}
}
}
enum Padding: String {
case PKCS5 = "pkcs5"
case None = "none"
var instance: Int {
switch self {
case .PKCS5: return kCCOptionPKCS7Padding
case .None: return 0
}
}
}
class Cipher {
func encrypt(data : Data, key : Data, algorithm : CipherAlgorithm, mode : BlockCipherMode, padding : Padding) -> [Data]? {
// Calculate Mac
let mac = Hash().digest(data: key + data, algorithm: .SHA256)
let payload = mac! + data
// Generate IV
let ivBytes = UnsafeMutableRawPointer.allocate(byteCount: kCCBlockSizeAES128, alignment: 1)
defer { ivBytes.deallocate() }
let ivStatus = CCRandomGenerateBytes(ivBytes, kCCBlockSizeAES128)
if (ivStatus != kCCSuccess) {
return nil
}
let ivData = Data(bytes: ivBytes, count: kCCBlockSizeAES128)
let algo = algorithm.instance
let options: CCOptions = UInt32(mode.instance + padding.instance)
guard var ciphertext = crypt(operation: kCCEncrypt,
algorithm: algo,
options: options,
key: key,
initializationVector: ivData,
dataIn: payload) else { return nil }
return [ciphertext, ivData]
}
func decrypt(payload : [Data], key : Data, algorithm : CipherAlgorithm, mode : BlockCipherMode, padding : Padding) -> Data? {
let encrypted = payload[1] + payload[0]
guard encrypted.count > kCCBlockSizeAES128 else { return nil }
let iv = encrypted.prefix(kCCBlockSizeAES128)
let ciphertext = encrypted.suffix(from: kCCBlockSizeAES128)
let algo = algorithm.instance
let options: CCOptions = UInt32(mode.instance + padding.instance)
guard var decrypted = crypt(operation: kCCDecrypt,
algorithm: algo,
options: options,
key: key,
initializationVector: iv,
dataIn: ciphertext) else {return nil}
// 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 vmac = Hash().digest(data: key + decrypted, algorithm: .SHA256)
if (mac.base64EncodedData() == vmac!.base64EncodedData()) {
return decrypted
} else {
return nil
}
}
private func crypt(operation: Int, algorithm: Int, options: UInt32, key: Data,
initializationVector: Data, dataIn: Data) -> Data? {
return key.withUnsafeBytes { keyUnsafeRawBufferPointer in
return dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in
return initializationVector.withUnsafeBytes { ivUnsafeRawBufferPointer in
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)
}
}
}
}
}

74
ios/Classes/Hash.swift Normal file
View File

@ -0,0 +1,74 @@
//
// Hash.swift
//
// NativeCryptoPlugin
//
// Copyright (c) 2020
// Author: Hugo Pointcheval
//
import Foundation
import CommonCrypto
enum HashAlgorithm: String {
case SHA1 = "sha1"
case SHA224 = "sha224"
case SHA256 = "sha256"
case SHA384 = "sha384"
case SHA512 = "sha512"
var digestLength: Int {
switch self {
case .SHA1: return Int(CC_SHA1_DIGEST_LENGTH)
case .SHA224: return Int(CC_SHA224_DIGEST_LENGTH)
case .SHA256: return Int(CC_SHA256_DIGEST_LENGTH)
case .SHA384: return Int(CC_SHA384_DIGEST_LENGTH)
case .SHA512: return Int(CC_SHA512_DIGEST_LENGTH)
}
}
var pbkdf2: UInt32 {
switch self {
case .SHA1: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1)
case .SHA224: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA224)
case .SHA256: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256)
case .SHA384: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA384)
case .SHA512: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512)
}
}
}
class Hash {
func digest(data: Data?, algorithm: HashAlgorithm) -> Data? {
if (data == nil) {
return nil
}
let hashBytes = UnsafeMutablePointer<UInt8>.allocate(capacity: algorithm.digestLength)
defer { hashBytes.deallocate() }
switch algorithm {
case .SHA1:
data!.withUnsafeBytes { (buffer) -> Void in
CC_SHA1(buffer.baseAddress!, CC_LONG(buffer.count), hashBytes)
}
case .SHA224:
data!.withUnsafeBytes { (buffer) -> Void in
CC_SHA224(buffer.baseAddress!, CC_LONG(buffer.count), hashBytes)
}
case .SHA256:
data!.withUnsafeBytes { (buffer) -> Void in
CC_SHA256(buffer.baseAddress!, CC_LONG(buffer.count), hashBytes)
}
case .SHA384:
data!.withUnsafeBytes { (buffer) -> Void in
CC_SHA384(buffer.baseAddress!, CC_LONG(buffer.count), hashBytes)
}
case .SHA512:
data!.withUnsafeBytes { (buffer) -> Void in
CC_SHA512(buffer.baseAddress!, CC_LONG(buffer.count), hashBytes)
}
}
return Data(bytes: hashBytes, count: algorithm.digestLength)
}
}

View File

@ -0,0 +1,40 @@
//
// KeyDerivation.swift
//
// NativeCryptoPlugin
//
// Copyright (c) 2020
// Author: Hugo Pointcheval
//
import Foundation
import CommonCrypto
class KeyDerivation {
func pbkdf2(password: String, salt: String, keyLength: Int, iteration: Int, algorithm: HashAlgorithm) -> Data? {
let passwordData = password.data(using: .utf8)!
let saltData = salt.data(using: .utf8)!
var derivedKeyData = Data(repeating: 0, count: keyLength)
var localDerivedKeyData = derivedKeyData
let derivationStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
saltData.withUnsafeBytes { saltBytes in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password, passwordData.count,
saltBytes, saltData.count,
algorithm.pbkdf2,
UInt32(iteration),
derivedKeyBytes, localDerivedKeyData.count)
}
}
if (derivationStatus != kCCSuccess) {
print("Error: \(derivationStatus)")
return nil;
}
return derivedKeyData
}
}

View File

@ -0,0 +1,25 @@
//
// KeyGeneration.swift
//
// NativeCryptoPlugin
//
// Copyright (c) 2020
// Author: Hugo Pointcheval
//
import Foundation
class KeyGeneration {
func keygen(size : NSNumber) -> Data? {
var bytes = [Int8](repeating: 0, count: size.intValue / 8)
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
if status == errSecSuccess {
let keyBytes = bytes.withUnsafeBytes {
return Data(Array($0))
}
return keyBytes
}
return nil
}
}

View File

@ -0,0 +1,4 @@
#import <Flutter/Flutter.h>
@interface NativeCryptoPlugin : NSObject<FlutterPlugin>
@end

View File

@ -1,15 +1,15 @@
#import "NativeCryptoIosPlugin.h" #import "NativeCryptoPlugin.h"
#if __has_include(<native_crypto_ios/native_crypto_ios-Swift.h>) #if __has_include(<native_crypto/native_crypto-Swift.h>)
#import <native_crypto_ios/native_crypto_ios-Swift.h> #import <native_crypto/native_crypto-Swift.h>
#else #else
// Support project import fallback if the generated compatibility header // Support project import fallback if the generated compatibility header
// is not copied when this plugin is created as a library. // 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 // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
#import "native_crypto_ios-Swift.h" #import "native_crypto-Swift.h"
#endif #endif
@implementation NativeCryptoIosPlugin @implementation NativeCryptoPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftNativeCryptoIosPlugin registerWithRegistrar:registrar]; [SwiftNativeCryptoPlugin registerWithRegistrar:registrar];
} }
@end @end

View File

@ -0,0 +1,133 @@
//
// NativeCryptoPlugin
//
// Copyright (c) 2020
// Author: Hugo Pointcheval
//
import Flutter
extension FlutterStandardTypedData {
var uint8Array: Array<UInt8> {
return Array(data)
}
var int8Array: Array<Int8> {
return data.withUnsafeBytes { raw in
[Int8](raw.bindMemory(to: Int8.self))
}
}
}
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) {
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":
let args = call.arguments as! NSDictionary
let password = args["password"] as! String
let salt = args["salt"] as! String
let keyLength = args["keyLength"] as! NSNumber
let iteration = args["iteration"] as! NSNumber
let algo = args["algorithm"] as! String
let algorithm : HashAlgorithm? = HashAlgorithm.init(rawValue: algo)
let key = KeyDerivation().pbkdf2(password: password, salt: salt, keyLength: keyLength.intValue, iteration: iteration.intValue, algorithm: algorithm!)
if key != nil {
result(FlutterStandardTypedData.init(bytes: key!))
} else {
result(FlutterError(code: "PBKDF2ERROR",
message: "PBKDF2 KEY IS NIL.",
details: nil)
)
}
case "keygen":
let args = call.arguments as! NSDictionary
let size = args["size"] as! NSNumber
let key = KeyGeneration().keygen(size: size)
if key != nil {
result(FlutterStandardTypedData.init(bytes: key!))
} else {
result(FlutterError(code: "KEYGENERROR",
message: "GENERATED KEY IS NIL.",
details: nil))
}
case "encrypt":
let args = call.arguments as! NSDictionary
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
let algorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algo)
let modeEnum : BlockCipherMode? = BlockCipherMode.init(rawValue: mode)
let paddingEnum : Padding? = Padding.init(rawValue: padding)
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 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 iv = (payload[1] as! FlutterStandardTypedData).data
let encryptedPayload = [encrypted, iv]
let algorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algo)
let modeEnum : BlockCipherMode? = BlockCipherMode.init(rawValue: mode)
let paddingEnum : Padding? = Padding.init(rawValue: padding)
let decrypted = Cipher().decrypt(payload: encryptedPayload, key: key, algorithm: algorithm!, mode: modeEnum!, padding: paddingEnum!)
if decrypted != nil {
result(FlutterStandardTypedData.init(bytes: decrypted!))
} else {
result(FlutterError(code: "DECRYPTIONERROR",
message: "DECRYPTED PAYLOAD IS NIL. MAYBE VERIFICATION MAC IS UNVALID.",
details: nil))
}
default: result(FlutterMethodNotImplemented)
}
}
}

View File

@ -1,9 +1,9 @@
# #
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint native_crypto_ios.podspec` to validate before publishing. # Run `pod lib lint native_crypto.podspec' to validate before publishing.
# #
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'native_crypto_ios' s.name = 'native_crypto'
s.version = '0.0.1' s.version = '0.0.1'
s.summary = 'A new flutter plugin project.' s.summary = 'A new flutter plugin project.'
s.description = <<-DESC s.description = <<-DESC
@ -15,9 +15,9 @@ A new flutter plugin project.
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.dependency 'Flutter' s.dependency 'Flutter'
s.platform = :ios, '13.0' s.platform = :ios, '8.0'
# Flutter.framework does not contain a i386 slice. # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
s.swift_version = '5.0' s.swift_version = '5.0'
end end

19
lib/native_crypto.dart Normal file
View File

@ -0,0 +1,19 @@
// Copyright (c) 2021
// Author: Hugo Pointcheval
export './src/exceptions.dart';
export './src/key.dart';
export './src/keyspec.dart';
export './src/keyderivation.dart';
export './src/digest.dart';
export './src/cipher.dart';
//export './src/kem.dart';
export './src/platform.dart';
export './src/utils.dart';
export './src/sym/AES.dart';
//export './src/asym/RSA.dart';
const String version = "0.0.6";
const String author = "Hugo Pointcheval";
const String website = "https://hugo.pointcheval.fr/";
const String repository = "https://github.com/hugo-pcl/native-crypto-flutter";

Some files were not shown because too many files have changed in this diff Show More