206 lines
6.1 KiB
Dart
206 lines
6.1 KiB
Dart
// Copyright (c) 2021
|
|
// Author: Hugo Pointcheval
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:native_crypto/native_crypto.dart';
|
|
|
|
import '../session.dart';
|
|
import '../utils.dart';
|
|
import '../widgets/button.dart';
|
|
import '../widgets/output.dart';
|
|
|
|
class CipherPage extends StatefulWidget {
|
|
const CipherPage({key}) : super(key: key);
|
|
|
|
@override
|
|
_CipherPageState createState() => _CipherPageState();
|
|
}
|
|
|
|
class _CipherPageState extends State<CipherPage> {
|
|
final Output keyContent = Output(
|
|
textEditingController: TextEditingController(),
|
|
);
|
|
final Output encryptionStatus = Output(
|
|
textEditingController: TextEditingController(),
|
|
);
|
|
final Output decryptionStatus = Output(
|
|
textEditingController: TextEditingController(),
|
|
);
|
|
final Output cipherExport = Output(
|
|
textEditingController: TextEditingController(),
|
|
large: true,
|
|
editable: true,
|
|
);
|
|
|
|
final TextEditingController _plainTextController = TextEditingController();
|
|
CipherText cipherText;
|
|
|
|
void _encrypt() async {
|
|
final plainText = _plainTextController.text.trim();
|
|
|
|
if (Session.secretKey == null || Session.secretKey.isEmpty) {
|
|
encryptionStatus
|
|
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
|
|
} else if (!Session.aesCipher.isInitialized) {
|
|
encryptionStatus.print(
|
|
'Cipher not initialized!\nGo in Key tab and generate or derive one.');
|
|
} else if (plainText.isEmpty) {
|
|
encryptionStatus.print('Entry is empty');
|
|
} else {
|
|
var stringToBytes = TypeHelper.stringToBytes(plainText);
|
|
cipherText = await Session.aesCipher.encrypt(stringToBytes);
|
|
encryptionStatus.print('String successfully encrypted.\n');
|
|
encryptionStatus.append("IV: " +
|
|
cipherText.iv.toString() +
|
|
"\nCipherText: " +
|
|
cipherText.bytes.toString());
|
|
}
|
|
}
|
|
|
|
void _alter() async {
|
|
if (cipherText == null || cipherText.bytes.isEmpty) {
|
|
decryptionStatus.print('Encrypt before altering CipherText!');
|
|
} else {
|
|
// Add 1 to the first byte
|
|
List<Uint8List> _altered = cipherText.bytes;
|
|
_altered[0][0] += 1;
|
|
// Recreate cipher text with altered data
|
|
cipherText = AESCipherText.from(_altered, cipherText.iv);
|
|
encryptionStatus.print('String successfully encrypted.\n');
|
|
encryptionStatus.append("IV: " +
|
|
cipherText.iv.toString() +
|
|
"\nCipherText: " +
|
|
cipherText.bytes.toString());
|
|
decryptionStatus.print('CipherText altered!\nDecryption will fail.');
|
|
}
|
|
}
|
|
|
|
void _decrypt() async {
|
|
if (Session.secretKey == null || Session.secretKey.isEmpty) {
|
|
decryptionStatus
|
|
.print('No SecretKey!\nGo in Key tab and generate or derive one.');
|
|
} else if (!Session.aesCipher.isInitialized) {
|
|
decryptionStatus.print(
|
|
'Cipher not initialized!\nGo in Key tab and generate or derive one.');
|
|
} else if (cipherText == null || cipherText.bytes.isEmpty) {
|
|
decryptionStatus.print('Encrypt before decrypting!');
|
|
} else {
|
|
try {
|
|
Uint8List plainText = await Session.aesCipher.decrypt(cipherText);
|
|
var bytesToString = TypeHelper.bytesToString(plainText);
|
|
decryptionStatus
|
|
.print('String successfully decrypted:\n\n$bytesToString');
|
|
} on DecryptionException catch (e) {
|
|
decryptionStatus.print(e.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _export() async {
|
|
if (cipherText == null) {
|
|
decryptionStatus.print('Encrypt data before export!');
|
|
} else {
|
|
// TODO: fix export format to support chunks !
|
|
Uint8List payload = cipherText.encode();
|
|
String data = TypeHelper.bytesToBase64(payload);
|
|
decryptionStatus.print('CipherText successfully exported');
|
|
cipherExport.print(data);
|
|
}
|
|
}
|
|
|
|
void _import() async {
|
|
final String data = cipherExport.read();
|
|
if (data.isEmpty) {
|
|
encryptionStatus.print('CipherText import failed');
|
|
} else {
|
|
Uint8List payload = TypeHelper.base64ToBytes(data);
|
|
cipherText = AESCipherText.empty();
|
|
cipherText.decode(payload);
|
|
encryptionStatus.print('CipherText successfully imported\n');
|
|
encryptionStatus.append("IV: " +
|
|
cipherText.iv.toString() +
|
|
"\nCipherText: " +
|
|
cipherText.bytes.toString());
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
if (Session.secretKey != null) {
|
|
keyContent.print(Session.secretKey.encoded.toString());
|
|
Session.aesCipher = AESCipher(
|
|
Session.secretKey,
|
|
CipherParameters(
|
|
BlockCipherMode.CBC,
|
|
PlainTextPadding.PKCS5,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
_plainTextController.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Column(
|
|
children: [
|
|
Align(
|
|
child: Text("Secret Key"),
|
|
alignment: Alignment.centerLeft,
|
|
),
|
|
keyContent,
|
|
TextField(
|
|
controller: _plainTextController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Plain text',
|
|
),
|
|
),
|
|
Button(
|
|
onPressed: _encrypt,
|
|
label: "Encrypt",
|
|
),
|
|
encryptionStatus,
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
Button(
|
|
onPressed: _alter,
|
|
label: "Alter cipher",
|
|
),
|
|
Button(
|
|
onPressed: _decrypt,
|
|
label: "Decrypt",
|
|
),
|
|
],
|
|
),
|
|
decryptionStatus,
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
Button(
|
|
onPressed: _export,
|
|
label: "Export cipher",
|
|
),
|
|
Button(
|
|
onPressed: _import,
|
|
label: "Import cipher",
|
|
),
|
|
],
|
|
),
|
|
cipherExport
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|