Compare commits

..

No commits in common. "master" and "v0.0.6" have entirely different histories.

320 changed files with 3476 additions and 13301 deletions

View File

@ -1,20 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
- name: quality-check
image: git.wyatt-studio.fr/wyatt-foss/flutter-melos:2.9.0
commands:
- melos run quality-check
- melos run publish:validate
- name: publish
image: git.wyatt-studio.fr/wyatt-foss/flutter-melos:2.9.0
commands:
- melos run publish:validate
trigger:
branch:
- master
event:
- push

442
.gitignore vendored
View File

@ -1,7 +1,107 @@
# 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,java,jetbrains+all,kotlin,linux,objective-c,rust,swift,windows,xcode
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,dart,flutter,java,jetbrains+all,kotlin,linux,objective-c,rust,swift,windows,xcode
# 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.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 ###
# See https://www.dartlang.org/guides/libraries/private-files
@ -9,7 +109,6 @@
# Files and directories created by pub
.dart_tool/
.packages
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
@ -17,9 +116,6 @@ pubspec.lock
# If you don't generate documentation locally you can remove this line.
doc/api/
# dotenv environment variables file
.env*
# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
@ -29,25 +125,13 @@ doc/api/
*.js.deps
*.js.map
.flutter-plugins
.flutter-plugins-dependencies
### Dart Patch ###
# dotenv environment variables file
.env
### Flutter ###
# Flutter/Dart/Pub related
**/doc/api/
.fvm/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.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/**/gradle-wrapper.jar
@ -55,7 +139,6 @@ lib/generated_plugin_registrant.dart
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/key.properties
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
@ -76,15 +159,12 @@ lib/generated_plugin_registrant.dart
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
@ -95,34 +175,8 @@ lib/generated_plugin_registrant.dart
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
@ -132,9 +186,6 @@ replay_pid*
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
@ -155,9 +206,6 @@ replay_pid*
# 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
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
@ -174,7 +222,6 @@ cmake-build-*/
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
@ -185,9 +232,6 @@ atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
@ -200,14 +244,20 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/*
.idea/
!.idea/codeStyles
!.idea/runConfigurations
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Kotlin ###
# Compiled class file
@ -215,27 +265,22 @@ fabric.properties
# Log file
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
### 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*
hs_err_pid*
### macOS ###
# General
@ -246,7 +291,6 @@ fabric.properties
# Icon must end with two \r
Icon
# Thumbnails
._*
@ -266,25 +310,15 @@ Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Objective-C ###
### Swift ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
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)
## Build generated
DerivedData/
*.moved-aside
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
@ -293,73 +327,19 @@ DerivedData/
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### Objective-C Patch ###
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
### Swift ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Playgrounds
timeline.xctimeline
playground.xcworkspace
@ -369,12 +349,9 @@ playground.xcworkspace
# Packages/
# Package.pins
# 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/
# Add this line if you want to avoid checking in Xcode SPM integration.
# .swiftpm/xcode
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
@ -388,47 +365,37 @@ playground.xcworkspace
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# Accio dependency management
Dependencies/
.accio/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/screenshots/**/*.png
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### VisualStudioCode ###
.vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
.vscode/*.code-snippets
# Ignore code-workspaces
*.code-workspace
### Windows ###
# Windows thumbnail cache files
@ -457,26 +424,121 @@ $RECYCLE.BIN/
*.lnk
### Xcode ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Xcode 8 and earlier
## User settings
### Xcode Patch ###
## 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
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,dart,flutter,java,jetbrains+all,kotlin,linux,objective-c,rust,swift,windows,xcode
### 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)
# IntelliJ
*.iml
*.ipr
*.iws
.idea/
# Mac
.DS_Store

View File

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

View File

@ -1,7 +0,0 @@
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v2.1.1
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: [build, ci, docs, feat, fix, perf, refactor, style, test, chore]

28
.vscode/launch.json vendored
View File

@ -1,28 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "native_crypto",
"cwd": "packages/native_crypto/example",
"request": "launch",
"type": "dart"
},
{
"name": "native_crypto (profile mode)",
"cwd": "packages/native_crypto/example",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "native_crypto (release mode)",
"cwd": "packages/native_crypto/example",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
]
}

19
.vscode/settings.json vendored
View File

@ -1,19 +0,0 @@
{
"bloc.newCubitTemplate.type": "equatable",
"psi-header.config": {
"blankLinesAfter": 0,
"forceToTop": true,
},
"psi-header.templates": [
{
"language": "*",
"template": [
"Copyright 2019-<<year>> <<author>>",
"",
"Use of this source code is governed by an MIT-style",
"license that can be found in the LICENSE file or at",
"https://opensource.org/licenses/MIT.",
]
}
],
}

View File

@ -1,6 +0,0 @@
# Below is a list of people and organizations that have contributed
# to this project. Names should be added to the list like so:
#
# Name/Organization <email address>
Hugo Pointcheval <git@pcl.ovh>

View File

@ -1,157 +1,25 @@
# Change Log
## 0.0.6
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
**WIP...**
## 2023-04-05
## 0.0.5
### Changes
* New API
* Add digest support
* Clean platform specific code base
## 0.0.4
---
* Improve AES
Packages with breaking changes:
## 0.0.3
- [`native_crypto` - `v0.2.0`](#native_crypto---v020)
* Add PBKDF2 support
* Add exceptions
* Improve documentation
## 0.0.2
Packages with other changes:
- [`native_crypto_example` - `v0.0.1`](#native_crypto_example---v001)
---
#### `native_crypto` - `v0.2.0`
- **REFACTOR**: (WIP) optimize exceptions and bytearray.
- **REFACTOR**: update benchmark page.
- **FIX**: accept empty decrypted plaintext.
- **FIX**: update code to pass tests.
- **FIX**: update code to pass all tests.
- **FIX**: change tag length in aes gcm cipher.
- **FEAT**: update example for ios file encryption.
- **FEAT**: update example with benchmark + file encryption.
- **FEAT**: update example.
- **FEAT**: rework bytearray and memory optimization, simplify API.
- **DOCS**: update readmes/licences.
- **DOCS**: update readme.
- **BREAKING** **FEAT**: rework full api with better object oriented architecture.
#### `native_crypto_example` - `v0.0.1`
- **REFACTOR**: update benchmark page.
- **REFACTOR**: change file organization.
- **PERF**: x10 perfomance improvement on android with better list management.
- **FIX**: update code to pass all tests.
- **FIX**: benchmark output.
- **FIX**: update and fix code.
- **FEAT**: update example for ios file encryption.
- **FEAT**: update example with benchmark + file encryption.
- **FEAT**: update example.
- **FEAT**: rework bytearray and memory optimization, simplify API.
- **FEAT**: export new exceptions.
- **FEAT**: add PointyCastle benchmark.
## 2023-04-05
### Changes
---
Packages with breaking changes:
- [`native_crypto_platform_interface` - `v0.2.0`](#native_crypto_platform_interface---v020)
Packages with other changes:
- [`native_crypto_android` - `v0.1.2`](#native_crypto_android---v012)
- [`native_crypto_ios` - `v0.1.2`](#native_crypto_ios---v012)
---
#### `native_crypto_platform_interface` - `v0.2.0`
- **REFACTOR**: (WIP) optimize exceptions and bytearray.
- **FIX**: update verify function.
- **FEAT**: make api injectable for test.
- **FEAT**: add exception code for platform throw.
- **DOCS**: update readmes/licences.
- **BREAKING** **FEAT**: set pigeon as default implementation.
- **BREAKING** **FEAT**: add pigeon + add hmac + remove useless decryption method.
#### `native_crypto_android` - `v0.1.2`
- **FIX**: file encryption.
- **FEAT**: use kotlin pigeon generator.
- **FEAT**: generate pigeon messages.
- **DOCS**: update readmes/licences.
#### `native_crypto_ios` - `v0.1.2`
- **REFACTOR**: remove useless lines/classes.
- **FIX**: key length in bits.
- **FEAT**: use swift pigeon generator.
- **FEAT**: generate pigeon messages.
- **DOCS**: update readmes/licences.
## 2022-05-25
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`native_crypto` - `v0.1.1`](#native_crypto---v011)
---
#### `native_crypto` - `v0.1.1`
- **REFACTOR**: change file organization.
- **PERF**: x10 perfomance improvement on android with better list management.
- **FIX**: benchmark output.
- **FIX**: update and fix code.
- **FEAT**: export new exceptions.
- **FEAT**: add PointyCastle benchmark.
- **DOCS**: add link to readme file.
## 2022-05-25
### Changes
---
Packages with breaking changes:
- There are no breaking changes in this release.
Packages with other changes:
- [`native_crypto_ios` - `v0.1.1`](#native_crypto_ios---v011)
- [`native_crypto_android` - `v0.1.1`](#native_crypto_android---v011)
- [`native_crypto_platform_interface` - `v0.1.1`](#native_crypto_platform_interface---v011)
---
#### `native_crypto_ios` - `v0.1.1`
- **REFACTOR**: rework swift part.
- **PERF**: optimize swift code.
#### `native_crypto_android` - `v0.1.1`
- **REFACTOR**: clean and modernize kotlin code.
- **PERF**: x10 perfomance improvement on android with better list management.
- **FEAT**: export new exceptions.
#### `native_crypto_platform_interface` - `v0.1.1`
- **PERF**: x10 perfomance improvement on android with better list management.
- **FEAT**: export new exceptions.
* Add different key size support
* Improve performances
## 0.0.1
* First AES cross-platform encryption & decryption implementation.

View File

@ -1,6 +1,8 @@
native_crypto
MIT License
Copyright (c) 2020-2023 Hugo Pointcheval
Copyright (c) 2021 Hugo Pointcheval
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

447
README.md
View File

@ -1,280 +1,167 @@
<p align="center">
<img width="700px" src="resources/native_crypto.png" style="background-color: rgb(255, 255, 255)">
<h5 align="center">Fast and powerful cryptographic functions for Flutter.</h5>
</p>
<p align="center">
<a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">
<img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />
</a>
<a href="https://github.com/invertase/melos">
<img src="https://img.shields.io/badge/Maintained%20with-melos-f700ff.svg?style=flat-square" alt="Maintained with Melos" />
</a>
<a href="https://drone.wyatt-studio.fr/hugo/native-crypto">
<img src="https://drone.wyatt-studio.fr/api/badges/hugo/native-crypto/status.svg" alt="Build Status" />
</a>
</p>
---
[[Changelog]](./CHANGELOG.md) | [[License]](./LICENSE)
---
## About
The goal of this plugin is to provide a fast and powerful cryptographic functions by calling native libraries. On Android, it uses [javax.cypto](https://developer.android.com/reference/javax/crypto/package-summary), and on iOS, it uses [CommonCrypto](https://opensource.apple.com/source/CommonCrypto/) and [CryptoKit](https://developer.apple.com/documentation/cryptokit/)
I started this projet because I wanted to add cryptographic functions on a Flutter app. But I faced a problem with the well-known [Pointy Castle](https://pub.dev/packages/pointycastle) library: the performance was very poor. Here some benchmarks and comparison:
![](resources/benchmarks.png)
For comparison, on a *iPhone 13*, you can encrypt/decrypt a message of **2MiB** in **~5.6s** with PointyCastle and in **~40ms** with NativeCrypto. And on an *OnePlus 5*, you can encrypt/decrypt a message of **50MiB** in **~6min30** with PointyCastle and in less than **~1s** with NativeCrypto.
In short, NativeCrypto is incomparable with PointyCastle.
## Features
* Hash functions
- SHA-256
- SHA-384
- SHA-512
* HMAC functions
- HMAC-SHA-256
- HMAC-SHA-384
- HMAC-SHA-512
* Secure random
* PBKDF2
* AES
- Uint8List encryption/decryption
- File encryption/decryption
## Quick start
```dart
import 'package:native_crypto/native_crypto.dart';
Future<void> main() async {
// Message to encrypt
final Uint8List message = 'Hello World!'.toBytes();
// Ask user for a password
final String password = await getPassword();
// Initialize a PBKDF2 object
final Pbkdf2 pbkdf2 = Pbkdf2(
length: 32, // 32 bytes
iterations: 1000,
salt: 'salt'.toBytes(),
hashAlgorithm: HashAlgorithm.sha256,
);
// Derive a secret key from the password
final SecretKey secretKey = await pbkdf2(password: password);
// Initialize an AES cipher
final AES cipher = AES(
key: secretKey,
mode: AESMode.gcm,
padding: AESPadding.none,
);
// Encrypt the message
final CipherText<AESCipherChunk> cipherText = await cipher.encrypt(message);
// Decrypt the message
final Uint8List decryptedMessage = await cipher.decrypt(cipherText);
// Verify and print the decrypted message
assert(listEquals(message, decryptedMessage));
print(decryptedMessage.toStr());
}
```
Check the [example](./native_crypto/example) for a complete example.
Please take a look a the compatibility table below to check if your target is supported.
> Note: This **Flutter** example must run on a real device or a simulator.
## Usage
#### Compatibility
First, check compatibility with your targets.
| iOS | Android | MacOS | Linux | Windows | Web |
| --- | ------- | ----- | ----- | ------- | --- |
| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
> Warning: NativeCrypto 0.2.0+ is not compatible with lower NativeCrypto versions. Especially, with NativeCrypto 0.0. X because the cipher mode is not the same. Now, NativeCrypto uses AES-GCM mode instead of AES-CBC mode. (See [Changelog](./CHANGELOG.md))
NativeCrypto ciphertexts are formatted as follow:
```
+------------------+--------------------+------------------+
| Nonce (12 bytes) | Cipher text (n-28) | Tag (16 bytes) |
+------------------+--------------------+------------------+
```
> Warning: If your data comes from another source, make sur to use the same format.
#### Hash
To digest a message, you'll need to initialize a Hasher object implementing `Hash` . Then, you can digest your message.
```dart
Hash hasher = Sha256();
Uint8List digest = await hasher.digest(message);
```
> In NativeCrypto, you can use the following hash functions: SHA-256, SHA-384, SHA-512
#### HMAC
To generate a HMAC, you'll need to initialize a `Hmac` object. Then, you can generate a HMAC from a message and a secret key.
```dart
Hmac hmac = HmacSha256();
Uint8List hmac = await hmac.digest(message, secretKey);
```
> In NativeCrypto, you can use the following HMAC functions: HMAC-SHA-256, HMAC-SHA-384, HMAC-SHA-512
#### Keys
You can build a `SecretKey` from utf8, utf16, base64, base16 (hex) strings, int list or raw bytes. You can also generate a SecretKey from secure random.
```dart
SecretKey secretKey = SecretKey(bytes); // bytes is a Uint8List
SecretKey secretKey = SecretKey.fromUtf8('secret');
SecretKet secretKey = SecretKey.fromUtf16('secret');
SecretKey secretKey = SecretKey.fromBase64('c2VjcmV0');
SecretKey secretKey = SecretKey.fromBase16('63657274');
SecretKey secretKey = SecretKey.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74]);
SecretKey secretKey = await SecretKey.fromSecureRandom(32); // 32 bytes
```
#### Key derivation
You can derive a `SecretKey` using **PBKDF2**.
First, you need to initialize a `Pbkdf2` object.
```dart
final Pbkdf2 pbkdf2 = Pbkdf2(
length: 32, // 32 bytes
iterations: 1000,
salt: salt.toBytes(),
hashAlgorithm: HashAlgorithm.sha256,
);
```
Then, you can derive a `SecretKey` from a password.
```dart
SecretKey secretKey = await pbkdf2(password: password);
```
> Note: Pbkdf2 is a callable class. You can use it like a function.
#### Cipher
And now, you can use the `SecretKey` to encrypt/decrypt a message.
First, you need to initialize a `Cipher` object.
```dart
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
```
Then, you can encrypt your message.
```dart
final CipherText<AESCipherChunk> cipherText = await cipher.encrypt(message);
```
After an encryption you obtain a `CipherText` which contains chunks. You can get the underlying bytes with `cipherText.bytes` .
Uppon receiving encrypted message `receivedData` , you can decrypt it.
You have to reconstruct the ciphertext and the setup the chunk factory.
```dart
final CipherText<AESCipherChunk> receivedCipherText CipherText(
receivedData,
chunkFactory: (bytes) => AESCipherChunk(
bytes,
ivLength: cipher.mode.ivLength,
tagLength: cipher.mode.tagLength,
),
),
```
Then, you can decrypt your message.
```dart
Uint8List message = await cipher.decrypt(receivedCipherText);
```
#### Files
You can encrypt/decrypt files.
First, you need to initialize a `Cipher` object.
```dart
final AES cipher = AES(
key: key,
mode: AESMode.gcm,
padding: AESPadding.none,
);
```
Then, you can encrypt your file.
```dart
await cipher.encryptFile(plainText, cipherText);
```
> Note: `plainText` and `cipherText` are `File` objects.
You can decrypt your file.
```dart
await cipher.decryptFile(cipherText, plainText);
```
#### Advanced
You can force the use of a specific IV. Please note that the IV must be unique for each encryption.
```dart
final CipherText<AESCipherChunk> cipherText = await cipher.encryptWithIV(message, iv);
```
⚠️ Use `encrypt(...)` instead of `encryptWithIV(...)` if you don't know what you are doing.
## Development
### Android
> https://docs.flutter.dev/development/packages-and-plugins/developing-packages#step-2b-add-android-platform-code-ktjava
* Launch Android Studio.
* Select Open an existing Android Studio Project in the Welcome to Android Studio dialog, or select File > Open from the menu, and select the `packages/native_crypto/example/android/build.gradle` file.
* In the Gradle Sync dialog, select OK.
* In the Android Gradle Plugin Update dialog, select Dont remind me again for this project.
### iOS
> https://docs.flutter.dev/development/packages-and-plugins/developing-packages#step-2c-add-ios-platform-code-swifthm
* Launch Xcode.
* Select File > Open, and select the `packages/native_crypto/example/ios/Runner.xcworkspace` file.
# 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 iPhone 11.
| Size (kB) | NativeCrypto **encryption** time (ms) |
|-----------|---------------------------------------|
| 2 mB | 13 ms
| 4 mB | 17 ms
| 8 mB | 56 ms
| 16 mB | 73 ms
| 32 mB | 120 ms
| 64 mB | 243 ms
| 128 mB | 509 ms
| 256 mB | 1057 ms
> ~1s for 256 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://github.com/hugo-pcl/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.
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.
- [x] 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
- [Chisom Maxwell](https://github.com/maxcotech) - For the chunks idea [#2](https://github.com/hugo-pcl/native-crypto-flutter/issues/2)

View File

@ -1,15 +1,15 @@
group 'fr.pointcheval.native_crypto_android'
group 'fr.pointcheval.native_crypto'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.6.21'
ext.kotlin_version = '1.3.50'
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -17,7 +17,7 @@ buildscript {
rootProject.allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
}
@ -25,27 +25,20 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
compileSdkVersion 29
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 26
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.documentfile:documentfile:1.0.1'
}

View File

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

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
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

@ -1,3 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.pointcheval.native_crypto_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

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

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

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: cf4400006550b70f28e4b4af815151d1e74846c6
revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3
channel: stable
project_type: plugin
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"
android {
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
compileSdkVersion 28
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "fr.pointcheval.native_crypto_example"
minSdkVersion 26
targetSdkVersion flutter.targetSdkVersion
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -57,7 +53,6 @@ android {
signingConfig signingConfigs.debug
}
}
namespace 'fr.pointcheval.native_crypto_example'
}
flutter {
@ -66,4 +61,7 @@ flutter {
dependencies {
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,4 +1,5 @@
<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">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,24 +1,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.pointcheval.native_crypto_example">
<!-- 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:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
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>
<action android:name="android.intent.action.MAIN"/>
<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

@ -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

@ -1,4 +1,5 @@
<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">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
#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-7.4-all.zip
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
*.mode2v3
*.moved-aside
@ -19,7 +18,6 @@ Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/

View File

@ -0,0 +1 @@
a1d04c54a9dc0dec55298921eadf7972

View File

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

View File

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

View File

@ -1,2 +1,3 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#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.10.1

View File

@ -3,17 +3,17 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
57C8B66CEF3FCADD27359CF2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B565CC9BF59F330E881E6A5 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
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 */
/* Begin PBXCopyFilesBuildPhase section */
@ -32,10 +32,8 @@
/* Begin PBXFileReference section */
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>"; };
2ABDB9EE0C984D50A4E8A819 /* 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>"; };
4276EDF2B0F07350DCE3D5A1 /* 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>"; };
5CD4F461EBFD40A270B90468 /* 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>"; };
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>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@ -46,7 +44,9 @@
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>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9B565CC9BF59F330E881E6A5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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 */
/* Begin PBXFrameworksBuildPhase section */
@ -54,22 +54,19 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
57C8B66CEF3FCADD27359CF2 /* Pods_Runner.framework in Frameworks */,
D294241BEFD2D3EF500C0577 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
56AFD4323C9A594E66DE3CA2 /* Pods */ = {
8C27A9C4EB5E71366C317BF8 /* Frameworks */ = {
isa = PBXGroup;
children = (
4276EDF2B0F07350DCE3D5A1 /* Pods-Runner.debug.xcconfig */,
2ABDB9EE0C984D50A4E8A819 /* Pods-Runner.release.xcconfig */,
5CD4F461EBFD40A270B90468 /* Pods-Runner.profile.xcconfig */,
C6CB4DB73769DFCBF23FCC12 /* Pods_Runner.framework */,
);
name = Pods;
path = Pods;
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
@ -89,8 +86,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
56AFD4323C9A594E66DE3CA2 /* Pods */,
CECC25F8D34DF7912A93B0E6 /* Frameworks */,
9F5A6E791DB57C1B2E0BF33E /* Pods */,
8C27A9C4EB5E71366C317BF8 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -109,6 +106,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@ -117,12 +115,21 @@
path = Runner;
sourceTree = "<group>";
};
CECC25F8D34DF7912A93B0E6 /* Frameworks */ = {
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
9B565CC9BF59F330E881E6A5 /* 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 */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
@ -132,14 +139,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
C54B0935BA386F7769969821 /* [CP] Check Pods Manifest.lock */,
D72ACB84F79B4AD23991D136 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
D9E654BBB4893905628A201A /* [CP] Embed Pods Frameworks */,
FF811A27CCC4B4D5EBD756CE /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -156,17 +163,18 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = 6Z5P8GG96U;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@ -200,7 +208,6 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -215,7 +222,6 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -228,7 +234,7 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C54B0935BA386F7769969821 /* [CP] Check Pods Manifest.lock */ = {
D72ACB84F79B4AD23991D136 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -250,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";
showEnvVarsInLog = 0;
};
D9E654BBB4893905628A201A /* [CP] Embed Pods Frameworks */ = {
FF811A27CCC4B4D5EBD756CE /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -342,7 +346,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -360,12 +364,18 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6Z5P8GG96U;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoExample;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoFlutterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -420,7 +430,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -469,12 +479,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@ -489,12 +498,18 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6Z5P8GG96U;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoExample;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoFlutterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -512,12 +527,18 @@
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6Z5P8GG96U;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoExample;
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = fr.pointcheval.nativeCryptoFlutterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View File

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:">
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

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

View File

@ -4,8 +4,6 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Native Crypto</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -42,14 +40,6 @@
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<false/>
</dict>
</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,36 @@
// Copyright (c) 2020
// Author: Hugo Pointcheval
import 'package:flutter/material.dart';
import 'package:native_crypto/native_crypto.dart';
class KemPage extends StatefulWidget {
KemPage({key}) : super(key: key);
@override
_KemPageState createState() => _KemPageState();
}
class _KemPageState extends State<KemPage> {
void test() async {
KeySpec specs = RSAKeySpec(2048);
KeyPair kp = await KeyPair.generate(specs);
print(kp.isComplete);
print(kp.privateKey);
print(kp.privateKey.encoded);
}
@override
void initState() {
super.initState();
test();
}
@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,51 +1,32 @@
name: native_crypto_example
description: Demonstrates how to use the native_crypto plugin.
# The following line prevents the package from being accidentally published to
# 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
version: 1.0.0+1
publish_to: 'none'
environment:
sdk: ">=2.17.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:
flutter: { sdk: flutter }
native_crypto: { path: ../ }
cupertino_icons: ^1.0.5
flutter_riverpod: ^2.1.3
pointycastle: ^3.6.2
flutter_bloc: ^8.1.1
flutter:
sdk: flutter
wyatt_architecture:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.1.0+1
wyatt_type_utils:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.0.4
wyatt_bloc_helper:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 2.0.0
get_it: ^7.2.0
file_picker: ^5.2.7
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test: { sdk: flutter }
flutter_test:
sdk: flutter
wyatt_analysis:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 2.4.1
native_crypto:
path: ../
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
@ -53,8 +34,8 @@ flutter:
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.

View File

@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View File

@ -34,5 +34,4 @@ Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/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,59 @@
//
// KeyGeneration.swift
//
// NativeCryptoPlugin
//
// Copyright (c) 2020
// Author: Hugo Pointcheval
//
import Foundation
import CommonCrypto
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
}
@available(iOS 10.0, watchOS 3.0, tvOS 10.0, *)
func rsaKeypairGen(size : NSNumber) throws -> [Data]? {
let tagData = UUID().uuidString.data(using: .utf8)
let isPermanent = true
let attributes: [CFString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits: (size.intValue * 8),
kSecPrivateKeyAttrs: [
kSecAttrIsPermanent: isPermanent,
kSecAttrApplicationTag: tagData!
]
]
var error: Unmanaged<CFError>?
guard let privKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
let pubKey = SecKeyCopyPublicKey(privKey)
var errorExport: Unmanaged<CFError>?
let data1 = SecKeyCopyExternalRepresentation(pubKey!, &errorExport)
let unwrappedData1 = data1 as Data?
let data2 = SecKeyCopyExternalRepresentation(privKey, &errorExport)
let unwrappedData2 = data2 as Data?
return [unwrappedData1!, unwrappedData2!]
}
}

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