Add new public api and example

This commit is contained in:
Hugo Pointcheval 2021-12-28 16:03:50 +01:00
parent c01e0a12ba
commit 3381ff67b9
35 changed files with 1439 additions and 98 deletions

View File

@ -1,3 +1,35 @@
## 0.1.0
> Breaking changes !
* Follow **Federated Plugin** Flutter standard.
## 0.0.6
* Add KeyPair generation.
* Rework exposed API.
## 0.0.5
* New API.
* Add digest support.
* Clean platform specific code base.
## 0.0.4
* Improve AES.
## 0.0.3
* Add PBKDF2 support.
* Add exceptions.
* Improve documentation.
## 0.0.2
* Add different key size support.
* Improve performances.
## 0.0.1
* TODO: Describe initial release.
* First AES cross-platform encryption & decryption implementation.

View File

@ -1 +1,23 @@
TODO: Add your license here.
NativeCrypto
MIT License
Copyright (c) 2019 - 2022 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,6 +1,6 @@
# native_crypto
# NativeCrypto
A new flutter plugin project.
Fast and powerful cryptographic functions thanks to **javax.crypto** and **CryptoKit**.
## Getting Started

View File

@ -44,7 +44,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "fr.pointcheval.native_crypto_example"
minSdkVersion flutter.minSdkVersion
minSdkVersion 26
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@ -3,6 +3,7 @@
<application
android:label="native_crypto_example"
android:name="${applicationName}"
android:largeHeap="true"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"

View File

@ -0,0 +1,68 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: home.dart
// Created Date: 28/12/2021 13:48:36
// Last Modified: 28/12/2021 15:18:03
// -----
// Copyright (c) 2021
import 'package:flutter/material.dart';
import 'package:native_crypto_example/pages/benchmark_page.dart';
import 'pages/cipher_page.dart';
import 'pages/kdf_page.dart';
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
final List<Widget> _children = [
KdfPage(),
CipherPage(),
BenchmarkPage()
];
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return 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: const [
BottomNavigationBarItem(
icon: Icon(Icons.vpn_key),
label: 'Key',
),
BottomNavigationBarItem(
icon: Icon(Icons.lock),
label: 'Encryption',
),
BottomNavigationBarItem(
icon: Icon(Icons.timer),
label: 'Benchmark',
),
],
),
);
}
}

View File

@ -1,62 +1,25 @@
import 'package:flutter/material.dart';
import 'dart:async';
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: main.dart
// Created Date: 27/12/2021 21:15:12
// Last Modified: 28/12/2021 13:51:36
// -----
// Copyright (c) 2021
import 'package:flutter/services.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto_example/home.dart';
void main() {
runApp(const MyApp());
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatefulWidget {
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
platformVersion =
await NativeCrypto.platformVersion ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text('Running on: $_platformVersion\n'),
),
),
);
return const MaterialApp(home: Home());
}
}

View File

@ -0,0 +1,137 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: benchmark_page.dart
// Created Date: 28/12/2021 15:12:39
// Last Modified: 28/12/2021 15:21:05
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../widgets/output.dart';
class BenchmarkPage extends ConsumerWidget {
BenchmarkPage({Key? key}) : super(key: key);
final Output keyContent = Output();
final Output benchmarkStatus = Output(large: true);
Future<void> _benchmark(WidgetRef ref, Cipher cipher) async {
Session state = ref.read(sessionProvider.state).state;
if (state.secretKey.bytes.isEmpty) {
benchmarkStatus
.print('No SecretKey!\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 cipher.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;
// Decryption
before = DateTime.now();
await cipher.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');
debugPrint("[Benchmark cvs]\n$csv");
}
void _clear() {
benchmarkStatus.clear();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
Session state = ref.read(sessionProvider.state).state;
if (state.secretKey.bytes.isEmpty) {
keyContent
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
],
),
),
);
}
keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm);
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
() => _benchmark(ref, cipher),
"Launch benchmark",
),
Button(
_clear,
"Clear",
),
],
),
benchmarkStatus,
],
),
),
);
}
}

