Update example app with full tests
This commit is contained in:
parent
a77ef8df00
commit
e32c54d685
@ -1,10 +1,11 @@
|
|||||||
// Copyright (c) 2020
|
// Copyright (c) 2020
|
||||||
// Author: Hugo Pointcheval
|
// Author: Hugo Pointcheval
|
||||||
import 'dart:developer';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:native_crypto/native_crypto.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());
|
void main() => runApp(MyApp());
|
||||||
|
|
||||||
@ -14,163 +15,20 @@ class MyApp extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
class _MyAppState extends State<MyApp> {
|
||||||
final textController = TextEditingController();
|
int _currentIndex = 0;
|
||||||
final pwdController = TextEditingController();
|
final List<Widget> _children = [
|
||||||
|
HashKeyDerivationPage(),
|
||||||
String _output = 'none';
|
CipherPage(),
|
||||||
String _bench;
|
KemPage(),
|
||||||
|
BenchmarkPage(),
|
||||||
AESCipher aes;
|
];
|
||||||
CipherText cipherText;
|
|
||||||
Uint8List plainText;
|
|
||||||
SecretKey key;
|
|
||||||
|
|
||||||
void _generateKey() async {
|
|
||||||
var output;
|
|
||||||
try {
|
|
||||||
aes = await AESCipher.generate(
|
|
||||||
AESKeySize.bits256,
|
|
||||||
CipherParameters(
|
|
||||||
BlockCipherMode.CBC,
|
|
||||||
PlainTextPadding.PKCS5,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
output = 'Key generated. Length: ${aes.secretKey.encoded.length}';
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
output = e.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void onTabTapped(int index) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_output = output;
|
_currentIndex = index;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pbkdf2() async {
|
|
||||||
final password = pwdController.text.trim();
|
|
||||||
|
|
||||||
var output;
|
|
||||||
if (password.isEmpty) {
|
|
||||||
output = 'Password is empty';
|
|
||||||
} else {
|
|
||||||
PBKDF2 _pbkdf2 =
|
|
||||||
PBKDF2(keyLength: 32, iteration: 1000, hash: HashAlgorithm.SHA512);
|
|
||||||
await _pbkdf2.derive(password: password, salt: 'salty');
|
|
||||||
key = _pbkdf2.key;
|
|
||||||
output = 'Key successfully derived.';
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_output = output;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _encrypt() async {
|
|
||||||
final plainText = textController.text.trim();
|
|
||||||
|
|
||||||
var output;
|
|
||||||
if (plainText.isEmpty) {
|
|
||||||
output = 'Entry is empty';
|
|
||||||
} else {
|
|
||||||
var stringToBytes = TypeHelper().stringToBytes(plainText);
|
|
||||||
cipherText = await aes.encrypt(stringToBytes);
|
|
||||||
output = 'String successfully encrypted.';
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_output = output;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _alter() async {
|
|
||||||
var output;
|
|
||||||
if (cipherText == null || cipherText.bytes.isEmpty) {
|
|
||||||
output = 'Encrypt before altering payload!';
|
|
||||||
} else {
|
|
||||||
// Add 1 to the first byte
|
|
||||||
Uint8List _altered = cipherText.bytes;
|
|
||||||
_altered[0] += 1;
|
|
||||||
// Recreate cipher text with altered data
|
|
||||||
cipherText = AESCipherText(_altered, cipherText.iv);
|
|
||||||
output = 'Payload altered.';
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_output = output;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _decrypt() async {
|
|
||||||
var output;
|
|
||||||
if (cipherText == null || cipherText.bytes.isEmpty) {
|
|
||||||
output = 'Encrypt before decrypting!';
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
plainText = await aes.decrypt(cipherText);
|
|
||||||
var bytesToString = TypeHelper().bytesToString(plainText);
|
|
||||||
output = 'String successfully decrypted:\n\n$bytesToString';
|
|
||||||
} on DecryptionException catch (e) {
|
|
||||||
output = e.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_output = output;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _benchmark(int megabytes) async {
|
|
||||||
String output;
|
|
||||||
var bigFile = Uint8List(megabytes * 1000000);
|
|
||||||
|
|
||||||
var before = DateTime.now();
|
|
||||||
var encryptedBigFile = await aes.encrypt(bigFile);
|
|
||||||
var after = DateTime.now();
|
|
||||||
|
|
||||||
var benchmark =
|
|
||||||
after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
output = '$megabytes MB\nAES Encryption: $benchmark ms\n';
|
|
||||||
|
|
||||||
before = DateTime.now();
|
|
||||||
await aes.decrypt(encryptedBigFile);
|
|
||||||
after = DateTime.now();
|
|
||||||
|
|
||||||
benchmark = after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
output += 'AES Decryption: $benchmark ms\n\n';
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _testPerf({int megabytes}) async {
|
|
||||||
var output = '';
|
|
||||||
if (megabytes != null) {
|
|
||||||
output = await _benchmark(megabytes);
|
|
||||||
setState(() {
|
|
||||||
_output = output;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
_bench = 'Open the logcat!';
|
|
||||||
});
|
|
||||||
for (int i = 1; i <= 50; i += 10) {
|
|
||||||
var benchmark = await _benchmark(i);
|
|
||||||
log(benchmark, name: 'fr.pointcheval.native_crypto');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
// Generate AES instance on init.
|
|
||||||
_generateKey();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
// Clean up the controller when the widget is disposed.
|
|
||||||
textController.dispose();
|
|
||||||
pwdController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
@ -179,126 +37,33 @@ class _MyAppState extends State<MyApp> {
|
|||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: const Text('Native Crypto'),
|
title: const Text('Native Crypto'),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: _children[_currentIndex],
|
||||||
child: Padding(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 10, 10, 20),
|
selectedItemColor: Colors.blue,
|
||||||
child: Center(
|
unselectedItemColor: Colors.black,
|
||||||
child: Column(
|
showUnselectedLabels: true,
|
||||||
children: <Widget>[
|
onTap: onTabTapped, // new
|
||||||
TextField(
|
currentIndex: _currentIndex, // new
|
||||||
controller: pwdController,
|
items: [
|
||||||
decoration: InputDecoration(
|
BottomNavigationBarItem(
|
||||||
hintText: 'Test password',
|
icon: Icon(Icons.vpn_key),
|
||||||
|
label: 'Key',
|
||||||
),
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.lock),
|
||||||
|
label: 'Encryption',
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
BottomNavigationBarItem(
|
||||||
FlatButton(
|
icon: Icon(Icons.connect_without_contact),
|
||||||
onPressed: _pbkdf2,
|
label: 'KEM',
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Pbkdf2',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
SizedBox(height: 30),
|
|
||||||
TextField(
|
|
||||||
controller: textController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Text to encrypt.',
|
|
||||||
),
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.timer),
|
||||||
|
label: 'Benchmark',
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: _encrypt,
|
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Encrypt String',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: _alter,
|
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Alter encrypted payload',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: _decrypt,
|
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Decrypt String',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
// Output
|
|
||||||
Text(
|
|
||||||
_output,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: () {
|
|
||||||
_testPerf(megabytes: 1);
|
|
||||||
},
|
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Benchmark 1 MB',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: () {
|
|
||||||
_testPerf(megabytes: 10);
|
|
||||||
},
|
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Benchmark 10 MB',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: () {
|
|
||||||
_testPerf(megabytes: 50);
|
|
||||||
},
|
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Benchmark 50 MB',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: () {
|
|
||||||
_testPerf();
|
|
||||||
},
|
|
||||||
color: Colors.blue,
|
|
||||||
child: Text(
|
|
||||||
'Full benchmark',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
)),
|
|
||||||
|
|
||||||
(_bench != null && _bench.isNotEmpty)
|
|
||||||
? Text(_bench)
|
|
||||||
: Container(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains some useful functions.
|
|
||||||
class TypeHelper {
|
|
||||||
/// Returns bytes `Uint8List` from a `String`.
|
|
||||||
Uint8List stringToBytes(String source) {
|
|
||||||
var list = source.runes.toList();
|
|
||||||
var bytes = Uint8List.fromList(list);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a `String` from bytes `Uint8List`.
|
|
||||||
String bytesToString(Uint8List bytes) {
|
|
||||||
var string = String.fromCharCodes(bytes);
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
115
example/lib/pages/benchmarkPage.dart
Normal file
115
example/lib/pages/benchmarkPage.dart
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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 '../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 1/5/10/25/50MB\n");
|
||||||
|
List<int> testedSizes = [1, 5, 10, 25, 50];
|
||||||
|
|
||||||
|
var beforeBench = DateTime.now();
|
||||||
|
for (int size in testedSizes) {
|
||||||
|
var bigFile = Uint8List(size * 1000000);
|
||||||
|
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');
|
||||||
|
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\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');
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
205
example/lib/pages/cipherPage.dart
Normal file
205
example/lib/pages/cipherPage.dart
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// 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 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
|
||||||
|
Uint8List _altered = cipherText.bytes;
|
||||||
|
_altered[0] += 1;
|
||||||
|
// Recreate cipher text with altered data
|
||||||
|
cipherText = AESCipherText(_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 {
|
||||||
|
Uint8List payload = Uint8List.fromList(cipherText.iv + cipherText.bytes);
|
||||||
|
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);
|
||||||
|
Uint8List iv = payload.sublist(0, 16);
|
||||||
|
Uint8List bytes = payload.sublist(16);
|
||||||
|
cipherText = AESCipherText(bytes, iv);
|
||||||
|
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
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
180
example/lib/pages/hashKeyDerivationPage.dart
Normal file
180
example/lib/pages/hashKeyDerivationPage.dart
Normal 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
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
21
example/lib/pages/kemPage.dart
Normal file
21
example/lib/pages/kemPage.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2020
|
||||||
|
// Author: Hugo Pointcheval
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class KemPage extends StatefulWidget {
|
||||||
|
KemPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_KemPageState createState() => _KemPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _KemPageState extends State<KemPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: Center(
|
||||||
|
child: Text("Not implemented."),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
9
example/lib/session.dart
Normal file
9
example/lib/session.dart
Normal 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
30
example/lib/utils.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
24
example/lib/widgets/button.dart
Normal file
24
example/lib/widgets/button.dart
Normal 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
example/lib/widgets/output.dart
Normal file
50
example/lib/widgets/output.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2020
|
||||||
|
// Author: Hugo Pointcheval
|
||||||
|
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) {
|
||||||
|
textEditingController.text = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(String message) {
|
||||||
|
textEditingController.text += message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendln(String message) {
|
||||||
|
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 ? 50 : 5,
|
||||||
|
decoration: InputDecoration(border: OutlineInputBorder()),
|
||||||
|
controller: textEditingController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user