feat: rework bytearray and memory optimization, simplify API

This commit is contained in:
Hugo Pointcheval 2022-05-26 20:42:53 +02:00
parent 6939a8df7e
commit 48ebabb54c
Signed by: hugo
GPG Key ID: A9E8E9615379254F
28 changed files with 485 additions and 407 deletions

View File

@ -3,7 +3,7 @@
// -----
// File: benchmark_page.dart
// Created Date: 28/12/2021 15:12:39
// Last Modified: 25/05/2022 17:16:12
// Last Modified: 26/05/2022 20:19:28
// -----
// Copyright (c) 2021
@ -40,7 +40,7 @@ class BenchmarkPage extends ConsumerWidget {
return;
}
List<int> testedSizes = [2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50];
List<int> testedSizes = [2, 4, 8, 16, 32, 64, 128, 256];
int multiplier = pow(2, 20).toInt(); // MiB
benchmarkStatus.print("[Benchmark] Sizes: ${testedSizes.join('/')}MiB\n");
@ -86,7 +86,7 @@ class BenchmarkPage extends ConsumerWidget {
if (usePc) {
pc.decrypt(encryptedBigFile as Uint8List, state.secretKey.bytes);
} else {
await cipher.decrypt(encryptedBigFile as CipherText);
await cipher.decrypt(encryptedBigFile as CipherTextWrapper);
}
after = DateTime.now();
benchmark =
@ -105,7 +105,7 @@ class BenchmarkPage extends ConsumerWidget {
.appendln('[Benchmark] Finished: ${sum}MiB in $benchmark ms');
benchmarkStatus.appendln('[Benchmark] Check the console for csv data');
benchmarkStatus.appendln(csv);
print(csv);
debugPrint(csv);
}
void _clear() {
@ -135,7 +135,7 @@ class BenchmarkPage extends ConsumerWidget {
}
keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm);
AES cipher = AES(state.secretKey);
return SingleChildScrollView(
child: Padding(

View File

@ -3,7 +3,7 @@
// -----
// File: cipher_page.dart
// Created Date: 28/12/2021 13:33:15
// Last Modified: 25/05/2022 10:49:30
// Last Modified: 26/05/2022 20:39:37
// -----
// Copyright (c) 2021
@ -12,10 +12,10 @@ 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/native_crypto_ext.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart';
// ignore: must_be_immutable
@ -26,8 +26,8 @@ class CipherPage extends ConsumerWidget {
final Output encryptionStatus = Output();
final Output decryptionStatus = Output();
final TextEditingController _plainTextController = TextEditingController();
CipherText? cipherText;
final TextEditingController _plainTextController = TextEditingController()..text = 'PlainText';
CipherTextWrapper? cipherText;
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
Session state = ref.read(sessionProvider.state).state;
@ -41,13 +41,10 @@ class CipherPage extends ConsumerWidget {
} 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());
encryptionStatus.print('String successfully encrypted:\n');
CipherText unwrap = cipherText!.unwrap<CipherText>();
encryptionStatus.append(unwrap.base16);
}
}
@ -56,17 +53,19 @@ class CipherPage extends ConsumerWidget {
decryptionStatus.print('Encrypt before altering CipherText!');
} else {
// Add 1 to the first byte
Uint8List _altered = cipherText!.data;
Uint8List _altered = cipherText!.unwrap<CipherText>().bytes;
_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());
cipherText = CipherTextWrapper.fromBytes(
_altered,
12,
_altered.length - 28,
16,
);
encryptionStatus.print('String successfully encrypted:\n');
CipherText unwrap = cipherText!.unwrap();
encryptionStatus.appendln(unwrap.base16);
decryptionStatus.print('CipherText altered!\nDecryption will fail.');
}
}
@ -114,7 +113,7 @@ class CipherPage extends ConsumerWidget {
}
keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm);
AES cipher = AES(state.secretKey);
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),

View File

