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 // File: benchmark_page.dart
// Created Date: 28/12/2021 15:12:39 // 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 // Copyright (c) 2021
@ -40,7 +40,7 @@ class BenchmarkPage extends ConsumerWidget {
return; 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 int multiplier = pow(2, 20).toInt(); // MiB
benchmarkStatus.print("[Benchmark] Sizes: ${testedSizes.join('/')}MiB\n"); benchmarkStatus.print("[Benchmark] Sizes: ${testedSizes.join('/')}MiB\n");
@ -86,7 +86,7 @@ class BenchmarkPage extends ConsumerWidget {
if (usePc) { if (usePc) {
pc.decrypt(encryptedBigFile as Uint8List, state.secretKey.bytes); pc.decrypt(encryptedBigFile as Uint8List, state.secretKey.bytes);
} else { } else {
await cipher.decrypt(encryptedBigFile as CipherText); await cipher.decrypt(encryptedBigFile as CipherTextWrapper);
} }
after = DateTime.now(); after = DateTime.now();
benchmark = benchmark =
@ -105,7 +105,7 @@ class BenchmarkPage extends ConsumerWidget {
.appendln('[Benchmark] Finished: ${sum}MiB in $benchmark ms'); .appendln('[Benchmark] Finished: ${sum}MiB in $benchmark ms');
benchmarkStatus.appendln('[Benchmark] Check the console for csv data'); benchmarkStatus.appendln('[Benchmark] Check the console for csv data');
benchmarkStatus.appendln(csv); benchmarkStatus.appendln(csv);
print(csv); debugPrint(csv);
} }
void _clear() { void _clear() {
@ -135,7 +135,7 @@ class BenchmarkPage extends ConsumerWidget {
} }
keyContent.print(state.secretKey.bytes.toString()); keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm); AES cipher = AES(state.secretKey);
return SingleChildScrollView( return SingleChildScrollView(
child: Padding( child: Padding(

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: cipher_page.dart // File: cipher_page.dart
// Created Date: 28/12/2021 13:33:15 // 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 // Copyright (c) 2021
@ -12,10 +12,10 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.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 'package:native_crypto_example/widgets/button.dart';
import '../session.dart'; import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart'; import '../widgets/output.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
@ -26,8 +26,8 @@ class CipherPage extends ConsumerWidget {
final Output encryptionStatus = Output(); final Output encryptionStatus = Output();
final Output decryptionStatus = Output(); final Output decryptionStatus = Output();
final TextEditingController _plainTextController = TextEditingController(); final TextEditingController _plainTextController = TextEditingController()..text = 'PlainText';
CipherText? cipherText; CipherTextWrapper? cipherText;
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async { Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
Session state = ref.read(sessionProvider.state).state; Session state = ref.read(sessionProvider.state).state;
@ -41,13 +41,10 @@ class CipherPage extends ConsumerWidget {
} else { } else {
var stringToBytes = plainText.toBytes(); var stringToBytes = plainText.toBytes();
cipherText = await cipher.encrypt(stringToBytes); cipherText = await cipher.encrypt(stringToBytes);
encryptionStatus.print('String successfully encrypted.\n'); encryptionStatus.print('String successfully encrypted:\n');
encryptionStatus.append("Nonce: " +
cipherText!.iv.toString() + CipherText unwrap = cipherText!.unwrap<CipherText>();
"\nData: " + encryptionStatus.append(unwrap.base16);
cipherText!.data.toString() +
"\nTag: " +
cipherText!.tag.toString());
} }
} }
@ -56,17 +53,19 @@ class CipherPage extends ConsumerWidget {
decryptionStatus.print('Encrypt before altering CipherText!'); decryptionStatus.print('Encrypt before altering CipherText!');
} else { } else {
// Add 1 to the first byte // Add 1 to the first byte
Uint8List _altered = cipherText!.data; Uint8List _altered = cipherText!.unwrap<CipherText>().bytes;
_altered[0] += 1; _altered[0] += 1;
// Recreate cipher text with altered data // Recreate cipher text with altered data
cipherText = CipherText(cipherText!.iv, _altered, cipherText!.tag); cipherText = CipherTextWrapper.fromBytes(
encryptionStatus.print('String successfully encrypted.\n'); _altered,
encryptionStatus.append("Nonce: " + 12,
cipherText!.iv.toString() + _altered.length - 28,
"\nData: " + 16,
cipherText!.data.toString() + );
"\nTag: " + encryptionStatus.print('String successfully encrypted:\n');
cipherText!.tag.toString());
CipherText unwrap = cipherText!.unwrap();
encryptionStatus.appendln(unwrap.base16);
decryptionStatus.print('CipherText altered!\nDecryption will fail.'); decryptionStatus.print('CipherText altered!\nDecryption will fail.');
} }
} }
@ -114,7 +113,7 @@ class CipherPage extends ConsumerWidget {
} }
keyContent.print(state.secretKey.bytes.toString()); keyContent.print(state.secretKey.bytes.toString());
AES cipher = AES(state.secretKey, AESMode.gcm); AES cipher = AES(state.secretKey);
return SingleChildScrollView( return SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: kdf_page.dart // File: kdf_page.dart
// Created Date: 28/12/2021 13:40:34 // 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 // Copyright (c) 2021
@ -12,10 +12,10 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_crypto/native_crypto.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 'package:native_crypto_example/widgets/button.dart';
import '../session.dart'; import '../session.dart';
import '../utils.dart';
import '../widgets/output.dart'; import '../widgets/output.dart';
class KdfPage extends ConsumerWidget { class KdfPage extends ConsumerWidget {
@ -26,8 +26,8 @@ class KdfPage extends ConsumerWidget {
final Output pbkdf2Status = Output(); final Output pbkdf2Status = Output();
final Output hashStatus = Output(large: true); final Output hashStatus = Output(large: true);
final TextEditingController _pwdTextController = TextEditingController(); final TextEditingController _pwdTextController = TextEditingController()..text = 'Password';
final TextEditingController _messageTextController = TextEditingController(); final TextEditingController _messageTextController = TextEditingController()..text = 'Message';
Future<void> _generate(WidgetRef ref) async { Future<void> _generate(WidgetRef ref) async {
Session state = ref.read(sessionProvider.state).state; Session state = ref.read(sessionProvider.state).state;
@ -66,7 +66,7 @@ class KdfPage extends ConsumerWidget {
} else { } else {
Uint8List hash = await hasher.digest(message.toBytes()); Uint8List hash = await hasher.digest(message.toBytes());
hashStatus.print( 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 // File: builders.dart
// Created Date: 23/05/2022 22:56:03 // 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 // 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 // File: aes.dart
// Created Date: 16/12/2021 16:28:00 // 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 // 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_mode.dart';
import 'package:native_crypto/src/ciphers/aes/aes_padding.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.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/interfaces/cipher.dart';
import 'package:native_crypto/src/keys/secret_key.dart'; import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/platform.dart'; import 'package:native_crypto/src/platform.dart';
import 'package:native_crypto/src/utils/cipher_algorithm.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'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
export 'package:native_crypto/src/ciphers/aes/aes_key_size.dart'; /// An AES cipher.
export 'package:native_crypto/src/ciphers/aes/aes_mode.dart'; ///
export 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; /// [AES] is a [Cipher] that can be used to encrypt or decrypt data.
class AES implements Cipher { class AES implements Cipher {
final SecretKey key; final SecretKey _key;
final AESMode mode; final AESMode mode;
final AESPadding padding; final AESPadding padding;
@override @override
CipherAlgorithm get algorithm => CipherAlgorithm.aes; CipherAlgorithm get algorithm => CipherAlgorithm.aes;
AES(this.key, this.mode, {this.padding = AESPadding.none}) { AES(SecretKey key, [this.mode = AESMode.gcm, this.padding = AESPadding.none])
if (!AESKeySize.supportedSizes.contains(key.bytes.length * 8)) { : _key = key {
throw const CipherInitException( if (!AESKeySize.supportedSizes.contains(key.bitLength)) {
message: 'Invalid key length!', throw NativeCryptoException(
code: 'invalid_key_length', 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)) { if (!mode.supportedPaddings.contains(padding)) {
throw const CipherInitException( throw NativeCryptoException(
message: 'Invalid padding!', message: 'Invalid padding! '
code: 'invalid_padding', 'Expected: ${mode.supportedPaddings.join(', ')}',
code: NativeCryptoExceptionCode.invalid_padding.code,
); );
} }
} }
Future<Uint8List> _decrypt(CipherText cipherText) async { Future<Uint8List> _decrypt(CipherText cipherText,
return await platform.decryptAsList( {int chunkCount = 0,}) async {
[cipherText.iv, cipherText.payload], final Uint8List? decrypted = await platform.decrypt(
key.bytes, cipherText.bytes,
_key.bytes,
algorithm.name, algorithm.name,
) ?? );
Uint8List(0);
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) async { Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async {
final List<Uint8List> cipherText = final Uint8List? encrypted = await platform.encrypt(
await platform.encryptAsList(data, key.bytes, algorithm.name) ?? data,
List.empty(); _key.bytes,
return CipherText.fromPairIvAndBytes( algorithm.name,
cipherText,
dataLength: cipherText.last.length - 16,
tagLength: 16,
); );
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 @override
Future<Uint8List> decrypt(CipherText cipherText) async { Future<Uint8List> decrypt(CipherTextWrapper cipherText) async {
final BytesBuilder decryptedData = BytesBuilder(copy: false); final BytesBuilder decryptedData = BytesBuilder(copy: false);
if (cipherText is CipherTextList) { if (cipherText.isList) {
for (final CipherText ct in cipherText.list) { int chunkCount = 0;
decryptedData.add(await _decrypt(ct)); for (final CipherText chunk in cipherText.list) {
decryptedData.add(await _decrypt(chunk, chunkCount: chunkCount++));
} }
} else { } else {
decryptedData.add(await _decrypt(cipherText)); decryptedData.add(await _decrypt(cipherText.single));
} }
return decryptedData.toBytes(); return decryptedData.toBytes();
} }
@override @override
Future<CipherText> encrypt(Uint8List data) async { Future<CipherTextWrapper> encrypt(Uint8List data) async {
CipherTextWrapper cipherTextWrapper;
Uint8List dataToEncrypt; Uint8List dataToEncrypt;
final CipherTextList cipherTextList = CipherTextList();
if (data.length > Cipher.bytesCountPerChunk) {
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();
if (chunkNb > 1) {
cipherTextWrapper = CipherTextWrapper.empty();
for (var i = 0; i < chunkNb; i++) { for (var i = 0; i < chunkNb; i++) {
dataToEncrypt = i < (chunkNb - 1) dataToEncrypt = i < (chunkNb - 1)
? data.sublist( ? data.sublist(
@ -102,11 +134,12 @@ class AES implements Cipher {
(i + 1) * Cipher.bytesCountPerChunk, (i + 1) * Cipher.bytesCountPerChunk,
) )
: data.sublist(i * Cipher.bytesCountPerChunk); : data.sublist(i * Cipher.bytesCountPerChunk);
cipherTextList.add(await _encrypt(dataToEncrypt)); cipherTextWrapper.add(await _encrypt(dataToEncrypt, chunkCount: i));
} }
} else { } 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 // File: aes_key_size.dart
// Created Date: 23/05/2022 22:10:07 // 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 // Copyright (c) 2022
@ -13,9 +13,13 @@ enum AESKeySize {
bits192(192), bits192(192),
bits256(256); bits256(256);
/// Returns the number of bits supported by an [AESKeySize].
static final List<int> supportedSizes = [128, 192, 256]; static final List<int> supportedSizes = [128, 192, 256];
/// Returns the number of bits in this [AESKeySize].
final int bits; final int bits;
/// Returns the number of bytes in this [AESKeySize].
int get bytes => bits ~/ 8; int get bytes => bits ~/ 8;
const AESKeySize(this.bits); const AESKeySize(this.bits);

View File

@ -3,9 +3,18 @@
// ----- // -----
// File: aes_mode.dart // File: aes_mode.dart
// Created Date: 23/05/2022 22:09:16 // 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 // Copyright (c) 2022
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
/// Defines the AES modes of operation. /// 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 // File: cipher_text.dart
// Created Date: 16/12/2021 16:59:53 // 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 // Copyright (c) 2021
import 'dart:typed_data'; 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/cipher_algorithm.dart';
import 'package:native_crypto/src/utils/extensions.dart'; import 'package:native_crypto/src/utils/extensions.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.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 /// ```txt
/// [[NONCE], [MESSAGE + TAG]] /// [IV + MESSAGE + TAG]
/// ``` /// ```
/// where: /// where:
/// - `[NONCE]` is a [Uint8List] of length [CipherText.ivLength] /// - IV's length is [CipherText.ivLength] bytes.
/// - `[MESSAGE + TAG]` is a [Uint8List] of length [CipherText.dataLength] /// - MESSAGE's length is [CipherText.messageLength] bytes.
/// - TAG's length is [CipherText.tagLength] bytes.
/// ///
/// To /// Check [CipherTextWrapper] for more information.
/// class CipherText extends ByteArray {
/// So accessing just the Message or just the Tag is costly and should be
/// done only when needed.
class CipherText {
final int _ivLength; final int _ivLength;
final int _messageLength; final int _messageLength;
final int _tagLength; final int _tagLength;
final CipherAlgorithm? _cipherAlgorithm; final CipherAlgorithm? _cipherAlgorithm;
final Uint8List? _iv; const CipherText._(
final Uint8List? _data; // Contains the message + tag (if any)
CipherText._(
this._ivLength, this._ivLength,
this._messageLength, this._messageLength,
this._tagLength, this._tagLength,
this._cipherAlgorithm, this._cipherAlgorithm,
this._iv, super.bytes,
this._data,
); );
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]. /// Gets the [CipherAlgorithm] used to encrypt the [CipherText].
CipherAlgorithm get cipherAlgorithm { CipherAlgorithm get cipherAlgorithm {
if (_cipherAlgorithm.isNotNull) { 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. /// Gets the length of the [CipherText]'s IV.
int get ivLength => _ivLength; int get ivLength => _ivLength;
/// Gets the [Uint8List] of the [CipherText]'s data. /// Gets the length of the [CipherText]'s Message.
Uint8List get data { int get messageLength => _messageLength;
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 data. /// Gets the length of the [CipherText]'s Tag.
int get dataLength => _messageLength + _tagLength; int get tagLength => _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;
} }

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 // File: cipher_text_wrapper.dart
// Created Date: 26/05/2022 14:27:32 // 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 // Copyright (c) 2022
@ -12,31 +12,85 @@ import 'dart:typed_data';
import 'package:native_crypto/native_crypto.dart'; import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto/src/utils/extensions.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 { class CipherTextWrapper {
final CipherText? _single; final CipherText? _single;
final List<CipherText>? _list; final List<CipherText>? _list;
CipherTextWrapper._(this._single, this._list); CipherTextWrapper._(this._single, this._list);
/// Creates a [CipherTextWrapper] from a [CipherText].
factory CipherTextWrapper.single(CipherText cipherText) => factory CipherTextWrapper.single(CipherText cipherText) =>
CipherTextWrapper._(cipherText, null); CipherTextWrapper._(cipherText, null);
/// Creates a [CipherTextWrapper] from a [List] of [CipherText].
factory CipherTextWrapper.list(List<CipherText> cipherTexts) => factory CipherTextWrapper.list(List<CipherText> cipherTexts) =>
CipherTextWrapper._(null, 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( factory CipherTextWrapper.fromBytes(
// Uint8List bytes, { Uint8List bytes,
// required int ivLength, int ivLength,
// required int dataLength, int messageLength,
// int tagLength = 0, int tagLength, {
// int? chunkSize, CipherAlgorithm? cipherAlgorithm,
// } int? chunkSize,
) { }) {
// TODO(hpcl): implement fromBytes chunkSize ??= Cipher.bytesCountPerChunk;
throw UnimplementedError(); 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; bool get isSingle => _single.isNotNull;
/// Checks if the [CipherText] is a [List] of [CipherText].
bool get isList => _list.isNotNull; bool get isList => _list.isNotNull;
/// Gets the [CipherText] if it's a single one. /// 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]. /// Gets the raw [Uint8List] of the [CipherText] or [List] of [CipherText].
Uint8List get raw { Uint8List get bytes {
if (isSingle) { if (isSingle) {
return single.bytes; return single.bytes;
} else { } 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 { int get chunkCount {
_single.isNull; _single.isNull;
if (_single.isNotNull) { if (_single.isNotNull) {
@ -86,4 +143,41 @@ class CipherTextWrapper {
return _list?.length ?? 0; 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 // File: core.dart
// Created Date: 23/05/2022 23:05:26 // 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 // Copyright (c) 2022
export 'cipher_text.dart'; 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 // File: byte_array.dart
// Created Date: 16/12/2021 17:54:16 // 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 // 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/encoding.dart';
import 'package:native_crypto/src/utils/extensions.dart'; import 'package:native_crypto/src/utils/extensions.dart';
/// Represents a byte array.
///
/// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods.
@immutable @immutable
abstract class ByteArray { abstract class ByteArray {
final Uint8List _bytes; final Uint8List _bytes;
@ -56,6 +59,12 @@ abstract class ByteArray {
/// Gets the [ByteArray] bytes as an UTF-16 representation. /// Gets the [ByteArray] bytes as an UTF-16 representation.
String get utf16 => _bytes.toStr(); 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other is ByteArray) { if (other is ByteArray) {

View File

@ -3,44 +3,48 @@
// ----- // -----
// File: cipher.dart // File: cipher.dart
// Created Date: 16/12/2021 16:28:00 // 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 // Copyright (c) 2021
import 'dart:typed_data'; 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'; 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 /// or decryption - a series of well-defined steps that can
/// be followed as a procedure. /// be followed as a procedure.
///
/// This interface is implemented by all the ciphers in NativeCrypto.
abstract class Cipher { abstract class Cipher {
/// Returns the size of a chunk of data
/// that can be processed by the cipher.
static int _bytesCountPerChunk = 33554432; 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; 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) { static set bytesCountPerChunk(int bytesCount) {
_bytesCountPerChunk = bytesCount; _bytesCountPerChunk = bytesCount;
} }
/// Returns the standard algorithm name for this cipher /// Returns the standard algorithm for this [Cipher].
CipherAlgorithm get algorithm; CipherAlgorithm get algorithm;
/// Encrypts data. /// Encrypts the [data].
/// ///
/// Takes [Uint8List] data as parameter. /// Takes [Uint8List] data as parameter.
/// Returns a [CipherText]. /// Returns a [CipherTextWrapper].
Future<CipherText> encrypt(Uint8List data); 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]. /// 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 // File: interfaces.dart
// Created Date: 23/05/2022 23:03:47 // 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 // Copyright (c) 2022
export 'base_key.dart';
export 'builder.dart'; export 'builder.dart';
export 'byte_array.dart'; export 'byte_array.dart';
export 'cipher.dart'; export 'cipher.dart';
// export 'key.dart';
export 'keyderivation.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 // File: kdf.dart
// Created Date: 18/12/2021 11:56:43 // 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 // Copyright (c) 2021
import 'package:native_crypto/src/keys/secret_key.dart'; import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/utils/kdf_algorithm.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 { 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; KdfAlgorithm get algorithm;
/// Derive key /// Derive a [SecretKey].
Future<SecretKey> derive(); Future<SecretKey> derive();
} }

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: pbkdf2.dart // File: pbkdf2.dart
// Created Date: 17/12/2021 14:50:42 // 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 // Copyright (c) 2021
@ -12,10 +12,15 @@ import 'dart:typed_data';
import 'package:native_crypto/src/interfaces/keyderivation.dart'; import 'package:native_crypto/src/interfaces/keyderivation.dart';
import 'package:native_crypto/src/keys/secret_key.dart'; import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/platform.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/hash_algorithm.dart';
import 'package:native_crypto/src/utils/kdf_algorithm.dart'; import 'package:native_crypto/src/utils/kdf_algorithm.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.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 { class Pbkdf2 extends KeyDerivation {
final int _keyBytesCount; final int _keyBytesCount;
final int _iterations; final int _iterations;
@ -35,20 +40,42 @@ class Pbkdf2 extends KeyDerivation {
@override @override
Future<SecretKey> derive({String? password, String? salt}) async { Future<SecretKey> derive({String? password, String? salt}) async {
if (password == null || salt == null) { if (password == null || salt == null) {
throw const KeyDerivationException( throw NativeCryptoException(
message: "Password or Salt can't be null!", message: 'Password and salt cannot be null. '
code: 'invalid_password_or_salt', 'Here is the password: $password, here is the salt: $salt',
code: NativeCryptoExceptionCode.invalid_argument.code,
); );
} }
final Uint8List derivation = (await platform.pbkdf2( final Uint8List? derivation = await platform.pbkdf2(
password, password,
salt, salt,
_keyBytesCount, _keyBytesCount,
_iterations, _iterations,
_hash.name, _hash.name,
)) ?? );
Uint8List(0);
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); return SecretKey(derivation);
} }

View File

@ -3,46 +3,54 @@
// ----- // -----
// File: secret_key.dart // File: secret_key.dart
// Created Date: 28/12/2021 13:36:54 // 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 // Copyright (c) 2021
import 'dart:typed_data'; 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/platform.dart';
import 'package:native_crypto/src/utils/extensions.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
/// A class representing a secret key. /// Represents a secret key in NativeCrypto.
/// A secret key is a key that is not accessible by anyone else. ///
/// It is used to encrypt and decrypt data. /// [SecretKey] is a [BaseKey] that can be used to store secret keys.
class SecretKey extends Key { /// 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); const SecretKey(super.bytes);
SecretKey.fromBase16(super.encoded) : super.fromBase16(); SecretKey.fromBase16(super.encoded) : super.fromBase16();
SecretKey.fromBase64(super.encoded) : super.fromBase64(); SecretKey.fromBase64(super.encoded) : super.fromBase64();
SecretKey.fromUtf8(super.input) : super.fromUtf8(); SecretKey.fromUtf8(super.input) : super.fromUtf8();
static Future<SecretKey> fromSecureRandom(int bitsCount) async { static Future<SecretKey> fromSecureRandom(int bitsCount) async {
Uint8List? key;
try { try {
final Uint8List? _key = await platform.generateSecretKey(bitsCount); 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);
} catch (e, s) { } catch (e, s) {
if (e is KeyException) { throw NativeCryptoException(
rethrow;
}
throw KeyException(
message: '$e', message: '$e',
code: 'failed_to_generate_secret_key', code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s, 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 // File: cipher_algorithm.dart
// Created Date: 23/05/2022 22:07:54 // 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 // Copyright (c) 2022
/// Represents different cipher algorithms /// Represents different cipher algorithms
enum CipherAlgorithm { aes, rsa } enum CipherAlgorithm { aes }

View File

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

View File

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

View File

@ -3,8 +3,9 @@
// ----- // -----
// File: kdf_algorithm.dart // File: kdf_algorithm.dart
// Created Date: 23/05/2022 22:36:24 // 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 // Copyright (c) 2022
/// Represents different key derivation functions
enum KdfAlgorithm { pbkdf2 } enum KdfAlgorithm { pbkdf2 }

View File

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