From 3381ff67b91e48f399228a677f6bb9369e11c632 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Tue, 28 Dec 2021 16:03:50 +0100 Subject: [PATCH] Add new public api and example --- native_crypto/CHANGELOG.md | 34 +++- native_crypto/LICENSE | 24 ++- native_crypto/README.md | 4 +- .../example/android/app/build.gradle | 2 +- .../android/app/src/main/AndroidManifest.xml | 1 + native_crypto/example/lib/home.dart | 68 ++++++++ native_crypto/example/lib/main.dart | 65 ++----- .../example/lib/pages/benchmark_page.dart | 137 +++++++++++++++ .../example/lib/pages/cipher_page.dart | 158 ++++++++++++++++++ native_crypto/example/lib/pages/kdf_page.dart | 130 ++++++++++++++ native_crypto/example/lib/session.dart | 24 +++ native_crypto/example/lib/utils.dart | 52 ++++++ native_crypto/example/lib/widgets/button.dart | 32 ++++ native_crypto/example/lib/widgets/output.dart | 61 +++++++ native_crypto/example/pubspec.yaml | 3 +- native_crypto/lib/native_crypto.dart | 40 +++-- native_crypto/lib/src/builder.dart | 12 ++ .../lib/src/builders/aes_builder.dart | 46 +++++ native_crypto/lib/src/byte_array.dart | 72 ++++++++ native_crypto/lib/src/cipher.dart | 37 ++++ native_crypto/lib/src/cipher_text.dart | 51 ++++++ native_crypto/lib/src/ciphers/aes.dart | 112 +++++++++++++ native_crypto/lib/src/exceptions.dart | 41 +++++ native_crypto/lib/src/hasher.dart | 27 +++ native_crypto/lib/src/hashers/sha256.dart | 15 ++ native_crypto/lib/src/hashers/sha384.dart | 15 ++ native_crypto/lib/src/hashers/sha512.dart | 15 ++ native_crypto/lib/src/kdf/pbkdf2.dart | 51 ++++++ native_crypto/lib/src/key.dart | 20 +++ native_crypto/lib/src/keyderivation.dart | 21 +++ native_crypto/lib/src/keys/secret_key.dart | 36 ++++ native_crypto/lib/src/platform.dart | 12 ++ native_crypto/lib/src/utils.dart | 83 +++++++++ native_crypto/pubspec.yaml | 12 +- native_crypto/test/native_crypto_test.dart | 24 +-- 35 files changed, 1439 insertions(+), 98 deletions(-) create mode 100644 native_crypto/example/lib/home.dart create mode 100644 native_crypto/example/lib/pages/benchmark_page.dart create mode 100644 native_crypto/example/lib/pages/cipher_page.dart create mode 100644 native_crypto/example/lib/pages/kdf_page.dart create mode 100644 native_crypto/example/lib/session.dart create mode 100644 native_crypto/example/lib/utils.dart create mode 100644 native_crypto/example/lib/widgets/button.dart create mode 100644 native_crypto/example/lib/widgets/output.dart create mode 100644 native_crypto/lib/src/builder.dart create mode 100644 native_crypto/lib/src/builders/aes_builder.dart create mode 100644 native_crypto/lib/src/byte_array.dart create mode 100644 native_crypto/lib/src/cipher.dart create mode 100644 native_crypto/lib/src/cipher_text.dart create mode 100644 native_crypto/lib/src/ciphers/aes.dart create mode 100644 native_crypto/lib/src/exceptions.dart create mode 100644 native_crypto/lib/src/hasher.dart create mode 100644 native_crypto/lib/src/hashers/sha256.dart create mode 100644 native_crypto/lib/src/hashers/sha384.dart create mode 100644 native_crypto/lib/src/hashers/sha512.dart create mode 100644 native_crypto/lib/src/kdf/pbkdf2.dart create mode 100644 native_crypto/lib/src/key.dart create mode 100644 native_crypto/lib/src/keyderivation.dart create mode 100644 native_crypto/lib/src/keys/secret_key.dart create mode 100644 native_crypto/lib/src/platform.dart create mode 100644 native_crypto/lib/src/utils.dart diff --git a/native_crypto/CHANGELOG.md b/native_crypto/CHANGELOG.md index 41cc7d8..d7c6738 100644 --- a/native_crypto/CHANGELOG.md +++ b/native_crypto/CHANGELOG.md @@ -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. diff --git a/native_crypto/LICENSE b/native_crypto/LICENSE index ba75c69..082d930 100644 --- a/native_crypto/LICENSE +++ b/native_crypto/LICENSE @@ -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. \ No newline at end of file diff --git a/native_crypto/README.md b/native_crypto/README.md index 1bf39c0..c16def3 100644 --- a/native_crypto/README.md +++ b/native_crypto/README.md @@ -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 diff --git a/native_crypto/example/android/app/build.gradle b/native_crypto/example/android/app/build.gradle index 803881a..15632c2 100644 --- a/native_crypto/example/android/app/build.gradle +++ b/native_crypto/example/android/app/build.gradle @@ -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 diff --git a/native_crypto/example/android/app/src/main/AndroidManifest.xml b/native_crypto/example/android/app/src/main/AndroidManifest.xml index c55f004..aa8a66a 100644 --- a/native_crypto/example/android/app/src/main/AndroidManifest.xml +++ b/native_crypto/example/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ _HomeState(); +} + +class _HomeState extends State { + int _currentIndex = 0; + final List _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', + ), + ], + ), + ); + } +} diff --git a/native_crypto/example/lib/main.dart b/native_crypto/example/lib/main.dart index 322f70f..65b60e0 100644 --- a/native_crypto/example/lib/main.dart +++ b/native_crypto/example/lib/main.dart @@ -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 createState() => _MyAppState(); -} - -class _MyAppState extends State { - String _platformVersion = 'Unknown'; - - @override - void initState() { - super.initState(); - initPlatformState(); - } - - // Platform messages are asynchronous, so we initialize in an async method. - Future 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()); } } diff --git a/native_crypto/example/lib/pages/benchmark_page.dart b/native_crypto/example/lib/pages/benchmark_page.dart new file mode 100644 index 0000000..bc708d9 --- /dev/null +++ b/native_crypto/example/lib/pages/benchmark_page.dart @@ -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 _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 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, + ], + ), + ), + ); + } +} diff --git a/native_crypto/example/lib/pages/cipher_page.dart b/native_crypto/example/lib/pages/cipher_page.dart new file mode 100644 index 0000000..8a6bc7c --- /dev/null +++ b/native_crypto/example/lib/pages/cipher_page.dart @@ -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 _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 _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, + ], + ), + ), + ); + } +} diff --git a/native_crypto/example/lib/pages/kdf_page.dart b/native_crypto/example/lib/pages/kdf_page.dart new file mode 100644 index 0000000..b60a454 --- /dev/null +++ b/native_crypto/example/lib/pages/kdf_page.dart @@ -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 _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 _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 _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, + ], + ), + ), + ); + } +} diff --git a/native_crypto/example/lib/session.dart b/native_crypto/example/lib/session.dart new file mode 100644 index 0000000..8c25499 --- /dev/null +++ b/native_crypto/example/lib/session.dart @@ -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((ref) => Session()); \ No newline at end of file diff --git a/native_crypto/example/lib/utils.dart b/native_crypto/example/lib/utils.dart new file mode 100644 index 0000000..874b778 --- /dev/null +++ b/native_crypto/example/lib/utils.dart @@ -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; + } +} diff --git a/native_crypto/example/lib/widgets/button.dart b/native_crypto/example/lib/widgets/button.dart new file mode 100644 index 0000000..30c28a5 --- /dev/null +++ b/native_crypto/example/lib/widgets/button.dart @@ -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), + ), + ); + } +} diff --git a/native_crypto/example/lib/widgets/output.dart b/native_crypto/example/lib/widgets/output.dart new file mode 100644 index 0000000..7e16b5f --- /dev/null +++ b/native_crypto/example/lib/widgets/output.dart @@ -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, + ); + } +} diff --git a/native_crypto/example/pubspec.yaml b/native_crypto/example/pubspec.yaml index e6e3ec3..b8fd86e 100644 --- a/native_crypto/example/pubspec.yaml +++ b/native_crypto/example/pubspec.yaml @@ -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 diff --git a/native_crypto/lib/native_crypto.dart b/native_crypto/lib/native_crypto.dart index 1036e3a..015dab2 100644 --- a/native_crypto/lib/native_crypto.dart +++ b/native_crypto/lib/native_crypto.dart @@ -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 .` 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 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 repositories = ["https://github.com/hugo-pcl/native-crypto-flutter", "https://git.pointcheval.fr/NativeCrypto/native-crypto-flutter"]; diff --git a/native_crypto/lib/src/builder.dart b/native_crypto/lib/src/builder.dart new file mode 100644 index 0000000..2ed8d17 --- /dev/null +++ b/native_crypto/lib/src/builder.dart @@ -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 { + Future build(); +} \ No newline at end of file diff --git a/native_crypto/lib/src/builders/aes_builder.dart b/native_crypto/lib/src/builders/aes_builder.dart new file mode 100644 index 0000000..8073490 --- /dev/null +++ b/native_crypto/lib/src/builders/aes_builder.dart @@ -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 { + SecretKey? _sk; + Future? _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 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); + } +} \ No newline at end of file diff --git a/native_crypto/lib/src/byte_array.dart b/native_crypto/lib/src/byte_array.dart new file mode 100644 index 0000000..8343a34 --- /dev/null +++ b/native_crypto/lib/src/byte_array.dart @@ -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; + } +} \ No newline at end of file diff --git a/native_crypto/lib/src/cipher.dart b/native_crypto/lib/src/cipher.dart new file mode 100644 index 0000000..fec80ca --- /dev/null +++ b/native_crypto/lib/src/cipher.dart @@ -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 encrypt(Uint8List data); + + /// Decrypts cipher text. + /// + /// Takes [CipherText] as parameter. + /// And returns plain text data as [Uint8List]. + Future decrypt(CipherText cipherText); +} \ No newline at end of file diff --git a/native_crypto/lib/src/cipher_text.dart b/native_crypto/lib/src/cipher_text.dart new file mode 100644 index 0000000..c4b53f2 --- /dev/null +++ b/native_crypto/lib/src/cipher_text.dart @@ -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 _list; + + CipherTextList() : _list = [], super(Uint8List(0), Uint8List(0), Uint8List(0)); + + void add(CipherText cipherText) { + _list.add(cipherText); + } + + get list => _list; +} \ No newline at end of file diff --git a/native_crypto/lib/src/ciphers/aes.dart b/native_crypto/lib/src/ciphers/aes.dart new file mode 100644 index 0000000..592a7ce --- /dev/null +++ b/native_crypto/lib/src/ciphers/aes.dart @@ -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 sizes = { + AESKeySize.bits128: 128, + AESKeySize.bits192: 192, + AESKeySize.bits256: 256, + }; + static final List 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> _supported = { + AESMode.gcm: [AESPadding.none], + }; + + if (!_supported[mode]!.contains(padding)) { + throw CipherInitException("Invalid padding!"); + } + } + + @override + Future 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 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; + } +} diff --git a/native_crypto/lib/src/exceptions.dart b/native_crypto/lib/src/exceptions.dart new file mode 100644 index 0000000..3a5d2ce --- /dev/null +++ b/native_crypto/lib/src/exceptions.dart @@ -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); +} diff --git a/native_crypto/lib/src/hasher.dart b/native_crypto/lib/src/hasher.dart new file mode 100644 index 0000000..bacf4bd --- /dev/null +++ b/native_crypto/lib/src/hasher.dart @@ -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 digest(Uint8List data) async { + Uint8List hash = (await platform.digest(data, Utils.enumToStr(algorithm))) ?? Uint8List(0); + + return hash; + } +} diff --git a/native_crypto/lib/src/hashers/sha256.dart b/native_crypto/lib/src/hashers/sha256.dart new file mode 100644 index 0000000..6c1284a --- /dev/null +++ b/native_crypto/lib/src/hashers/sha256.dart @@ -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; +} \ No newline at end of file diff --git a/native_crypto/lib/src/hashers/sha384.dart b/native_crypto/lib/src/hashers/sha384.dart new file mode 100644 index 0000000..b9f44dd --- /dev/null +++ b/native_crypto/lib/src/hashers/sha384.dart @@ -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; +} \ No newline at end of file diff --git a/native_crypto/lib/src/hashers/sha512.dart b/native_crypto/lib/src/hashers/sha512.dart new file mode 100644 index 0000000..d4f7d6b --- /dev/null +++ b/native_crypto/lib/src/hashers/sha512.dart @@ -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; +} \ No newline at end of file diff --git a/native_crypto/lib/src/kdf/pbkdf2.dart b/native_crypto/lib/src/kdf/pbkdf2.dart new file mode 100644 index 0000000..8d5a004 --- /dev/null +++ b/native_crypto/lib/src/kdf/pbkdf2.dart @@ -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 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); + } +} diff --git a/native_crypto/lib/src/key.dart b/native_crypto/lib/src/key.dart new file mode 100644 index 0000000..b86c1d2 --- /dev/null +++ b/native_crypto/lib/src/key.dart @@ -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); +} \ No newline at end of file diff --git a/native_crypto/lib/src/keyderivation.dart b/native_crypto/lib/src/keyderivation.dart new file mode 100644 index 0000000..279346e --- /dev/null +++ b/native_crypto/lib/src/keyderivation.dart @@ -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 derive(); +} \ No newline at end of file diff --git a/native_crypto/lib/src/keys/secret_key.dart b/native_crypto/lib/src/keys/secret_key.dart new file mode 100644 index 0000000..4579496 --- /dev/null +++ b/native_crypto/lib/src/keys/secret_key.dart @@ -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 fromSecureRandom(int bitsCount) async { + try { + Uint8List _key = (await platform.generateSecretKey(bitsCount)) ?? Uint8List(0); + + return SecretKey(_key); + } on PlatformException catch (e) { + throw KeyException(e); + } + } +} diff --git a/native_crypto/lib/src/platform.dart b/native_crypto/lib/src/platform.dart new file mode 100644 index 0000000..9834907 --- /dev/null +++ b/native_crypto/lib/src/platform.dart @@ -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; \ No newline at end of file diff --git a/native_crypto/lib/src/utils.dart b/native_crypto/lib/src/utils.dart new file mode 100644 index 0000000..df0dff2 --- /dev/null +++ b/native_crypto/lib/src/utils.dart @@ -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 enumToList(List enumValues) { + List _res = []; + for (T enumValue in enumValues) { + _res.add(enumToStr(enumValue)); + } + + return _res; + } + + /// Returns enum from string + static T strToEnum(String str, List 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(algorithm.toLowerCase(), HashAlgorithm.values); + } + + /// Returns all available [HashAlgorithm] as String list + static List getAvailableHashAlgorithms() { + return enumToList(HashAlgorithm.values); + } + + /// Returns [KdfAlgorithm] from his name. + static KdfAlgorithm getKdfAlgorithm(String algorithm) { + return strToEnum(algorithm.toLowerCase(), KdfAlgorithm.values); + } + + /// Returns all available [KdfAlgorithm] as String list + static List getAvailableKdfAlgorithms() { + return enumToList(KdfAlgorithm.values); + } + + /// Returns [CipherAlgorithm] from his name. + static CipherAlgorithm getCipherAlgorithm(String algorithm) { + return strToEnum(algorithm.toLowerCase(), CipherAlgorithm.values); + } + + /// Returns all available [CipherAlgorithm] as String list + static List getAvailableCipherAlgorithms() { + return enumToList(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(), + ); + } +} diff --git a/native_crypto/pubspec.yaml b/native_crypto/pubspec.yaml index 3baf2be..5a63d48 100644 --- a/native_crypto/pubspec.yaml +++ b/native_crypto/pubspec.yaml @@ -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 \ No newline at end of file diff --git a/native_crypto/test/native_crypto_test.dart b/native_crypto/test/native_crypto_test.dart index 5caceab..0ffdd02 100644 --- a/native_crypto/test/native_crypto_test.dart +++ b/native_crypto/test/native_crypto_test.dart @@ -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 \ No newline at end of file