View File

@ -0,0 +1,158 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_page.dart
// Created Date: 28/12/2021 13:33:15
// Last Modified: 28/12/2021 15:20:43
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart';
// ignore: must_be_immutable
class CipherPage extends ConsumerWidget {
CipherPage({Key? key}) : super(key: key);
final Output keyContent = Output();
final Output encryptionStatus = Output();
final Output decryptionStatus = Output();
final TextEditingController _plainTextController = TextEditingController();
CipherText? cipherText;
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
Session state = ref.read(sessionProvider.state).state;
final plainText = _plainTextController.text.trim();
if (state.secretKey.bytes.isEmpty) {
encryptionStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
} else if (plainText.isEmpty) {
encryptionStatus.print('Entry is empty');
} else {
var stringToBytes = plainText.toBytes();
cipherText = await cipher.encrypt(stringToBytes);
encryptionStatus.print('String successfully encrypted.\n');
encryptionStatus.append("Nonce: " +
cipherText!.iv.toString() +
"\nData: " +
cipherText!.data.toString() +
"\nTag: " +
cipherText!.tag.toString());
}
}
Future<void> _alter() async {
if (cipherText == null) {
decryptionStatus.print('Encrypt before altering CipherText!');
} else {
// Add 1 to the first byte
Uint8List _altered = cipherText!.data;
_altered[0] += 1;
// Recreate cipher text with altered data
cipherText = CipherText(cipherText!.iv, _altered, cipherText!.tag);
encryptionStatus.print('String successfully encrypted.\n');
encryptionStatus.append("Nonce: " +
cipherText!.iv.toString() +
"\nData: " +
cipherText!.data.toString() +
"\nTag: " +
cipherText!.tag.toString());
decryptionStatus.print('CipherText altered!\nDecryption will fail.');
}
}
void _decrypt(WidgetRef ref, Cipher cipher) async {
Session state = ref.read(sessionProvider.state).state;
if (state.secretKey.bytes.isEmpty) {
decryptionStatus
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
} else if (cipherText == null) {
decryptionStatus.print('Encrypt before decrypting!');
} else {
try {
Uint8List plainText = await cipher.decrypt(cipherText!);
var bytesToString = plainText.toStr();
decryptionStatus
.print('String successfully decrypted:\n\n$bytesToString');
} on DecryptionException catch (e) {
decryptionStatus.print(e.message);
}
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
Session state = ref.read(sessionProvider.state).state;
if (state.secretKey.bytes.isEmpty) {
keyContent
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
],
),
),
);
}
keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm);
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("Secret Key"),
alignment: Alignment.centerLeft,
),
keyContent,
TextField(
controller: _plainTextController,
decoration: const InputDecoration(
hintText: 'Plain text',
),
),
Button(
() => _encrypt(ref, cipher),
"Encrypt",
),
encryptionStatus,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
_alter,
"Alter cipher",
),
Button(
() => _decrypt(ref, cipher),
"Decrypt",
),
],
),
decryptionStatus,
],
),
),
);
}
}

View File

