304 lines
8.7 KiB
Dart
304 lines
8.7 KiB
Dart
// Copyright (c) 2020
|
|
// Author: Hugo Pointcheval
|
|
import 'dart:developer';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:native_crypto/symmetric_crypto.dart';
|
|
import 'package:native_crypto/exceptions.dart';
|
|
|
|
void main() => runApp(MyApp());
|
|
|
|
class MyApp extends StatefulWidget {
|
|
@override
|
|
_MyAppState createState() => _MyAppState();
|
|
}
|
|
|
|
class _MyAppState extends State<MyApp> {
|
|
final textController = TextEditingController();
|
|
final pwdController = TextEditingController();
|
|
|
|
String _output = 'none';
|
|
String _bench;
|
|
|
|
AES aes = AES();
|
|
List<Uint8List> encryptedPayload;
|
|
Uint8List decryptedPayload;
|
|
Uint8List key;
|
|
|
|
void _generateKey() async {
|
|
// You can also generate key before creating aes object.
|
|
// Uint8List aeskey = await KeyGenerator().secretKey(keySize: KeySize.bits256);
|
|
// AES aes = AES(key: aeskey);
|
|
var output;
|
|
try {
|
|
await aes.init(KeySize.bits256);
|
|
output = 'Key generated. Length: ${aes.key.length}';
|
|
} catch (e) {
|
|
// PlatformException or KeyException, both have message property.
|
|
output = e.message;
|
|
}
|
|
|
|
setState(() {
|
|
_output = output;
|
|
});
|
|
}
|
|
|
|
void _pbkdf2() async {
|
|
final password = pwdController.text.trim();
|
|
|
|
var output;
|
|
if (password.isEmpty) {
|
|
output = 'Password is empty';
|
|
} else {
|
|
key = await KeyGenerator().pbkdf2(password, 'salt');
|
|
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);
|
|
// You can also pass a specific key.
|
|
// encryptedPayload = await AES().encrypt(stringToBytes, key: aeskey);
|
|
encryptedPayload = await aes.encrypt(stringToBytes, key: key?? null);
|
|
output = 'String successfully encrypted.';
|
|
}
|
|
setState(() {
|
|
_output = output;
|
|
});
|
|
}
|
|
|
|
void _alter() async {
|
|
var output;
|
|
if (encryptedPayload == null || encryptedPayload[0].isEmpty) {
|
|
output = 'Encrypt before altering payload!';
|
|
} else {
|
|
// Add 1 to the first byte
|
|
encryptedPayload[0][0] += 1;
|
|
output = 'Payload altered.';
|
|
}
|
|
setState(() {
|
|
_output = output;
|
|
});
|
|
}
|
|
|
|
void _decrypt() async {
|
|
var output;
|
|
if (encryptedPayload == null || encryptedPayload[0].isEmpty) {
|
|
output = 'Encrypt before decrypting!';
|
|
} else {
|
|
// You can also pass a specific key.
|
|
// decryptedPayload = await AES().decrypt(encryptedPayload, key: aeskey);
|
|
try {
|
|
decryptedPayload = await aes.decrypt(encryptedPayload, key: key?? null);
|
|
var bytesToString = TypeHelper().bytesToString(decryptedPayload);
|
|
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
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
appBar: AppBar(
|
|
centerTitle: true,
|
|
title: const Text('Native Crypto'),
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 10, 10, 20),
|
|
child: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
TextField(
|
|
controller: pwdController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Test password',
|
|
),
|
|
),
|
|
SizedBox(height: 20),
|
|
FlatButton(
|
|
onPressed: _pbkdf2,
|
|
color: Colors.blue,
|
|
child: Text(
|
|
'Pbkdf2',
|
|
style: TextStyle(color: Colors.white),
|
|
)),
|
|
SizedBox(height: 30),
|
|
TextField(
|
|
controller: textController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Text to encrypt.',
|
|
),
|
|
),
|
|
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;
|
|
}
|
|
}
|