Compare commits

..

No commits in common. "cf4227fb582a7487194dc51d4631919c296790a8" and "96f9aad1b314bb749c1cf1997f41b09c949fe797" have entirely different histories.

20 changed files with 112 additions and 639 deletions

View File

@ -1,13 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
- name: test:all
image: cirrusci/flutter:stable
commands:
- PATH=$PATH:$HOME/.pub-cache/bin
- flutter doctor
- flutter pub global activate melos
- melos bs
- melos run test:all

View File

@ -15,24 +15,6 @@ scripts:
lint:all: lint:all:
run: melos run analyze && melos run format run: melos run analyze && melos run format
description: Run all static analysis checks. description: Run all static analysis checks.
test:all:
run: |
melos run test --no-select
description: |
Run all tests available.
test:
run: |
melos exec -c 6 --fail-fast -- \
"flutter test --no-pub --no-test-assets"
description: Run `flutter test` for a specific package.
select-package:
dir-exists:
- test
ignore:
- "*web*"
- "*example*"
analyze: analyze:
run: | run: |

View File

@ -91,8 +91,8 @@ class BenchmarkPage extends ConsumerWidget {
after = DateTime.now(); after = DateTime.now();
benchmark = benchmark =
after.millisecondsSinceEpoch - before.millisecondsSinceEpoch; after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
benchmarkStatus.appendln( benchmarkStatus
'[Benchmark] ${size}MiB => Decryption took $benchmark ms'); .appendln('[Benchmark] ${size}MiB => Decryption took $benchmark ms');
csvLine.write(';$benchmark'); csvLine.write(';$benchmark');
} }
csv += csvLine.toString() + '\n'; csv += csvLine.toString() + '\n';

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: 27/05/2022 16:42:10 // Last Modified: 26/05/2022 20:39:37
// ----- // -----
// Copyright (c) 2021 // Copyright (c) 2021
@ -26,8 +26,7 @@ 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';
..text = 'PlainText';
CipherTextWrapper? cipherText; CipherTextWrapper? cipherText;
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async { Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
@ -59,8 +58,9 @@ class CipherPage extends ConsumerWidget {
// Recreate cipher text with altered data // Recreate cipher text with altered data
cipherText = CipherTextWrapper.fromBytes( cipherText = CipherTextWrapper.fromBytes(
_altered, _altered,
ivLength: AESMode.gcm.ivLength, 12,
tagLength: AESMode.gcm.tagLength, _altered.length - 28,
16,
); );
encryptionStatus.print('String successfully encrypted:\n'); encryptionStatus.print('String successfully encrypted:\n');

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: 26/05/2022 21:09:47 // Last Modified: 26/05/2022 20:30:31
// ----- // -----
// Copyright (c) 2021 // Copyright (c) 2021
@ -26,10 +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';
..text = 'Password'; final TextEditingController _messageTextController = TextEditingController()..text = 'Message';
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;
@ -52,11 +50,7 @@ class KdfPage extends ConsumerWidget {
if (password.isEmpty) { if (password.isEmpty) {
pbkdf2Status.print('Password is empty'); pbkdf2Status.print('Password is empty');
} else { } else {
Pbkdf2 _pbkdf2 = Pbkdf2( Pbkdf2 _pbkdf2 = Pbkdf2(32, 1000, algorithm: HashAlgorithm.sha512);
keyBytesCount: 32,
iterations: 1000,
algorithm: HashAlgorithm.sha512,
);
SecretKey sk = await _pbkdf2.derive(password: password, salt: 'salt'); SecretKey sk = await _pbkdf2.derive(password: password, salt: 'salt');
state.setKey(sk); state.setKey(sk);
pbkdf2Status.print('Key successfully derived.'); pbkdf2Status.print('Key successfully derived.');

View File

@ -3,12 +3,10 @@
// ----- // -----
// File: aes_gcm.dart // File: aes_gcm.dart
// Created Date: 24/05/2022 16:34:54 // Created Date: 24/05/2022 16:34:54
// Last Modified: 27/05/2022 17:36:31 // Last Modified: 24/05/2022 17:15:22
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
// ignore_for_file: implementation_imports
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';

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: 27/05/2022 12:13:28 // Last Modified: 26/05/2022 19:43:22
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@ -21,10 +21,6 @@ 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';
export 'aes_key_size.dart';
export 'aes_mode.dart';
export 'aes_padding.dart';
/// An AES cipher. /// An AES cipher.
/// ///
/// [AES] is a [Cipher] that can be used to encrypt or decrypt data. /// [AES] is a [Cipher] that can be used to encrypt or decrypt data.
@ -46,6 +42,7 @@ class AES implements Cipher {
); );
} }
if (!mode.supportedPaddings.contains(padding)) { if (!mode.supportedPaddings.contains(padding)) {
throw NativeCryptoException( throw NativeCryptoException(
message: 'Invalid padding! ' message: 'Invalid padding! '
@ -55,25 +52,13 @@ class AES implements Cipher {
} }
} }
Future<Uint8List> _decrypt( Future<Uint8List> _decrypt(CipherText cipherText,
CipherText cipherText, { {int chunkCount = 0,}) async {
int chunkCount = 0, final Uint8List? decrypted = await platform.decrypt(
}) async { cipherText.bytes,
Uint8List? decrypted; _key.bytes,
algorithm.name,
try { );
decrypted = await platform.decrypt(
cipherText.bytes,
_key.bytes,
algorithm.name,
);
} catch (e, s) {
throw NativeCryptoException(
message: '$e',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
);
}
if (decrypted.isNull) { if (decrypted.isNull) {
throw NativeCryptoException( throw NativeCryptoException(
@ -91,21 +76,11 @@ class AES implements Cipher {
} }
Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async { Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async {
Uint8List? encrypted; final Uint8List? encrypted = await platform.encrypt(
data,
try { _key.bytes,
encrypted = await platform.encrypt( algorithm.name,
data, );
_key.bytes,
algorithm.name,
);
} catch (e, s) {
throw NativeCryptoException(
message: '$e on chunk #$chunkCount',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
);
}
if (encrypted.isNull) { if (encrypted.isNull) {
throw NativeCryptoException( throw NativeCryptoException(
@ -118,21 +93,13 @@ class AES implements Cipher {
code: NativeCryptoExceptionCode.platform_returned_empty_data.code, code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
); );
} else { } else {
try { return CipherText.fromBytes(
return CipherText.fromBytes( 12,
encrypted, encrypted.length - 28,
ivLength: 12, 16,
messageLength: encrypted.length - 28, CipherAlgorithm.aes,
tagLength: 16, encrypted,
cipherAlgorithm: CipherAlgorithm.aes, );
);
} on NativeCryptoException catch (e, s) {
throw NativeCryptoException(
message: '${e.message} on chunk #$chunkCount',
code: e.code,
stackTrace: s,
);
}
} }
} }
@ -154,9 +121,6 @@ class AES implements Cipher {
@override @override
Future<CipherTextWrapper> encrypt(Uint8List data) async { Future<CipherTextWrapper> encrypt(Uint8List data) async {
if (data.isEmpty) {
return CipherTextWrapper.empty();
}
CipherTextWrapper cipherTextWrapper; CipherTextWrapper cipherTextWrapper;
Uint8List dataToEncrypt; Uint8List dataToEncrypt;
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();

View File

@ -3,7 +3,7 @@
// ----- // -----
// 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: 26/05/2022 21:03:26 // Last Modified: 26/05/2022 18:41:31
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@ -11,20 +11,10 @@ import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
/// Defines the AES modes of operation. /// Defines the AES modes of operation.
enum AESMode { enum AESMode {
gcm([AESPadding.none], 12, 16); gcm([AESPadding.none]);
/// Returns the list of supported [AESPadding] for this [AESMode]. /// Returns the list of supported [AESPadding] for this [AESMode].
final List<AESPadding> supportedPaddings; final List<AESPadding> supportedPaddings;
/// Returns the default IV length for this [AESMode]. const AESMode(this.supportedPaddings);
final int ivLength;
/// Returns the default tag length for this [AESMode].
final int tagLength;
const AESMode(
this.supportedPaddings, [
this.ivLength = 16,
this.tagLength = 0,
]);
} }

View File

@ -3,7 +3,7 @@
// ----- // -----
// 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: 27/05/2022 12:09:47 // Last Modified: 26/05/2022 19:43:57
// ----- // -----
// Copyright (c) 2021 // Copyright (c) 2021
@ -27,7 +27,7 @@ import 'package:native_crypto_platform_interface/native_crypto_platform_interfac
/// - IV's length is [CipherText.ivLength] bytes. /// - IV's length is [CipherText.ivLength] bytes.
/// - MESSAGE's length is [CipherText.messageLength] bytes. /// - MESSAGE's length is [CipherText.messageLength] bytes.
/// - TAG's length is [CipherText.tagLength] bytes. /// - TAG's length is [CipherText.tagLength] bytes.
/// ///
/// Check [CipherTextWrapper] for more information. /// Check [CipherTextWrapper] for more information.
class CipherText extends ByteArray { class CipherText extends ByteArray {
final int _ivLength; final int _ivLength;
@ -45,35 +45,16 @@ class CipherText extends ByteArray {
); );
factory CipherText.fromBytes( factory CipherText.fromBytes(
Uint8List bytes, { int ivLength,
required int ivLength, int messageLength,
required int tagLength, int tagLength,
int? messageLength,
CipherAlgorithm? cipherAlgorithm, CipherAlgorithm? cipherAlgorithm,
}) { Uint8List bytes,
messageLength ??= bytes.length - ivLength - tagLength; ) {
if (ivLength.isNegative ||
messageLength.isNegative ||
tagLength.isNegative) {
throw NativeCryptoException(
message: 'Invalid length! Must be positive.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
if (bytes.isEmpty) {
throw NativeCryptoException(
message: 'Passed data is empty!',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
if (bytes.length != ivLength + messageLength + tagLength) { if (bytes.length != ivLength + messageLength + tagLength) {
throw NativeCryptoException( throw NativeCryptoException(
message: 'Invalid cipher text length! ' message: 'Invalid cipher text length! '
'Expected: ${ivLength + messageLength + tagLength} bytes ' 'Expected: ${ivLength + messageLength + tagLength} bytes',
'got: ${bytes.length} bytes.',
code: NativeCryptoExceptionCode.invalid_argument.code, code: NativeCryptoExceptionCode.invalid_argument.code,
); );
} }

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: 27/05/2022 13:43:29 // Last Modified: 26/05/2022 20:32:38
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@ -49,47 +49,39 @@ class CipherTextWrapper {
/// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is /// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is
/// not a valid [CipherText] or a [List] of [CipherText]. /// 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 tagLength, int messageLength,
int tagLength, {
CipherAlgorithm? cipherAlgorithm, CipherAlgorithm? cipherAlgorithm,
int? chunkSize, int? chunkSize,
}) { }) {
chunkSize ??= Cipher.bytesCountPerChunk; chunkSize ??= Cipher.bytesCountPerChunk;
Cipher.bytesCountPerChunk = chunkSize; Cipher.bytesCountPerChunk = chunkSize;
final int messageLength = bytes.length - ivLength - tagLength; if (bytes.length <= chunkSize) {
if (messageLength <= chunkSize) {
return CipherTextWrapper.single( return CipherTextWrapper.single(
CipherText.fromBytes( CipherText.fromBytes(
ivLength,
messageLength,
tagLength,
cipherAlgorithm,
bytes, bytes,
ivLength: ivLength,
tagLength: tagLength,
cipherAlgorithm: cipherAlgorithm,
), ),
); );
} else { } else {
final cipherTexts = <CipherText>[]; final cipherTexts = <CipherText>[];
for (var i = 0; i < bytes.length; i += chunkSize + ivLength + tagLength) { for (var i = 0; i < bytes.length; i += chunkSize) {
final chunk = bytes.trySublist(i, i + chunkSize + ivLength + tagLength); final chunk = bytes.sublist(i, i + chunkSize);
cipherTexts.add(
try { CipherText.fromBytes(
cipherTexts.add( ivLength,
CipherText.fromBytes( messageLength,
chunk, tagLength,
ivLength: ivLength, cipherAlgorithm,
tagLength: tagLength, chunk,
cipherAlgorithm: cipherAlgorithm, ),
), );
);
} on NativeCryptoException catch (e, s) {
throw NativeCryptoException(
message: '${e.message} on chunk #$i',
code: e.code,
stackTrace: s,
);
}
} }
return CipherTextWrapper.list(cipherTexts); return CipherTextWrapper.list(cipherTexts);
} }
@ -136,7 +128,7 @@ class CipherTextWrapper {
if (isSingle) { if (isSingle) {
return single.bytes; return single.bytes;
} else { } else {
return list.map((cipherText) => cipherText.bytes).toList().combine(); return list.map((cipherText) => cipherText.bytes).toList().sum();
} }
} }

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: cipher.dart // File: cipher.dart
// Created Date: 16/12/2021 16:28:00 // Created Date: 16/12/2021 16:28:00
// Last Modified: 26/05/2022 21:21:07 // Last Modified: 26/05/2022 17:38:26
// ----- // -----
// Copyright (c) 2021 // Copyright (c) 2021
@ -17,16 +17,12 @@ import 'package:native_crypto/src/utils/cipher_algorithm.dart';
/// 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. /// This interface is implemented by all the ciphers in NativeCrypto.
abstract class Cipher { abstract class Cipher {
static const int _bytesCountPerChunkDefault = 33554432; static int _bytesCountPerChunk = 33554432;
static int _bytesCountPerChunk = _bytesCountPerChunkDefault;
/// Returns the default number of bytes per chunk. /// Returns the size of a chunk of data
static int get defaultBytesCountPerChunk => _bytesCountPerChunkDefault;
/// Returns the size of a chunk of data
/// that can be processed by the [Cipher]. /// that can be processed by the [Cipher].
static int get bytesCountPerChunk => Cipher._bytesCountPerChunk; static int get bytesCountPerChunk => Cipher._bytesCountPerChunk;
@ -35,10 +31,11 @@ abstract class Cipher {
static set bytesCountPerChunk(int bytesCount) { static set bytesCountPerChunk(int bytesCount) {
_bytesCountPerChunk = bytesCount; _bytesCountPerChunk = bytesCount;
} }
/// Returns the standard algorithm for this [Cipher]. /// Returns the standard algorithm for this [Cipher].
CipherAlgorithm get algorithm; CipherAlgorithm get algorithm;
/// Encrypts the [data]. /// Encrypts the [data].
/// ///
/// Takes [Uint8List] data as parameter. /// Takes [Uint8List] data as parameter.

View File

@ -11,8 +11,8 @@ 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 (KDF) in NativeCrypto. /// Represents a Key Derivation Function (KDF) in NativeCrypto.
/// ///
/// [KeyDerivation] function is a function that takes some /// [KeyDerivation] function is a function that takes some
/// parameters and returns a [SecretKey]. /// parameters and returns a [SecretKey].
abstract class KeyDerivation { abstract class KeyDerivation {
/// Returns the standard algorithm for this key derivation function /// Returns the standard algorithm for this key derivation function

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: 26/05/2022 23:19:46 // Last Modified: 26/05/2022 18:51:59
// ----- // -----
// Copyright (c) 2021 // Copyright (c) 2021
@ -29,64 +29,31 @@ class Pbkdf2 extends KeyDerivation {
@override @override
KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2; KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2;
Pbkdf2({ Pbkdf2(
required int keyBytesCount, int keyBytesCount,
required int iterations, int iterations, {
HashAlgorithm algorithm = HashAlgorithm.sha256, HashAlgorithm algorithm = HashAlgorithm.sha256,
}) : _keyBytesCount = keyBytesCount, }) : _keyBytesCount = keyBytesCount,
_iterations = iterations, _iterations = iterations,
_hash = algorithm { _hash = algorithm;
if (keyBytesCount < 0) {
throw NativeCryptoException(
message: 'keyBytesCount must be positive.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
if (iterations <= 0) {
throw NativeCryptoException(
message: 'iterations must be strictly positive.',
code: NativeCryptoExceptionCode.invalid_argument.code,
);
}
}
@override @override
Future<SecretKey> derive({String? password, String? salt}) async { Future<SecretKey> derive({String? password, String? salt}) async {
Uint8List? derivation; if (password == null || salt == null) {
if (_keyBytesCount == 0) {
return SecretKey(Uint8List(0));
}
if (password.isNull) {
throw NativeCryptoException( throw NativeCryptoException(
message: 'Password cannot be null.', message: 'Password and salt cannot be null. '
'Here is the password: $password, here is the salt: $salt',
code: NativeCryptoExceptionCode.invalid_argument.code, code: NativeCryptoExceptionCode.invalid_argument.code,
); );
} }
if (salt.isNull) { final Uint8List? derivation = await platform.pbkdf2(
throw NativeCryptoException( password,
message: 'Salt cannot be null.', salt,
code: NativeCryptoExceptionCode.invalid_argument.code, _keyBytesCount,
); _iterations,
} _hash.name,
);
try {
derivation = await platform.pbkdf2(
password!,
salt!,
_keyBytesCount,
_iterations,
_hash.name,
);
} catch (e, s) {
throw NativeCryptoException(
message: '$e',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
);
}
if (derivation.isNull) { if (derivation.isNull) {
throw NativeCryptoException( throw NativeCryptoException(

View File

@ -3,7 +3,7 @@
// ----- // -----
// 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 23:13:10 // Last Modified: 26/05/2022 19:26:35
// ----- // -----
// Copyright (c) 2021 // Copyright (c) 2021
@ -28,10 +28,6 @@ class SecretKey extends BaseKey {
static Future<SecretKey> fromSecureRandom(int bitsCount) async { static Future<SecretKey> fromSecureRandom(int bitsCount) async {
Uint8List? key; Uint8List? key;
if (bitsCount == 0) {
return SecretKey(Uint8List(0));
}
try { try {
key = await platform.generateSecretKey(bitsCount); key = await platform.generateSecretKey(bitsCount);
} catch (e, s) { } catch (e, s) {

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: 27/05/2022 12:26:55 // Last Modified: 26/05/2022 18:52:48
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@ -19,7 +19,7 @@ extension ObjectX on Object? {
/// Returns `true` if the object is **not** `null`. /// Returns `true` if the object is **not** `null`.
bool get isNotNull => this != null; bool get isNotNull => this != null;
/// Prints the object to the console. /// Prints the object to the console.
void log() => developer.log(toString()); void log() => developer.log(toString());
} }
@ -30,10 +30,14 @@ 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 combine() { Uint8List sum() {
if (isEmpty) return Uint8List(0); for (var i = 1; i < length; i++) {
return reduce((value, element) => value.plus(element)); first.addAll(this[i]);
removeAt(i);
}
return first;
} }
} }
@ -86,16 +90,6 @@ extension Uint8ListX on Uint8List {
} }
/// Returns a concatenation of this with the other [Uint8List]. /// Returns a concatenation of this with the other [Uint8List].
Uint8List plus(final Uint8List other) => [...this, ...other].toTypedList(); Uint8List operator +(final Uint8List other) =>
[...this, ...other].toTypedList();
/// Returns a sublist of this from the [start] index to the [end] index.
/// If [end] is greater than the length of the list, it is set to the length
Uint8List trySublist(int start, [int? end]) {
if (isEmpty) return this;
int ending = end ?? length;
if (ending > length) ending = length;
return sublist(start, ending);
}
} }

View File

@ -3,48 +3,23 @@
// ----- // -----
// 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: 26/05/2022 22:59:04 // Last Modified: 26/05/2022 18:53:38
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
import 'dart:typed_data'; import 'dart:typed_data';
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';
/// Defines the hash algorithms. /// Defines the hash algorithms.
enum HashAlgorithm { enum HashAlgorithm {
sha256, sha256,
sha384, sha384,
sha512; sha512;
/// Digest the [data] using this [HashAlgorithm]. /// Digest the [data] using this [HashAlgorithm].
Future<Uint8List> digest(Uint8List data) async { Future<Uint8List> digest(Uint8List data) async {
Uint8List? hash; final Uint8List hash = (await platform.digest(data, name)) ?? Uint8List(0);
try {
hash = await platform.digest(data, name);
} catch (e, s) {
throw NativeCryptoException(
message: '$e',
code: NativeCryptoExceptionCode.platform_throws.code,
stackTrace: s,
);
}
if (hash.isNull) {
throw NativeCryptoException(
message: 'Failed to digest data! Platform returned null.',
code: NativeCryptoExceptionCode.platform_returned_null.code,
);
}
if (hash!.isEmpty) {
throw NativeCryptoException(
message: 'Failed to digest data! Platform returned no data.',
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
);
}
return hash; return hash;
} }

View File

@ -1,323 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: aes_cipher_test.dart
// Created Date: 26/05/2022 23:20:53
// Last Modified: 27/05/2022 16:39:44
// -----
// Copyright (c) 2022
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:native_crypto/native_crypto.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
import '../mocks/mock_native_crypto_platform.dart';
void main() {
final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform();
NativeCryptoPlatform.instance = mock;
setUp(() {
Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk;
});
group('Constructor', () {
test('throws on invalid key length', () {
expect(
() => AES(SecretKey(Uint8List(0))),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_key_length',
)
.having(
(e) => e.message,
'message',
contains('Invalid key'),
),
),
);
});
test('creates a valid instance', () {
expect(
AES(
SecretKey(Uint8List(16)),
),
isA<AES>(),
);
});
});
group('encrypt', () {
test('returns a valid cipher text wrapper', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(16 + 28));
final aes = AES(SecretKey(Uint8List(16)));
expect(
await aes.encrypt(Uint8List(16)),
isA<CipherTextWrapper>().having((e) => e.isSingle, 'is single', isTrue),
);
});
test('returns a valid cipher text with multiple chunks', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(16 + 28)); // Returns 1 encrypted chunk
Cipher.bytesCountPerChunk = 16;
final aes = AES(SecretKey(Uint8List(16)));
expect(
await aes.encrypt(Uint8List(16 * 3)),
isA<CipherTextWrapper>().having((e) => e.isList, 'is list', isTrue),
);
});
test('handles returning empty list', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(0));
final aes = AES(SecretKey(Uint8List(16)));
await expectLater(
() => aes.encrypt(Uint8List(16)),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_empty_data',
),
),
);
});
test('handles returning null', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => null);
final aes = AES(SecretKey(Uint8List(16)));
await expectLater(
() => aes.encrypt(Uint8List(16)),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
),
),
);
});
test('handles throwing PlatformException', () async {
mock
..setEncryptExpectations(
data: Uint8List(16),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(
() => throw PlatformException(
code: 'native_crypto',
message: 'dummy error',
),
);
final aes = AES(SecretKey(Uint8List(16)));
await expectLater(
() => aes.encrypt(Uint8List(16)),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
contains(
'PlatformException(native_crypto, dummy error, null, null)',
),
)
.having(
(e) => e.code,
'code',
'platform_throws',
),
),
);
});
});
group('decrypt', () {
test('returns a valid Uint8List', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(16));
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
expect(
await aes.decrypt(wrapper),
isA<Uint8List>().having((e) => e.length, 'length', 16),
);
});
test('returns a valid Uint8List on decrypting multiple chunks', () async {
const int chunkSize = 8;
mock
..setDecryptExpectations(
data: Uint8List(chunkSize + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(chunkSize));
Cipher.bytesCountPerChunk = chunkSize;
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List((chunkSize + 28) * 3);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
expect(
await aes.decrypt(wrapper),
isA<Uint8List>().having((e) => e.length, 'length', chunkSize * 3),
);
});
test('handles returning empty list', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => Uint8List(0));
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
await expectLater(
() => aes.decrypt(wrapper),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_empty_data',
),
),
);
});
test('handles returning null', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(() => null);
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
await expectLater(
() => aes.decrypt(wrapper),
throwsA(
isA<NativeCryptoException>().having(
(e) => e.code,
'code',
'platform_returned_null',
),
),
);
});
test('handles throwing PlatformException', () async {
mock
..setDecryptExpectations(
data: Uint8List(16 + 28),
key: Uint8List(16),
algorithm: 'aes',
)
..setResponse(
() => throw PlatformException(
code: 'native_crypto',
message: 'dummy error',
),
);
final aes = AES(SecretKey(Uint8List(16)));
final bytes = Uint8List(16 + 28);
final wrapper = CipherTextWrapper.fromBytes(
bytes,
ivLength: 12,
tagLength: 16,
);
await expectLater(
() => aes.decrypt(wrapper),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.message,
'message',
contains(
'PlatformException(native_crypto, dummy error, null, null)',
),
)
.having(
(e) => e.code,
'code',
'platform_throws',
),
),
);
});
});
}

View File

@ -157,7 +157,7 @@ void main() {
expect(cipherText.cipherAlgorithm, CipherAlgorithm.aes); expect(cipherText.cipherAlgorithm, CipherAlgorithm.aes);
}); });
}); });
group('Lengths', () { group('Lengths', () {
test('get.ivLength returns the expected value', () { test('get.ivLength returns the expected value', () {
final CipherText cipherText = CipherText.fromBytes( final CipherText cipherText = CipherText.fromBytes(

View File

@ -3,7 +3,7 @@
// ----- // -----
// File: cipher_text_wrapper_test.dart // File: cipher_text_wrapper_test.dart
// Created Date: 26/05/2022 21:35:41 // Created Date: 26/05/2022 21:35:41
// Last Modified: 27/05/2022 13:46:54 // Last Modified: 26/05/2022 22:27:31
// ----- // -----
// Copyright (c) 2022 // Copyright (c) 2022
@ -293,6 +293,7 @@ void main() {
final wrapper = CipherTextWrapper.fromBytes( final wrapper = CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3]), Uint8List.fromList([1, 2, 3]),
ivLength: 1, ivLength: 1,
messageLength: 1,
tagLength: 1, tagLength: 1,
); );
expect(wrapper.isSingle, isTrue); expect(wrapper.isSingle, isTrue);
@ -300,10 +301,11 @@ void main() {
}); });
test('creates list from bytes when too big', () { test('creates list from bytes when too big', () {
Cipher.bytesCountPerChunk = 1; Cipher.bytesCountPerChunk = 3;
final wrapper = CipherTextWrapper.fromBytes( final wrapper = CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3, 4, 5, 6]), Uint8List.fromList([1, 2, 3, 4, 5, 6]),
ivLength: 1, ivLength: 1,
messageLength: 1,
tagLength: 1, tagLength: 1,
); );
expect(wrapper.isList, isTrue); expect(wrapper.isList, isTrue);
@ -315,35 +317,11 @@ void main() {
CipherTextWrapper.fromBytes( CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3]), Uint8List.fromList([1, 2, 3]),
ivLength: 1, ivLength: 1,
messageLength: 1,
tagLength: 1, tagLength: 1,
chunkSize: 3, chunkSize: 3,
); );
expect(Cipher.bytesCountPerChunk, 3); expect(Cipher.bytesCountPerChunk, 3);
}); });
test('throws if trying to build list with bad parameters', () {
Cipher.bytesCountPerChunk = 1; // length of a message
expect(
() => CipherTextWrapper.fromBytes(
Uint8List.fromList([1, 2, 3, 4, 5, 6]),
ivLength: 2,
tagLength: 1,
),
throwsA(
isA<NativeCryptoException>()
.having(
(e) => e.code,
'code',
'invalid_argument',
)
.having(
(e) => e.message,
'message',
contains('on chunk #'),
),
),
);
});
}); });
} }

View File

@ -150,7 +150,7 @@ void main() {
], ],
); );
}); });
test('decrypt', () async { test('decrypt', () async {
await nativeCrypto.decrypt( await nativeCrypto.decrypt(
Uint8List(0), Uint8List(0),
@ -171,5 +171,6 @@ void main() {
], ],
); );
}); });
}); });
} }