@ -0,0 +1,130 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: kdf_page.dart
// Created Date: 28/12/2021 13:40:34
// Last Modified: 28/12/2021 15:14:12
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart';
class KdfPage extends ConsumerWidget {
KdfPage({Key? key}) : super(key: key);
final Output keyContent = Output();
final Output keyStatus = Output();
final Output pbkdf2Status = Output();
final Output hashStatus = Output(large: true);
final TextEditingController _pwdTextController = TextEditingController();
final TextEditingController _messageTextController = TextEditingController();
Future<void> _generate(WidgetRef ref) async {
Session state = ref.read(sessionProvider.state).state;
try {
SecretKey sk = await SecretKey.fromSecureRandom(256);
state.setKey(sk);
keyStatus.print(
"SecretKey successfully generated.\nLength: ${state.secretKey.bytes.length} bytes");
keyContent.print(state.secretKey.bytes.toString());
debugPrint("As hex :\n${sk.base16}");
} catch (e) {
keyStatus.print(e.toString());
}
}
Future<void> _pbkdf2(WidgetRef ref) async {
Session state = ref.read(sessionProvider.state).state;
final password = _pwdTextController.text.trim();
if (password.isEmpty) {
pbkdf2Status.print('Password is empty');
} else {
PBKDF2 _pbkdf2 = PBKDF2(32, 1000, algorithm: HashAlgorithm.sha512);
SecretKey sk = await _pbkdf2.derive(password: password, salt: 'salt');
state.setKey(sk);
pbkdf2Status.print('Key successfully derived.');
keyContent.print(state.secretKey.bytes.toString());
debugPrint("As hex :\n${sk.base16}");
}
}
Future<void> _hash(Hasher hasher) async {
final message = _messageTextController.text.trim();
if (message.isEmpty) {
hashStatus.print('Message is empty');
} else {
Uint8List hash = await hasher.digest(message.toBytes());
hashStatus.print(
'Message successfully hashed with ${hasher.algorithm} :${hash.toStr(to: Encoding.hex)}');
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Align(
child: Text("SecretKey"),
alignment: Alignment.centerLeft,
),
keyContent,
Button(
() => _generate(ref),
"Generate SecretKey",
),
keyStatus,
TextField(
controller: _pwdTextController,
decoration: const InputDecoration(
hintText: 'Password',
),
),
Button(
() => _pbkdf2(ref),
"Apply PBKDF2",
),
pbkdf2Status,
TextField(
controller: _messageTextController,
decoration: const InputDecoration(
hintText: 'Message',
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Button(
() => _hash(SHA256()),
"SHA256",
),
Button(
() => _hash(SHA384()),
"SHA384",
),
Button(
() => _hash(SHA512()),
"SHA512",
),
],
),
hashStatus,
],
),
),
);
}
}

View File

@ -0,0 +1,24 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: session.dart
// Created Date: 28/12/2021 13:54:29
// Last Modified: 28/12/2021 13:58:49
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.dart';
class Session {
SecretKey secretKey;
Session() : secretKey = SecretKey(Uint8List(0));
void setKey(SecretKey sk) { secretKey = sk; }
}
// Providers
final sessionProvider = StateProvider<Session>((ref) => Session());

View File

@ -0,0 +1,52 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: utils.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 28/12/2021 14:40:21
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'dart:convert';
enum Encoding { utf16, base64, hex }
extension StringX on String {
Uint8List toBytes({final from = Encoding.utf16}) {
Uint8List bytes = Uint8List(0);
switch (from) {
case Encoding.utf16:
bytes = Uint8List.fromList(runes.toList());
break;
case Encoding.base64:
bytes = base64.decode(this);
break;
case Encoding.hex:
bytes = Uint8List.fromList(
List.generate(
length ~/ 2,
(i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16),
).toList(),
);
}
return bytes;
}
}
extension Uint8ListX on Uint8List {
String toStr({final to = Encoding.utf16}) {
String str = "";
switch (to) {
case Encoding.utf16:
str = String.fromCharCodes(this);
break;
case Encoding.base64:
str = base64.encode(this);
break;
case Encoding.hex:
str = map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
}
return str;
}
}

View File

@ -0,0 +1,32 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: button.dart
// Created Date: 28/12/2021 13:31:17
// Last Modified: 28/12/2021 13:31:34
// -----
// Copyright (c) 2021
import 'package:flutter/material.dart';
class Button extends StatelessWidget {
final void Function() onPressed;
final String label;
const Button(this.onPressed, this.label, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: TextButton.styleFrom(
primary: Colors.blue,
),
child: Text(
label,
style: const TextStyle(color: Colors.white),
),
);
}
}

View File