@ -3,7 +3,7 @@
// -----
// File: kdf_page.dart
// Created Date: 28/12/2021 13:40:34
// Last Modified: 23/05/2022 22:49:06
// Last Modified: 26/05/2022 20:30:31
// -----
// Copyright (c) 2021
@ -12,10 +12,10 @@ 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/native_crypto_ext.dart';
import 'package:native_crypto_example/widgets/button.dart';
import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart';
class KdfPage extends ConsumerWidget {
@ -26,8 +26,8 @@ class KdfPage extends ConsumerWidget {
final Output pbkdf2Status = Output();
final Output hashStatus = Output(large: true);
final TextEditingController _pwdTextController = TextEditingController();
final TextEditingController _messageTextController = TextEditingController();
final TextEditingController _pwdTextController = TextEditingController()..text = 'Password';
final TextEditingController _messageTextController = TextEditingController()..text = 'Message';
Future<void> _generate(WidgetRef ref) async {
Session state = ref.read(sessionProvider.state).state;
@ -66,7 +66,7 @@ class KdfPage extends ConsumerWidget {
} else {
Uint8List hash = await hasher.digest(message.toBytes());
hashStatus.print(
'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.hex)}');
'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.base16)}');
}
}

View File

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

View File

@ -0,0 +1,11 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: native_crypto_ext.dart
// Created Date: 26/05/2022 19:36:54
// Last Modified: 26/05/2022 19:38:44
// -----
// Copyright (c) 2022
export 'src/utils/encoding.dart';
export 'src/utils/extensions.dart';

View File

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

View File

@ -3,8 +3,8 @@
// -----
// File: builders.dart
// Created Date: 23/05/2022 22:56:03
// Last Modified: 23/05/2022 22:56:12
// Last Modified: 26/05/2022 19:22:19
// -----
// Copyright (c) 2022
export 'aes_builder.dart';
export 'decryption_builder.dart';

View File

@ -0,0 +1,46 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: decryption_builder.dart
// Created Date: 26/05/2022 19:07:52
// Last Modified: 26/05/2022 19:21:00
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
import 'package:native_crypto/src/interfaces/cipher.dart';
class DecryptionBuilder extends StatelessWidget {
final Cipher cipher;
final CipherTextWrapper data;
final Widget Function(BuildContext context) onLoading;
final Widget Function(BuildContext context, Object error) onError;
final Widget Function(BuildContext context, Uint8List plainText) onSuccess;
const DecryptionBuilder({
super.key,
required this.cipher,
required this.data,
required this.onLoading,
required this.onError,
required this.onSuccess,
});
@override
Widget build(BuildContext context) {
return FutureBuilder<Uint8List>(
future: cipher.decrypt(data),
builder: (context, snapshot) {
if (snapshot.hasData) {
return onSuccess(context, snapshot.data!);
} else if (snapshot.hasError) {
return onError(context, snapshot.error!);
}
return onLoading(context);
},
);
}
}

View File

@ -3,7 +3,7 @@
// -----
// File: aes.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 25/05/2022 21:17:10
// Last Modified: 26/05/2022 19:43:22
// -----
// Copyright (c) 2022
@ -13,88 +13,120 @@ import 'package:native_crypto/src/ciphers/aes/aes_key_size.dart';
import 'package:native_crypto/src/ciphers/aes/aes_mode.dart';
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
import 'package:native_crypto/src/core/cipher_text.dart';
import 'package:native_crypto/src/core/cipher_text_list.dart';
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
import 'package:native_crypto/src/interfaces/cipher.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/platform.dart';
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
import 'package:native_crypto/src/utils/extensions.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
export 'package:native_crypto/src/ciphers/aes/aes_key_size.dart';
export 'package:native_crypto/src/ciphers/aes/aes_mode.dart';
export 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
/// An AES cipher.
///
/// [AES] is a [Cipher] that can be used to encrypt or decrypt data.
class AES implements Cipher {
final SecretKey key;
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 (!AESKeySize.supportedSizes.contains(key.bytes.length * 8)) {
throw const CipherInitException(
message: 'Invalid key length!',
code: 'invalid_key_length',
AES(SecretKey key, [this.mode = AESMode.gcm, this.padding = AESPadding.none])
: _key = key {
if (!AESKeySize.supportedSizes.contains(key.bitLength)) {
throw NativeCryptoException(
message: 'Invalid key size! '
'Expected: ${AESKeySize.supportedSizes.join(', ')} bits',
code: NativeCryptoExceptionCode.invalid_key_length.code,
);
}
final Map<AESMode, List<AESPadding>> _supported = {
AESMode.gcm: [AESPadding.none],
};
if (!_supported[mode]!.contains(padding)) {
throw const CipherInitException(
message: 'Invalid padding!',
code: 'invalid_padding',
if (!mode.supportedPaddings.contains(padding)) {
throw NativeCryptoException(
message: 'Invalid padding! '
'Expected: ${mode.supportedPaddings.join(', ')}',
code: NativeCryptoExceptionCode.invalid_padding.code,
);
}
}
Future<Uint8List> _decrypt(CipherText cipherText) async {
return await platform.decryptAsList(
[cipherText.iv, cipherText.payload],
key.bytes,
algorithm.name,
) ??
Uint8List(0);
}
Future<CipherText> _encrypt(Uint8List data) async {
final List<Uint8List> cipherText =
await platform.encryptAsList(data, key.bytes, algorithm.name) ??
List.empty();
return CipherText.fromPairIvAndBytes(
cipherText,
dataLength: cipherText.last.length - 16,
tagLength: 16,
Future<Uint8List> _decrypt(CipherText cipherText,
{int chunkCount = 0,}) async {
final Uint8List? decrypted = await platform.decrypt(
cipherText.bytes,
_key.bytes,
algorithm.name,
);
if (decrypted.isNull) {
throw NativeCryptoException(
message: 'Platform returned null when decrypting chunk #$chunkCount',
code: NativeCryptoExceptionCode.platform_returned_null.code,
);
} else if (decrypted!.isEmpty) {
throw NativeCryptoException(
message: 'Platform returned no data when decrypting chunk #$chunkCount',
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
);
} else {
return decrypted;
}
}
Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async {
final Uint8List? encrypted = await platform.encrypt(
data,
_key.bytes,
algorithm.name,
);
if (encrypted.isNull) {
throw NativeCryptoException(
message: 'Platform returned null when encrypting chunk #$chunkCount',
code: NativeCryptoExceptionCode.platform_returned_null.code,
);
} else if (encrypted!.isEmpty) {
throw NativeCryptoException(
message: 'Platform returned no data when encrypting chunk #$chunkCount',
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
);
} else {
return CipherText.fromBytes(
12,
encrypted.length - 28,
16,
CipherAlgorithm.aes,
encrypted,
);
}
}
@override
Future<Uint8List> decrypt(CipherText cipherText) async {
Future<Uint8List> decrypt(CipherTextWrapper cipherText) async {
final BytesBuilder decryptedData = BytesBuilder(copy: false);
if (cipherText is CipherTextList) {
for (final CipherText ct in cipherText.list) {
decryptedData.add(await _decrypt(ct));
if (cipherText.isList) {
int chunkCount = 0;
for (final CipherText chunk in cipherText.list) {
decryptedData.add(await _decrypt(chunk, chunkCount: chunkCount++));
}
} else {
decryptedData.add(await _decrypt(cipherText));
decryptedData.add(await _decrypt(cipherText.single));
}
return decryptedData.toBytes();
}
@override
Future<CipherText> encrypt(Uint8List data) async {
Future<CipherTextWrapper> encrypt(Uint8List data) async {
CipherTextWrapper cipherTextWrapper;
Uint8List dataToEncrypt;
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();
final CipherTextList cipherTextList = CipherTextList();
if (data.length > Cipher.bytesCountPerChunk) {
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();
if (chunkNb > 1) {
cipherTextWrapper = CipherTextWrapper.empty();
for (var i = 0; i < chunkNb; i++) {
dataToEncrypt = i < (chunkNb - 1)
? data.sublist(
@ -102,11 +134,12 @@ class AES implements Cipher {
(i + 1) * Cipher.bytesCountPerChunk,
)
: data.sublist(i * Cipher.bytesCountPerChunk);
cipherTextList.add(await _encrypt(dataToEncrypt));
cipherTextWrapper.add(await _encrypt(dataToEncrypt, chunkCount: i));
}
} else {
return _encrypt(data);
cipherTextWrapper = CipherTextWrapper.single(await _encrypt(data));
}
return cipherTextList;
return cipherTextWrapper;
}
}

View File

@ -3,7 +3,7 @@
// -----
// File: aes_key_size.dart
// Created Date: 23/05/2022 22:10:07
// Last Modified: 23/05/2022 22:33:32
// Last Modified: 26/05/2022 18:45:01
// -----
// Copyright (c) 2022
@ -13,9 +13,13 @@ enum AESKeySize {
bits192(192),
bits256(256);
/// Returns the number of bits supported by an [AESKeySize].
static final List<int> supportedSizes = [128, 192, 256];
/// Returns the number of bits in this [AESKeySize].
final int bits;
/// Returns the number of bytes in this [AESKeySize].
int get bytes => bits ~/ 8;
const AESKeySize(this.bits);

View File

@ -3,9 +3,18 @@
// -----
// File: aes_mode.dart
// Created Date: 23/05/2022 22:09:16
// Last Modified: 25/05/2022 09:23:54
// Last Modified: 26/05/2022 18:41:31
// -----
// Copyright (c) 2022
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
/// Defines the AES modes of operation.
enum AESMode { gcm }
enum AESMode {
gcm([AESPadding.none]);
/// Returns the list of supported [AESPadding] for this [AESMode].
final List<AESPadding> supportedPaddings;
const AESMode(this.supportedPaddings);
}

View File

@ -3,49 +3,78 @@
// -----
// File: cipher_text.dart
// Created Date: 16/12/2021 16:59:53
// Last Modified: 26/05/2022 16:22:49
// Last Modified: 26/05/2022 19:43:57
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
import 'package:native_crypto/src/interfaces/byte_array.dart';
import 'package:native_crypto/src/interfaces/cipher.dart';
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
import 'package:native_crypto/src/utils/extensions.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
/// Represents a cipher text in Native Crypto.
/// Represents a cipher text in NativeCrypto.
///
/// It is represented as a [List] of [Uint8List] like:
/// [CipherText] is a [ByteArray] that can be used to store encrypted data.
/// It is represented like:
/// ```txt
/// [[NONCE], [MESSAGE + TAG]]
/// [IV + MESSAGE + TAG]
/// ```
/// where:
/// - `[NONCE]` is a [Uint8List] of length [CipherText.ivLength]
/// - `[MESSAGE + TAG]` is a [Uint8List] of length [CipherText.dataLength]
/// - IV's length is [CipherText.ivLength] bytes.
/// - MESSAGE's length is [CipherText.messageLength] bytes.
/// - TAG's length is [CipherText.tagLength] bytes.
///
/// To
///
/// So accessing just the Message or just the Tag is costly and should be
/// done only when needed.
class CipherText {
/// Check [CipherTextWrapper] for more information.
class CipherText extends ByteArray {
final int _ivLength;
final int _messageLength;
final int _tagLength;
final CipherAlgorithm? _cipherAlgorithm;
final Uint8List? _iv;
final Uint8List? _data; // Contains the message + tag (if any)
CipherText._(
const CipherText._(
this._ivLength,
this._messageLength,
this._tagLength,
this._cipherAlgorithm,
this._iv,
this._data,
super.bytes,
);
factory CipherText.fromBytes(
int ivLength,
int messageLength,
int tagLength,
CipherAlgorithm? cipherAlgorithm,
Uint8List bytes,
) {
if (bytes.length != ivLength + messageLength + tagLength) {
throw NativeCryptoException(
message: 'Invalid cipher text length! '
'Expected: ${ivLength + messageLength + tagLength} bytes',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
if (messageLength > Cipher.bytesCountPerChunk) {
throw NativeCryptoException(
message: 'Cipher text is too big! Consider using chunks.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
return CipherText._(
ivLength,
messageLength,
tagLength,
cipherAlgorithm,
bytes,
);
}
/// Gets the [CipherAlgorithm] used to encrypt the [CipherText].
CipherAlgorithm get cipherAlgorithm {
if (_cipherAlgorithm.isNotNull) {
@ -58,89 +87,12 @@ class CipherText {
}
}
/// Gets the [Uint8List] of the [CipherText]'s IV.
Uint8List get iv {
if (_iv.isNotNull) {
return _iv!;
} else {
throw NativeCryptoException(
message: 'IV is not specified',
code: NativeCryptoExceptionCode.invalid_data.code,
);
}
}
/// Gets the length of the [CipherText]'s IV.
int get ivLength => _ivLength;
/// Gets the [Uint8List] of the [CipherText]'s data.
Uint8List get data {
if (_data.isNotNull) {
return _data!;
} else {
throw NativeCryptoException(
message: 'Data is not specified',
code: NativeCryptoExceptionCode.invalid_data.code,
);
}
}
/// Gets the length of the [CipherText]'s Message.
int get messageLength => _messageLength;
/// Gets the length of the [CipherText]'s data.
int get dataLength => _messageLength + _tagLength;
// CipherText.fromBytes(
// Uint8List bytes, {
// required int ivLength,
// required int dataLength,
// int tagLength = 0,
// }) : _ivLength = ivLength,
// _dataLength = dataLength,
// _tagLength = tagLength,
// _iv = bytes.sublist(0, ivLength),
// super(bytes.sublist(ivLength, bytes.length - tagLength));
// const CipherText.fromIvAndBytes(
// Uint8List iv,
// super.data, {
// required int dataLength,
// int tagLength = 0,
// }) : _ivLength = iv.length,
// _dataLength = dataLength,
// _tagLength = tagLength,
// _iv = iv;
// CipherText.fromPairIvAndBytes(
// List<Uint8List> pair, {
// required int dataLength,
// int tagLength = 0,
// }) : _ivLength = pair.first.length,
// _dataLength = dataLength,
// _tagLength = tagLength,
// _iv = pair.first,
// super(pair.last);
// /// Gets the CipherText IV.
// Uint8List get iv => _iv;
// /// Gets the CipherText data.
// Uint8List get data => _tagLength > 0
// ? bytes.sublist(0, _dataLength - _tagLength)
// : bytes;
// /// Gets the CipherText tag.
// Uint8List get tag => _tagLength > 0
// ? bytes.sublist(_dataLength - _tagLength, _dataLength)
// : Uint8List(0);
// /// Gets the CipherText data and tag.
// Uint8List get payload => bytes;
// /// 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;
/// Gets the length of the [CipherText]'s Tag.
int get tagLength => _tagLength;
}

View File

@ -1,26 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: cipher_text_list.dart
// Created Date: 23/05/2022 22:59:02
// Last Modified: 24/05/2022 20:18:26
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:native_crypto/src/core/cipher_text.dart';
class CipherTextList extends CipherText {
final List<CipherText> _list;
CipherTextList()
: _list = [],
super(Uint8List(0), Uint8List(0), Uint8List(0));
void add(CipherText cipherText) {
_list.add(cipherText);
}
List<CipherText> get list => _list;
}

View File

@ -3,7 +3,7 @@
// -----
// File: cipher_text_wrapper.dart
// Created Date: 26/05/2022 14:27:32
// Last Modified: 26/05/2022 15:53:46
// Last Modified: 26/05/2022 20:32:38
// -----
// Copyright (c) 2022
@ -12,31 +12,85 @@ import 'dart:typed_data';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto/src/utils/extensions.dart';
/// Wrapper for [CipherText]
///
/// Typically, this object is the result of an encryption operation.
/// For decryption you have to build this before using it.
class CipherTextWrapper {
final CipherText? _single;
final List<CipherText>? _list;
CipherTextWrapper._(this._single, this._list);
/// Creates a [CipherTextWrapper] from a [CipherText].
factory CipherTextWrapper.single(CipherText cipherText) =>
CipherTextWrapper._(cipherText, null);
/// Creates a [CipherTextWrapper] from a [List] of [CipherText].
factory CipherTextWrapper.list(List<CipherText> cipherTexts) =>
CipherTextWrapper._(null, cipherTexts);
/// Creates an empty [List] in a [CipherTextWrapper].
///
/// This is useful when you want to create a [CipherTextWrapper] then
/// fill it with data.
factory CipherTextWrapper.empty() => CipherTextWrapper._(null, []);
/// Creates a [CipherTextWrapper] from a [Uint8List].
///
/// This is a convenience method to create a [CipherTextWrapper]
/// from a [Uint8List]. It tries to detect if the [Uint8List] is a
/// single [CipherText] or a list of [CipherText].
///
/// You can customize the chunk size by passing a [chunkSize] parameter.
/// The default chunk size is [Cipher.bytesCountPerChunk].
///
/// Throw an [NativeCryptoExceptionCode] with
/// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is
/// not a valid [CipherText] or a [List] of [CipherText].
factory CipherTextWrapper.fromBytes(
// Uint8List bytes, {
// required int ivLength,
// required int dataLength,
// int tagLength = 0,
// int? chunkSize,
// }
) {
// TODO(hpcl): implement fromBytes
throw UnimplementedError();
Uint8List bytes,
int ivLength,
int messageLength,
int tagLength, {
CipherAlgorithm? cipherAlgorithm,
int? chunkSize,
}) {
chunkSize ??= Cipher.bytesCountPerChunk;
Cipher.bytesCountPerChunk = chunkSize;
if (bytes.length <= chunkSize) {
return CipherTextWrapper.single(
CipherText.fromBytes(
ivLength,
messageLength,
tagLength,
cipherAlgorithm,
bytes,
),
);
} else {
final cipherTexts = <CipherText>[];
for (var i = 0; i < bytes.length; i += chunkSize) {
final chunk = bytes.sublist(i, i + chunkSize);
cipherTexts.add(
CipherText.fromBytes(
ivLength,
messageLength,
tagLength,
cipherAlgorithm,
chunk,
),
);
}
return CipherTextWrapper.list(cipherTexts);
}
}
/// Checks if the [CipherText] is a single [CipherText].
bool get isSingle => _single.isNotNull;
/// Checks if the [CipherText] is a [List] of [CipherText].
bool get isList => _list.isNotNull;
/// Gets the [CipherText] if it's a single one.
@ -70,7 +124,7 @@ class CipherTextWrapper {
}
/// Gets the raw [Uint8List] of the [CipherText] or [List] of [CipherText].
Uint8List get raw {
Uint8List get bytes {
if (isSingle) {
return single.bytes;
} else {
@ -78,6 +132,9 @@ class CipherTextWrapper {
}
}
/// Gets the number of parts of the [CipherText] or [List] of [CipherText].
///
/// Check [Cipher.bytesCountPerChunk] for more information.
int get chunkCount {
_single.isNull;
if (_single.isNotNull) {
@ -86,4 +143,41 @@ class CipherTextWrapper {
return _list?.length ?? 0;
}
}
/// Gets the [CipherText] or the [List] of [CipherText].
///
/// Throws [NativeCryptoException] with
/// [NativeCryptoExceptionCode.invalid_data] if it's not a single or a list or
/// if [T] is not [CipherText] or [List] of [CipherText].
T unwrap<T>() {
if (isSingle && T == CipherText) {
return single as T;
} else if (isList && T == List<CipherText>) {
return list as T;
} else {
final String type =
isSingle ? 'CipherText' : (isList ? 'List<CipherText>' : 'unknown');
throw NativeCryptoException(
message: 'CipherTextWrapper is not a $T but a $type, '
'you should use unwrap<$type>()',
code: NativeCryptoExceptionCode.invalid_data.code,
);
}
}
void add(CipherText cipherText) {
if (isSingle) {
throw NativeCryptoException(
message: 'CipherTextWrapper is already single',
code: NativeCryptoExceptionCode.invalid_data.code,
);
} else if (isList) {
_list!.add(cipherText);
} else {
throw NativeCryptoException(
message: 'CipherTextWrapper is not single or list',
code: NativeCryptoExceptionCode.invalid_data.code,
);
}
}
}

View File

@ -3,9 +3,9 @@
// -----
// File: core.dart
// Created Date: 23/05/2022 23:05:26
// Last Modified: 25/05/2022 10:44:32
// Last Modified: 26/05/2022 17:10:25
// -----
// Copyright (c) 2022
export 'cipher_text.dart';
export 'cipher_text_list.dart';
export 'cipher_text_wrapper.dart';

View File

@ -0,0 +1,22 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: base_key.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 26/05/2022 17:40:38
// -----
// Copyright (c) 2021
import 'package:native_crypto/src/interfaces/byte_array.dart';
/// Represents a key in NativeCrypto.
///
/// [BaseKey] is a [ByteArray] that can be used to store keys.
///
/// This interface is implemented by all the key classes.
abstract class BaseKey extends ByteArray {
const BaseKey(super.bytes);
BaseKey.fromBase16(super.encoded) : super.fromBase16();
BaseKey.fromBase64(super.encoded) : super.fromBase64();
BaseKey.fromUtf8(super.input) : super.fromUtf8();
}

View File

@ -3,7 +3,7 @@
// -----
// File: byte_array.dart
// Created Date: 16/12/2021 17:54:16
// Last Modified: 26/05/2022 14:25:05
// Last Modified: 26/05/2022 17:13:27
// -----
// Copyright (c) 2021
@ -13,6 +13,9 @@ import 'package:flutter/foundation.dart';
import 'package:native_crypto/src/utils/encoding.dart';
import 'package:native_crypto/src/utils/extensions.dart';
/// Represents a byte array.
///
/// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods.
@immutable
abstract class ByteArray {
final Uint8List _bytes;
@ -56,6 +59,12 @@ abstract class ByteArray {
/// Gets the [ByteArray] bytes as an UTF-16 representation.
String get utf16 => _bytes.toStr();
/// Gets the [ByteArray] length in bytes.
int get length => _bytes.length;
/// Gets the [ByteArray] length in bits.
int get bitLength => _bytes.length * 8;
@override
bool operator ==(Object other) {
if (other is ByteArray) {

View File

@ -3,44 +3,48 @@
// -----
// File: cipher.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 24/05/2022 19:55:38
// Last Modified: 26/05/2022 17:38:26
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:native_crypto/src/core/cipher_text.dart';
import 'package:native_crypto/src/core/cipher_text_wrapper.dart';
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
/// Represents a cipher.
/// Represents a cipher in NativeCrypto.
///
/// In cryptography, a cipher is an algorithm for performing encryption
/// 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.
///
/// This interface is implemented by all the ciphers in NativeCrypto.
abstract class Cipher {
/// Returns the size of a chunk of data
/// that can be processed by the cipher.
static int _bytesCountPerChunk = 33554432;
/// Returns the size of a chunk of data
/// that can be processed by the [Cipher].
static int get bytesCountPerChunk => Cipher._bytesCountPerChunk;
/// Sets the size of a chunk of data
/// that can be processed by the [Cipher].
static set bytesCountPerChunk(int bytesCount) {
_bytesCountPerChunk = bytesCount;
}
/// Returns the standard algorithm name for this cipher
/// Returns the standard algorithm for this [Cipher].
CipherAlgorithm get algorithm;
/// Encrypts data.
/// Encrypts the [data].
///
/// Takes [Uint8List] data as parameter.
/// Returns a [CipherText].
Future<CipherText> encrypt(Uint8List data);
/// Returns a [CipherTextWrapper].
Future<CipherTextWrapper> encrypt(Uint8List data);
/// Decrypts cipher text.
/// Decrypts the [cipherText]
///
/// Takes [CipherText] as parameter.
/// Takes [CipherTextWrapper] as parameter.
/// And returns plain text data as [Uint8List].
Future<Uint8List> decrypt(CipherText cipherText);
Future<Uint8List> decrypt(CipherTextWrapper cipherText);
}

View File

@ -3,12 +3,12 @@
// -----
// File: interfaces.dart
// Created Date: 23/05/2022 23:03:47
// Last Modified: 23/05/2022 23:10:15
// Last Modified: 26/05/2022 17:41:06
// -----
// Copyright (c) 2022
export 'base_key.dart';
export 'builder.dart';
export 'byte_array.dart';
export 'cipher.dart';
// export 'key.dart';
export 'keyderivation.dart';

View File

@ -1,18 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: key.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 23/05/2022 23:02:10
// -----
// Copyright (c) 2021
import 'package:native_crypto/src/interfaces/byte_array.dart';
/// A class representing a key.
abstract class Key extends ByteArray {
const Key(super.bytes);
Key.fromBase16(super.encoded) : super.fromBase16();
Key.fromBase64(super.encoded) : super.fromBase64();
Key.fromUtf8(super.input) : super.fromUtf8();
}

View File

@ -3,18 +3,21 @@
// -----
// File: kdf.dart
// Created Date: 18/12/2021 11:56:43
// Last Modified: 23/05/2022 22:37:04
// Last Modified: 26/05/2022 18:47:15
// -----
// Copyright (c) 2021
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/utils/kdf_algorithm.dart';
/// Represents a Key Derivation Function
/// Represents a Key Derivation Function (KDF) in NativeCrypto.
///
/// [KeyDerivation] function is a function that takes some
/// parameters and returns a [SecretKey].
abstract class KeyDerivation {
/// Returns the standard algorithm name for this key derivation function
/// Returns the standard algorithm for this key derivation function
KdfAlgorithm get algorithm;
/// Derive key
/// Derive a [SecretKey].
Future<SecretKey> derive();
}

View File

@ -3,7 +3,7 @@
// -----
// File: pbkdf2.dart
// Created Date: 17/12/2021 14:50:42
// Last Modified: 25/05/2022 10:45:00
// Last Modified: 26/05/2022 18:51:59
// -----
// Copyright (c) 2021
@ -12,10 +12,15 @@ import 'dart:typed_data';
import 'package:native_crypto/src/interfaces/keyderivation.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/platform.dart';
import 'package:native_crypto/src/utils/extensions.dart';
import 'package:native_crypto/src/utils/hash_algorithm.dart';
import 'package:native_crypto/src/utils/kdf_algorithm.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
/// Represent a PBKDF2 Key Derivation Function (KDF) in NativeCrypto.
///
/// [Pbkdf2] is a function that takes password, salt, iteration count and
/// derive a [SecretKey] of specified length.
class Pbkdf2 extends KeyDerivation {
final int _keyBytesCount;
final int _iterations;
@ -35,20 +40,42 @@ class Pbkdf2 extends KeyDerivation {
@override
Future<SecretKey> derive({String? password, String? salt}) async {
if (password == null || salt == null) {
throw const KeyDerivationException(
message: "Password or Salt can't be null!",
code: 'invalid_password_or_salt',
throw NativeCryptoException(
message: 'Password and salt cannot be null. '
'Here is the password: $password, here is the salt: $salt',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
final Uint8List derivation = (await platform.pbkdf2(
password,
salt,
_keyBytesCount,
_iterations,
_hash.name,
)) ??
Uint8List(0);
final Uint8List? derivation = await platform.pbkdf2(
password,
salt,
_keyBytesCount,
_iterations,
_hash.name,
);
if (derivation.isNull) {
throw NativeCryptoException(
message: 'Failed to derive a key! Platform returned null.',
code: NativeCryptoExceptionCode.platform_returned_null.code,
);
}
if (derivation!.isEmpty) {
throw NativeCryptoException(
message: 'Failed to derive a key! Platform returned no data.',
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
);
}
if (derivation.length != _keyBytesCount) {
throw NativeCryptoException(
message: 'Failed to derive a key! Platform returned '
'${derivation.length} bytes, but expected $_keyBytesCount bytes.',
code: NativeCryptoExceptionCode.platform_returned_invalid_data.code,
);
}
return SecretKey(derivation);
}

View File

@ -3,46 +3,54 @@
// -----
// File: secret_key.dart
// Created Date: 28/12/2021 13:36:54
// Last Modified: 26/05/2022 11:56:06
// Last Modified: 26/05/2022 19:26:35
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:native_crypto/src/interfaces/key.dart';
import 'package:native_crypto/src/interfaces/base_key.dart';
import 'package:native_crypto/src/interfaces/cipher.dart';
import 'package:native_crypto/src/platform.dart';
import 'package:native_crypto/src/utils/extensions.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.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 {
/// Represents a secret key in NativeCrypto.
///
/// [SecretKey] is a [BaseKey] that can be used to store secret keys.
/// A [SecretKey] is a key that can be used to encrypt or decrypt data with
/// a symmetric [Cipher].
class SecretKey extends BaseKey {
const SecretKey(super.bytes);
SecretKey.fromBase16(super.encoded) : super.fromBase16();
SecretKey.fromBase64(super.encoded) : super.fromBase64();
SecretKey.fromUtf8(super.input) : super.fromUtf8();
static Future<SecretKey> fromSecureRandom(int bitsCount) async {
Uint8List? key;
try {
final Uint8List? _key = await platform.generateSecretKey(bitsCount);
if (_key == null || _key.isEmpty) {
throw const KeyException(
message: 'Could not generate secret key, platform returned null',
code: 'platform_returned_null',
);
}
return SecretKey(_key);
key = await platform.generateSecretKey(bitsCount);
} catch (e, s) {
if (e is KeyException) {
rethrow;
}
throw KeyException(
throw NativeCryptoException(
message: '$e',
code: 'failed_to_generate_secret_key',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
);
}
if (key.isNull) {
throw NativeCryptoException(
message: 'Failed to generate a secret key! Platform returned null.',
code: NativeCryptoExceptionCode.platform_returned_null.code,
);
}
if (key!.isEmpty) {
throw NativeCryptoException(
message: 'Failed to generate a secret key! '
'Platform returned no data.',
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
);
}
return SecretKey(key);
}
}

View File

@ -3,9 +3,9 @@
// -----
// File: cipher_algorithm.dart
// Created Date: 23/05/2022 22:07:54
// Last Modified: 23/05/2022 22:33:56
// Last Modified: 26/05/2022 18:52:32
// -----
// Copyright (c) 2022
/// Represents different cipher algorithms
enum CipherAlgorithm { aes, rsa }
enum CipherAlgorithm { aes }

View File

@ -3,7 +3,7 @@
// -----
// File: extensions.dart
// Created Date: 26/05/2022 12:12:48
// Last Modified: 26/05/2022 15:49:38
// Last Modified: 26/05/2022 18:52:48
// -----
// Copyright (c) 2022
@ -30,8 +30,8 @@ extension ListIntX on List<int> {
}
extension ListUint8ListX on List<Uint8List> {
/// Reduce a [List] of [Uint8List] to a [Uint8List].
Uint8List sum() {
for (var i = 1; i < length; i++) {
first.addAll(this[i]);

View File

@ -3,7 +3,7 @@
// -----
// File: hash_algorithm.dart
// Created Date: 23/05/2022 22:01:59
// Last Modified: 23/05/2022 22:47:08
// Last Modified: 26/05/2022 18:53:38
// -----
// Copyright (c) 2022
@ -11,12 +11,13 @@ import 'dart:typed_data';
import 'package:native_crypto/src/platform.dart';
/// Defines the hash algorithms.
enum HashAlgorithm {
sha256,
sha384,
sha512;
/// Hashes a message
/// Digest the [data] using this [HashAlgorithm].
Future<Uint8List> digest(Uint8List data) async {
final Uint8List hash = (await platform.digest(data, name)) ?? Uint8List(0);

View File

@ -3,8 +3,9 @@
// -----
// File: kdf_algorithm.dart
// Created Date: 23/05/2022 22:36:24
// Last Modified: 23/05/2022 22:36:36
// Last Modified: 26/05/2022 18:53:50
// -----
// Copyright (c) 2022
/// Represents different key derivation functions
enum KdfAlgorithm { pbkdf2 }

View File

@ -3,7 +3,7 @@
// -----
// File: secret_key_test.dart
// Created Date: 26/05/2022 10:52:41
// Last Modified: 26/05/2022 12:07:33
// Last Modified: 26/05/2022 19:24:44
// -----
// Copyright (c) 2022
@ -42,10 +42,10 @@ void main() {
await expectLater(
() => SecretKey.fromSecureRandom(5),
throwsA(
isA<KeyException>().having(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
'platform_returned_empty_data',
),
),
);
@ -59,7 +59,7 @@ void main() {
await expectLater(
() => SecretKey.fromSecureRandom(5),
throwsA(
isA<KeyException>().having(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
@ -81,7 +81,7 @@ void main() {
await expectLater(
() => SecretKey.fromSecureRandom(5),
throwsA(
isA<KeyException>()
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
@ -90,7 +90,7 @@ void main() {
.having(
(e) => e.code,
'code',
'failed_to_generate_secret_key',
'platform_throws',
),
),
);