@ -0,0 +1,61 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: output.dart
// Created Date: 28/12/2021 13:31:39
// Last Modified: 28/12/2021 14:12:11
// -----
// Copyright (c) 2021
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class Output extends StatelessWidget {
late TextEditingController controller;
final bool large;
final bool editable;
Output({
Key? key,
TextEditingController? controller,
this.large = false,
this.editable = false,
}) : super(key: key) {
this.controller = controller ?? TextEditingController();
}
void print(String message) {
debugPrint(message);
controller.text = message;
}
void append(String message) {
debugPrint(message);
controller.text += message;
}
void appendln(String message) {
debugPrint(message);
controller.text += message + "\n";
}
void clear() {
controller.clear();
}
String read() {
return controller.text;
}
@override
Widget build(BuildContext context) {
return TextField(
enableInteractiveSelection: true,
readOnly: editable ? false : true,
minLines: large ? 3 : 1,
maxLines: large ? 500 : 5,
decoration: const InputDecoration(border: OutlineInputBorder()),
controller: controller,
);
}
}

View File

@ -29,6 +29,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
flutter_riverpod: ^1.0.3
dev_dependencies:
flutter_test:
@ -39,7 +40,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
flutter_lints: ^1.0.4
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@ -1,17 +1,27 @@
// You have generated a new plugin project without
// specifying the `--platforms` flag. A plugin project supports no platforms is generated.
// To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same
// directory. You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: native_crypto.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 28/12/2021 15:06:48
// -----
// Copyright (c) 2021
import 'dart:async';
export 'src/byte_array.dart';
export 'src/cipher.dart';
export 'src/cipher_text.dart';
export 'src/ciphers/aes.dart';
export 'src/exceptions.dart';
export 'src/hasher.dart';
export 'src/hashers/sha256.dart';
export 'src/hashers/sha384.dart';
export 'src/hashers/sha512.dart';
export 'src/kdf/pbkdf2.dart';
export 'src/keyderivation.dart';
export 'src/keys/secret_key.dart';
export 'src/utils.dart';
import 'package:flutter/services.dart';
class NativeCrypto {
static const MethodChannel _channel = MethodChannel('native_crypto');
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
const String version = "0.1.0";
const String author = "Hugo Pointcheval";
const String website = "https://hugo.pointcheval.fr/";
const List<String> repositories = ["https://github.com/hugo-pcl/native-crypto-flutter", "https://git.pointcheval.fr/NativeCrypto/native-crypto-flutter"];

View File

@ -0,0 +1,12 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: builder.dart
// Created Date: 28/12/2021 12:02:34
// Last Modified: 28/12/2021 12:32:12
// -----
// Copyright (c) 2021
abstract class Builder<T> {
Future<T> build();
}

View File

@ -0,0 +1,46 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes_builder.dart
// Created Date: 28/12/2021 12:03:11
// Last Modified: 28/12/2021 13:39:23
// -----
// Copyright (c) 2021
import '../builder.dart';
import '../ciphers/aes.dart';
import '../exceptions.dart';
import '../keys/secret_key.dart';
class AESBuilder implements Builder<AES> {
SecretKey? _sk;
Future<SecretKey>? _fsk;
AESMode _mode = AESMode.gcm;
AESBuilder withGeneratedKey(int bitsCount) {
_fsk = SecretKey.fromSecureRandom(bitsCount);
return this;
}
AESBuilder withKey(SecretKey secretKey) {
_sk = secretKey;
return this;
}
AESBuilder using(AESMode mode) {
_mode = mode;
return this;
}
@override
Future<AES> build() async {
if (_sk == null) {
if (_fsk == null) {
throw CipherInitException("You must specify or generate a secret key.");
} else {
_sk = await _fsk;
}
}
return AES(_sk!, _mode);
}
}

View File

@ -0,0 +1,72 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: byte_array.dart
// Created Date: 16/12/2021 17:54:16
// Last Modified: 27/12/2021 21:51:36
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'utils.dart';
import 'dart:convert' as convert;
class ByteArray {
Uint8List _bytes;
ByteArray(this._bytes);
/// Creates an ByteArray object from a hexdecimal string.
ByteArray.fromBase16(String encoded) : _bytes = Utils.decodeHexString(encoded);
/// Creates an ByteArray object from a Base64 string.
ByteArray.fromBase64(String encoded)
: _bytes = convert.base64.decode(encoded);
/// Creates an ByteArray object from a UTF-8 string.
ByteArray.fromUtf8(String input)
: _bytes = Uint8List.fromList(convert.utf8.encode(input));
/// Creates an ByteArray object from a length.
ByteArray.fromLength(int length) : _bytes = Uint8List(length);
/// Gets the ByteArray bytes.
// ignore: unnecessary_getters_setters
Uint8List get bytes => _bytes;
/// Sets the ByteArray bytes.
set bytes(Uint8List value) => _bytes = value;
/// Gets the ByteArray bytes as a Hexadecimal representation.
String get base16 =>
_bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
/// Gets the ByteArray bytes as a Base64 representation.
String get base64 => convert.base64.encode(_bytes);
@override
bool operator ==(other) {
if (other is ByteArray) {
for (int i = 0; i < _bytes.length; i++) {
if (_bytes[i] != other._bytes[i]) {
return false;
}
}
return true;
}
return false;
}
@override
int get hashCode {
int hash = 0;
for (int i = 0; i < _bytes.length; i++) {
hash = _bytes[i] + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
}

View File

@ -0,0 +1,37 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 28/12/2021 12:25:38
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'cipher_text.dart';
/// Represents different cipher algorithms
enum CipherAlgorithm { aes, rsa }
/// Represents a cipher.
///
/// In cryptography, a cipher is an algorithm for performing encryption
/// or decryption - a series of well-defined steps that can
/// be followed as a procedure.
abstract class Cipher {
/// Returns the standard algorithm name for this cipher
CipherAlgorithm get algorithm;
/// Encrypts data.
///
/// Takes [Uint8List] data as parameter.
/// Returns a [CipherText].
Future<CipherText> encrypt(Uint8List data);
/// Decrypts cipher text.
///
/// Takes [CipherText] as parameter.
/// And returns plain text data as [Uint8List].
Future<Uint8List> decrypt(CipherText cipherText);
}

View File

@ -0,0 +1,51 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_text.dart
// Created Date: 16/12/2021 16:59:53
// Last Modified: 27/12/2021 22:32:06
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'byte_array.dart';
class CipherText extends ByteArray {
final int _ivLength;
final int _dataLength;
final int _tagLength;
CipherText(Uint8List iv, Uint8List data, Uint8List tag) : _ivLength = iv.length, _dataLength = data.length, _tagLength = tag.length, super(Uint8List.fromList(iv + data + tag));
/// Gets the CipherText IV.
Uint8List get iv => bytes.sublist(0, _ivLength);
/// Gets the CipherText data.
Uint8List get data => bytes.sublist(_ivLength, _ivLength + _dataLength);
/// Gets the CipherText tag.
Uint8List get tag => bytes.sublist(_ivLength + _dataLength, _ivLength + _dataLength + _tagLength);
/// Gets the CipherText IV length.
int get ivLength => _ivLength;
/// Gets the CipherText data length.
int get dataLength => _dataLength;
/// Gets the CipherText tag length.
int get tagLength => _tagLength;
}
class CipherTextList extends CipherText {
static const int chunkSize = 33554432;
final List<CipherText> _list;
CipherTextList() : _list = [], super(Uint8List(0), Uint8List(0), Uint8List(0));
void add(CipherText cipherText) {
_list.add(cipherText);
}
get list => _list;
}

View File

@ -0,0 +1,112 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 28/12/2021 13:39:00
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import '../cipher.dart';
import '../cipher_text.dart';
import '../exceptions.dart';
import '../keys/secret_key.dart';
import '../platform.dart';
import '../utils.dart';
/// Defines the AES modes of operation.
enum AESMode { gcm }
/// Defines all available key sizes.
enum AESKeySize { bits128, bits192, bits256 }
/// Represents different paddings.
enum AESPadding { none }
extension AESKeySizeExtension on AESKeySize {
static final Map<AESKeySize, int> sizes = <AESKeySize, int>{
AESKeySize.bits128: 128,
AESKeySize.bits192: 192,
AESKeySize.bits256: 256,
};
static final List<int> supportedSizes = sizes.values.toList(growable: false);
int get length {
return sizes[this]!; // this is safe because `this` is listed in the enum
}
}
class AES implements Cipher {
final SecretKey key;
final AESMode mode;
final AESPadding padding;
@override
CipherAlgorithm get algorithm => CipherAlgorithm.aes;
AES(this.key, this.mode, {this.padding = AESPadding.none}) {
if (!AESKeySizeExtension.supportedSizes.contains(key.bytes.length * 8)) {
throw CipherInitException("Invalid key length!");
}
Map<AESMode, List<AESPadding>> _supported = {
AESMode.gcm: [AESPadding.none],
};
if (!_supported[mode]!.contains(padding)) {
throw CipherInitException("Invalid padding!");
}
}
@override
Future<Uint8List> decrypt(CipherText cipherText) async {
BytesBuilder decryptedData = BytesBuilder(copy: false);
if (cipherText is CipherTextList) {
for (CipherText ct in cipherText.list) {
Uint8List d = await platform.decrypt(
ct.bytes, key.bytes, Utils.enumToStr(algorithm)) ??
Uint8List(0);
decryptedData.add(d);
}
} else {
Uint8List d = await platform.decrypt(
cipherText.bytes, key.bytes, Utils.enumToStr(algorithm)) ??
Uint8List(0);
decryptedData.add(d);
}
return decryptedData.toBytes();
}
@override
Future<CipherText> encrypt(Uint8List data) async {
Uint8List dataToEncrypt;
CipherTextList cipherTextList = CipherTextList();
// If data is bigger than 32mB -> split in chunks
if (data.length > CipherTextList.chunkSize) {
int chunkNb = (data.length / CipherTextList.chunkSize).ceil();
for (var i = 0; i < chunkNb; i++) {
dataToEncrypt = i < (chunkNb - 1)
? data.sublist(i * CipherTextList.chunkSize, (i + 1) * CipherTextList.chunkSize)
: data.sublist(i * CipherTextList.chunkSize);
Uint8List c = await platform.encrypt(
dataToEncrypt,
key.bytes,
Utils.enumToStr(algorithm)
) ?? Uint8List(0);
cipherTextList.add(CipherText(c.sublist(0, 12), c.sublist(12, c.length - 16), c.sublist(c.length - 16, c.length))); // TODO: generify this
}
} else {
Uint8List c = await platform.encrypt(
data,
key.bytes,
Utils.enumToStr(algorithm)
) ?? Uint8List(0);
return CipherText(c.sublist(0, 12), c.sublist(12, c.length - 16), c.sublist(c.length - 16, c.length)); // TODO: generify this
}
return cipherTextList;
}
}

View File

@ -0,0 +1,41 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: exceptions.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 27/12/2021 23:28:31
// -----
// Copyright (c) 2021
class NativeCryptoException implements Exception {
String message;
NativeCryptoException(this.message);
}
class UtilsException extends NativeCryptoException {
UtilsException(message) : super(message);
}
class KeyException extends NativeCryptoException {
KeyException(message) : super(message);
}
class KeyDerivationException extends NativeCryptoException {
KeyDerivationException(message) : super(message);
}
class CipherInitException extends NativeCryptoException {
CipherInitException(message) : super(message);
}
class EncryptionException extends NativeCryptoException {
EncryptionException(message) : super(message);
}
class DecryptionException extends NativeCryptoException {
DecryptionException(message) : super(message);
}
class NotImplementedException extends NativeCryptoException {
NotImplementedException(message) : super(message);
}

View File

@ -0,0 +1,27 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: hasher.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 27/12/2021 22:06:29
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'platform.dart';
import 'utils.dart';
enum HashAlgorithm { sha256, sha384, sha512 }
abstract class Hasher {
/// Returns the standard algorithm name for this digest
HashAlgorithm get algorithm;
/// Hashes a message
Future<Uint8List> digest(Uint8List data) async {
Uint8List hash = (await platform.digest(data, Utils.enumToStr(algorithm))) ?? Uint8List(0);
return hash;
}
}

View File

@ -0,0 +1,15 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: sha256.dart
// Created Date: 17/12/2021 11:31:20
// Last Modified: 18/12/2021 12:09:33
// -----
// Copyright (c) 2021
import '../hasher.dart';
class SHA256 extends Hasher{
@override
HashAlgorithm get algorithm => HashAlgorithm.sha256;
}

View File

@ -0,0 +1,15 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: sha384.dart
// Created Date: 17/12/2021 11:31:53
// Last Modified: 18/12/2021 12:09:45
// -----
// Copyright (c) 2021
import '../hasher.dart';
class SHA384 extends Hasher{
@override
HashAlgorithm get algorithm => HashAlgorithm.sha384;
}

View File

@ -0,0 +1,15 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: sha512.dart
// Created Date: 17/12/2021 11:32:14
// Last Modified: 18/12/2021 12:09:58
// -----
// Copyright (c) 2021
import '../hasher.dart';
class SHA512 extends Hasher{
@override
HashAlgorithm get algorithm => HashAlgorithm.sha512;
}

View File

@ -0,0 +1,51 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: pbkdf2.dart
// Created Date: 17/12/2021 14:50:42
// Last Modified: 28/12/2021 13:38:50
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import '../exceptions.dart';
import '../hasher.dart';
import '../keyderivation.dart';
import '../keys/secret_key.dart';
import '../platform.dart';
import '../utils.dart';
class PBKDF2 extends KeyDerivation {
final int _keyBytesCount;
final int _iterations;
final HashAlgorithm _hash;
@override
KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2;
PBKDF2(
int keyBytesCount,
int iterations, {
HashAlgorithm algorithm = HashAlgorithm.sha256,
}) : _keyBytesCount = keyBytesCount,
_iterations = iterations,
_hash = algorithm;
@override
Future<SecretKey> derive({String? password, String? salt}) async {
if (password == null || salt == null) {
throw KeyDerivationException("Password or Salt can't be null!");
}
Uint8List derivation = (await platform.pbkdf2(
password,
salt,
_keyBytesCount,
_iterations,
Utils.enumToStr(_hash),
)) ?? Uint8List(0);
return SecretKey(derivation);
}
}

View File

@ -0,0 +1,20 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: key.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 28/12/2021 13:37:50
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'byte_array.dart';
/// A class representing a key.
class Key extends ByteArray {
Key(Uint8List bytes) : super(bytes);
Key.fromBase16(String encoded) : super.fromBase16(encoded);
Key.fromBase64(String encoded) : super.fromBase64(encoded);
Key.fromUtf8(String input) : super.fromUtf8(input);
}

View File

@ -0,0 +1,21 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: kdf.dart
// Created Date: 18/12/2021 11:56:43
// Last Modified: 28/12/2021 13:38:02
// -----
// Copyright (c) 2021
import './keys/secret_key.dart';
enum KdfAlgorithm { pbkdf2 }
/// Represents a Key Derivation Function
abstract class KeyDerivation {
/// Returns the standard algorithm name for this key derivation function
KdfAlgorithm get algorithm;
/// Derive key
Future<SecretKey> derive();
}

View File

@ -0,0 +1,36 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: secret_key.dart
// Created Date: 28/12/2021 13:36:54
// Last Modified: 28/12/2021 13:37:45
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/services.dart';
import '../exceptions.dart';
import '../key.dart';
import '../platform.dart';
/// A class representing a secret key.
/// A secret key is a key that is not accessible by anyone else.
/// It is used to encrypt and decrypt data.
class SecretKey extends Key {
SecretKey(Uint8List bytes) : super(bytes);
SecretKey.fromBase16(String encoded) : super.fromBase16(encoded);
SecretKey.fromBase64(String encoded) : super.fromBase64(encoded);
SecretKey.fromUtf8(String input) : super.fromUtf8(input);
static Future<SecretKey> fromSecureRandom(int bitsCount) async {
try {
Uint8List _key = (await platform.generateSecretKey(bitsCount)) ?? Uint8List(0);
return SecretKey(_key);
} on PlatformException catch (e) {
throw KeyException(e);
}
}
}

View File

@ -0,0 +1,12 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: platform.dart
// Created Date: 27/12/2021 22:03:58
// Last Modified: 27/12/2021 22:04:30
// -----
// Copyright (c) 2021
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
NativeCryptoPlatform platform = NativeCryptoPlatform.instance;

View File

@ -0,0 +1,83 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: utils.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 27/12/2021 22:04:07
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'cipher.dart';
import 'exceptions.dart';
import 'hasher.dart';
import 'keyderivation.dart';
class Utils {
/// Returns enum value to string, without the enum name
static String enumToStr(dynamic enumValue) {
return enumValue.toString().split('.').last;
}
/// Returns enum list as string list
static List<String> enumToList<T>(List<T> enumValues) {
List<String> _res = [];
for (T enumValue in enumValues) {
_res.add(enumToStr(enumValue));
}
return _res;
}
/// Returns enum from string
static T strToEnum<T>(String str, List<T> enumValues) {
for (T enumValue in enumValues) {
if (enumToStr(enumValue) == str) {
return enumValue;
}
}
throw UtilsException('Invalid enum value: $str');
}
/// Returns [HashAlgorithm] from his name.
static HashAlgorithm getHashAlgorithm(String algorithm) {
return strToEnum<HashAlgorithm>(algorithm.toLowerCase(), HashAlgorithm.values);
}
/// Returns all available [HashAlgorithm] as String list
static List<String> getAvailableHashAlgorithms() {
return enumToList<HashAlgorithm>(HashAlgorithm.values);
}
/// Returns [KdfAlgorithm] from his name.
static KdfAlgorithm getKdfAlgorithm(String algorithm) {
return strToEnum<KdfAlgorithm>(algorithm.toLowerCase(), KdfAlgorithm.values);
}
/// Returns all available [KdfAlgorithm] as String list
static List<String> getAvailableKdfAlgorithms() {
return enumToList<KdfAlgorithm>(KdfAlgorithm.values);
}
/// Returns [CipherAlgorithm] from his name.
static CipherAlgorithm getCipherAlgorithm(String algorithm) {
return strToEnum<CipherAlgorithm>(algorithm.toLowerCase(), CipherAlgorithm.values);
}
/// Returns all available [CipherAlgorithm] as String list
static List<String> getAvailableCipherAlgorithms() {
return enumToList<CipherAlgorithm>(CipherAlgorithm.values);
}
static Uint8List decodeHexString(String input) {
assert(input.length % 2 == 0, 'Input needs to be an even length.');
return Uint8List.fromList(
List.generate(
input.length ~/ 2,
(i) => int.parse(input.substring(i * 2, (i * 2) + 2), radix: 16),
).toList(),
);
}
}

View File

@ -1,6 +1,6 @@
name: native_crypto
description: Fast and secure cryptography for Flutter.
version: 0.0.7
version: 0.1.0
publish_to: 'none'
environment:
@ -11,9 +11,15 @@ dependencies:
flutter:
sdk: flutter
native_crypto_android:
path: ../native_crypto_android
native_crypto_ios:
path: ../native_crypto_ios
native_crypto_platform_interface:
path: ../native_crypto_platform_interface
dev_dependencies:
flutter_test:
sdk: flutter
@ -22,7 +28,7 @@ dev_dependencies:
flutter:
plugin:
platforms:
# android:
# default_package: native_crypto_android
android:
default_package: native_crypto_android
ios:
default_package: native_crypto_ios

View File

@ -1,23 +1 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
void main() {
const MethodChannel channel = MethodChannel('native_crypto');
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await NativeCrypto.platformVersion, '42');
});
}
// TODO