Compare commits
5 Commits
96f9aad1b3
...
cf4227fb58
Author | SHA1 | Date | |
---|---|---|---|
cf4227fb58 | |||
ac35cd89ca | |||
1b00d20ec5 | |||
cefa73ec3d | |||
ebdcf00c15 |
13
.drone.yml
Normal file
13
.drone.yml
Normal file
@ -0,0 +1,13 @@
|
||||
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
|
18
melos.yaml
18
melos.yaml
@ -15,6 +15,24 @@ scripts:
|
||||
lint:all:
|
||||
run: melos run analyze && melos run format
|
||||
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:
|
||||
run: |
|
||||
|
@ -91,8 +91,8 @@ class BenchmarkPage extends ConsumerWidget {
|
||||
after = DateTime.now();
|
||||
benchmark =
|
||||
after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
|
||||
benchmarkStatus
|
||||
.appendln('[Benchmark] ${size}MiB => Decryption took $benchmark ms');
|
||||
benchmarkStatus.appendln(
|
||||
'[Benchmark] ${size}MiB => Decryption took $benchmark ms');
|
||||
csvLine.write(';$benchmark');
|
||||
}
|
||||
csv += csvLine.toString() + '\n';
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: cipher_page.dart
|
||||
// Created Date: 28/12/2021 13:33:15
|
||||
// Last Modified: 26/05/2022 20:39:37
|
||||
// Last Modified: 27/05/2022 16:42:10
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -26,7 +26,8 @@ class CipherPage extends ConsumerWidget {
|
||||
final Output encryptionStatus = Output();
|
||||
final Output decryptionStatus = Output();
|
||||
|
||||
final TextEditingController _plainTextController = TextEditingController()..text = 'PlainText';
|
||||
final TextEditingController _plainTextController = TextEditingController()
|
||||
..text = 'PlainText';
|
||||
CipherTextWrapper? cipherText;
|
||||
|
||||
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
|
||||
@ -58,9 +59,8 @@ class CipherPage extends ConsumerWidget {
|
||||
// Recreate cipher text with altered data
|
||||
cipherText = CipherTextWrapper.fromBytes(
|
||||
_altered,
|
||||
12,
|
||||
_altered.length - 28,
|
||||
16,
|
||||
ivLength: AESMode.gcm.ivLength,
|
||||
tagLength: AESMode.gcm.tagLength,
|
||||
);
|
||||
encryptionStatus.print('String successfully encrypted:\n');
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: kdf_page.dart
|
||||
// Created Date: 28/12/2021 13:40:34
|
||||
// Last Modified: 26/05/2022 20:30:31
|
||||
// Last Modified: 26/05/2022 21:09:47
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -26,8 +26,10 @@ class KdfPage extends ConsumerWidget {
|
||||
final Output pbkdf2Status = Output();
|
||||
final Output hashStatus = Output(large: true);
|
||||
|
||||
final TextEditingController _pwdTextController = TextEditingController()..text = 'Password';
|
||||
final TextEditingController _messageTextController = TextEditingController()..text = 'Message';
|
||||
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;
|
||||
@ -50,7 +52,11 @@ class KdfPage extends ConsumerWidget {
|
||||
if (password.isEmpty) {
|
||||
pbkdf2Status.print('Password is empty');
|
||||
} else {
|
||||
Pbkdf2 _pbkdf2 = Pbkdf2(32, 1000, algorithm: HashAlgorithm.sha512);
|
||||
Pbkdf2 _pbkdf2 = Pbkdf2(
|
||||
keyBytesCount: 32,
|
||||
iterations: 1000,
|
||||
algorithm: HashAlgorithm.sha512,
|
||||
);
|
||||
SecretKey sk = await _pbkdf2.derive(password: password, salt: 'salt');
|
||||
state.setKey(sk);
|
||||
pbkdf2Status.print('Key successfully derived.');
|
||||
|
@ -3,10 +3,12 @@
|
||||
// -----
|
||||
// File: aes_gcm.dart
|
||||
// Created Date: 24/05/2022 16:34:54
|
||||
// Last Modified: 24/05/2022 17:15:22
|
||||
// Last Modified: 27/05/2022 17:36:31
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:pointycastle/export.dart';
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: aes.dart
|
||||
// Created Date: 16/12/2021 16:28:00
|
||||
// Last Modified: 26/05/2022 19:43:22
|
||||
// Last Modified: 27/05/2022 12:13:28
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -21,6 +21,10 @@ 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 'aes_key_size.dart';
|
||||
export 'aes_mode.dart';
|
||||
export 'aes_padding.dart';
|
||||
|
||||
/// An AES cipher.
|
||||
///
|
||||
/// [AES] is a [Cipher] that can be used to encrypt or decrypt data.
|
||||
@ -42,7 +46,6 @@ class AES implements Cipher {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (!mode.supportedPaddings.contains(padding)) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Invalid padding! '
|
||||
@ -52,13 +55,25 @@ class AES implements Cipher {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> _decrypt(CipherText cipherText,
|
||||
{int chunkCount = 0,}) async {
|
||||
final Uint8List? decrypted = await platform.decrypt(
|
||||
cipherText.bytes,
|
||||
_key.bytes,
|
||||
algorithm.name,
|
||||
);
|
||||
Future<Uint8List> _decrypt(
|
||||
CipherText cipherText, {
|
||||
int chunkCount = 0,
|
||||
}) async {
|
||||
Uint8List? decrypted;
|
||||
|
||||
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) {
|
||||
throw NativeCryptoException(
|
||||
@ -76,11 +91,21 @@ class AES implements Cipher {
|
||||
}
|
||||
|
||||
Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async {
|
||||
final Uint8List? encrypted = await platform.encrypt(
|
||||
data,
|
||||
_key.bytes,
|
||||
algorithm.name,
|
||||
);
|
||||
Uint8List? encrypted;
|
||||
|
||||
try {
|
||||
encrypted = await platform.encrypt(
|
||||
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) {
|
||||
throw NativeCryptoException(
|
||||
@ -93,13 +118,21 @@ class AES implements Cipher {
|
||||
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
|
||||
);
|
||||
} else {
|
||||
return CipherText.fromBytes(
|
||||
12,
|
||||
encrypted.length - 28,
|
||||
16,
|
||||
CipherAlgorithm.aes,
|
||||
encrypted,
|
||||
);
|
||||
try {
|
||||
return CipherText.fromBytes(
|
||||
encrypted,
|
||||
ivLength: 12,
|
||||
messageLength: encrypted.length - 28,
|
||||
tagLength: 16,
|
||||
cipherAlgorithm: CipherAlgorithm.aes,
|
||||
);
|
||||
} on NativeCryptoException catch (e, s) {
|
||||
throw NativeCryptoException(
|
||||
message: '${e.message} on chunk #$chunkCount',
|
||||
code: e.code,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +154,9 @@ class AES implements Cipher {
|
||||
|
||||
@override
|
||||
Future<CipherTextWrapper> encrypt(Uint8List data) async {
|
||||
if (data.isEmpty) {
|
||||
return CipherTextWrapper.empty();
|
||||
}
|
||||
CipherTextWrapper cipherTextWrapper;
|
||||
Uint8List dataToEncrypt;
|
||||
final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil();
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: aes_mode.dart
|
||||
// Created Date: 23/05/2022 22:09:16
|
||||
// Last Modified: 26/05/2022 18:41:31
|
||||
// Last Modified: 26/05/2022 21:03:26
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -11,10 +11,20 @@ import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
|
||||
|
||||
/// Defines the AES modes of operation.
|
||||
enum AESMode {
|
||||
gcm([AESPadding.none]);
|
||||
gcm([AESPadding.none], 12, 16);
|
||||
|
||||
/// Returns the list of supported [AESPadding] for this [AESMode].
|
||||
final List<AESPadding> supportedPaddings;
|
||||
|
||||
const AESMode(this.supportedPaddings);
|
||||
/// Returns the default IV length for this [AESMode].
|
||||
final int ivLength;
|
||||
|
||||
/// Returns the default tag length for this [AESMode].
|
||||
final int tagLength;
|
||||
|
||||
const AESMode(
|
||||
this.supportedPaddings, [
|
||||
this.ivLength = 16,
|
||||
this.tagLength = 0,
|
||||
]);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: cipher_text.dart
|
||||
// Created Date: 16/12/2021 16:59:53
|
||||
// Last Modified: 26/05/2022 19:43:57
|
||||
// Last Modified: 27/05/2022 12:09:47
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -27,7 +27,7 @@ import 'package:native_crypto_platform_interface/native_crypto_platform_interfac
|
||||
/// - IV's length is [CipherText.ivLength] bytes.
|
||||
/// - MESSAGE's length is [CipherText.messageLength] bytes.
|
||||
/// - TAG's length is [CipherText.tagLength] bytes.
|
||||
///
|
||||
///
|
||||
/// Check [CipherTextWrapper] for more information.
|
||||
class CipherText extends ByteArray {
|
||||
final int _ivLength;
|
||||
@ -45,16 +45,35 @@ class CipherText extends ByteArray {
|
||||
);
|
||||
|
||||
factory CipherText.fromBytes(
|
||||
int ivLength,
|
||||
int messageLength,
|
||||
int tagLength,
|
||||
Uint8List bytes, {
|
||||
required int ivLength,
|
||||
required int tagLength,
|
||||
int? messageLength,
|
||||
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) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Invalid cipher text length! '
|
||||
'Expected: ${ivLength + messageLength + tagLength} bytes',
|
||||
'Expected: ${ivLength + messageLength + tagLength} bytes '
|
||||
'got: ${bytes.length} bytes.',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: cipher_text_wrapper.dart
|
||||
// Created Date: 26/05/2022 14:27:32
|
||||
// Last Modified: 26/05/2022 20:32:38
|
||||
// Last Modified: 27/05/2022 13:43:29
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -49,39 +49,47 @@ class CipherTextWrapper {
|
||||
/// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is
|
||||
/// not a valid [CipherText] or a [List] of [CipherText].
|
||||
factory CipherTextWrapper.fromBytes(
|
||||
Uint8List bytes,
|
||||
int ivLength,
|
||||
int messageLength,
|
||||
int tagLength, {
|
||||
Uint8List bytes, {
|
||||
required int ivLength,
|
||||
required int tagLength,
|
||||
CipherAlgorithm? cipherAlgorithm,
|
||||
int? chunkSize,
|
||||
}) {
|
||||
chunkSize ??= Cipher.bytesCountPerChunk;
|
||||
Cipher.bytesCountPerChunk = chunkSize;
|
||||
|
||||
if (bytes.length <= chunkSize) {
|
||||
final int messageLength = bytes.length - ivLength - tagLength;
|
||||
|
||||
if (messageLength <= chunkSize) {
|
||||
return CipherTextWrapper.single(
|
||||
CipherText.fromBytes(
|
||||
ivLength,
|
||||
messageLength,
|
||||
tagLength,
|
||||
cipherAlgorithm,
|
||||
bytes,
|
||||
ivLength: ivLength,
|
||||
tagLength: tagLength,
|
||||
cipherAlgorithm: cipherAlgorithm,
|
||||
),
|
||||
);
|
||||
} 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,
|
||||
),
|
||||
);
|
||||
for (var i = 0; i < bytes.length; i += chunkSize + ivLength + tagLength) {
|
||||
final chunk = bytes.trySublist(i, i + chunkSize + ivLength + tagLength);
|
||||
|
||||
try {
|
||||
cipherTexts.add(
|
||||
CipherText.fromBytes(
|
||||
chunk,
|
||||
ivLength: ivLength,
|
||||
tagLength: tagLength,
|
||||
cipherAlgorithm: cipherAlgorithm,
|
||||
),
|
||||
);
|
||||
} on NativeCryptoException catch (e, s) {
|
||||
throw NativeCryptoException(
|
||||
message: '${e.message} on chunk #$i',
|
||||
code: e.code,
|
||||
stackTrace: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
return CipherTextWrapper.list(cipherTexts);
|
||||
}
|
||||
@ -128,7 +136,7 @@ class CipherTextWrapper {
|
||||
if (isSingle) {
|
||||
return single.bytes;
|
||||
} else {
|
||||
return list.map((cipherText) => cipherText.bytes).toList().sum();
|
||||
return list.map((cipherText) => cipherText.bytes).toList().combine();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: cipher.dart
|
||||
// Created Date: 16/12/2021 16:28:00
|
||||
// Last Modified: 26/05/2022 17:38:26
|
||||
// Last Modified: 26/05/2022 21:21:07
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -17,12 +17,16 @@ import 'package:native_crypto/src/utils/cipher_algorithm.dart';
|
||||
/// 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 {
|
||||
static int _bytesCountPerChunk = 33554432;
|
||||
static const int _bytesCountPerChunkDefault = 33554432;
|
||||
static int _bytesCountPerChunk = _bytesCountPerChunkDefault;
|
||||
|
||||
/// Returns the size of a chunk of data
|
||||
/// Returns the default number of bytes per chunk.
|
||||
static int get defaultBytesCountPerChunk => _bytesCountPerChunkDefault;
|
||||
|
||||
/// Returns the size of a chunk of data
|
||||
/// that can be processed by the [Cipher].
|
||||
static int get bytesCountPerChunk => Cipher._bytesCountPerChunk;
|
||||
|
||||
@ -31,11 +35,10 @@ abstract class Cipher {
|
||||
static set bytesCountPerChunk(int bytesCount) {
|
||||
_bytesCountPerChunk = bytesCount;
|
||||
}
|
||||
|
||||
|
||||
/// Returns the standard algorithm for this [Cipher].
|
||||
CipherAlgorithm get algorithm;
|
||||
|
||||
|
||||
/// Encrypts the [data].
|
||||
///
|
||||
/// Takes [Uint8List] data as parameter.
|
||||
|
@ -11,8 +11,8 @@ import 'package:native_crypto/src/keys/secret_key.dart';
|
||||
import 'package:native_crypto/src/utils/kdf_algorithm.dart';
|
||||
|
||||
/// 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].
|
||||
abstract class KeyDerivation {
|
||||
/// Returns the standard algorithm for this key derivation function
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: pbkdf2.dart
|
||||
// Created Date: 17/12/2021 14:50:42
|
||||
// Last Modified: 26/05/2022 18:51:59
|
||||
// Last Modified: 26/05/2022 23:19:46
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -29,31 +29,64 @@ class Pbkdf2 extends KeyDerivation {
|
||||
@override
|
||||
KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2;
|
||||
|
||||
Pbkdf2(
|
||||
int keyBytesCount,
|
||||
int iterations, {
|
||||
Pbkdf2({
|
||||
required int keyBytesCount,
|
||||
required int iterations,
|
||||
HashAlgorithm algorithm = HashAlgorithm.sha256,
|
||||
}) : _keyBytesCount = keyBytesCount,
|
||||
_iterations = iterations,
|
||||
_hash = algorithm;
|
||||
|
||||
@override
|
||||
Future<SecretKey> derive({String? password, String? salt}) async {
|
||||
if (password == null || salt == null) {
|
||||
_hash = algorithm {
|
||||
if (keyBytesCount < 0) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Password and salt cannot be null. '
|
||||
'Here is the password: $password, here is the salt: $salt',
|
||||
message: 'keyBytesCount must be positive.',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
|
||||
final Uint8List? derivation = await platform.pbkdf2(
|
||||
password,
|
||||
salt,
|
||||
_keyBytesCount,
|
||||
_iterations,
|
||||
_hash.name,
|
||||
);
|
||||
if (iterations <= 0) {
|
||||
throw NativeCryptoException(
|
||||
message: 'iterations must be strictly positive.',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SecretKey> derive({String? password, String? salt}) async {
|
||||
Uint8List? derivation;
|
||||
|
||||
if (_keyBytesCount == 0) {
|
||||
return SecretKey(Uint8List(0));
|
||||
}
|
||||
if (password.isNull) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Password cannot be null.',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
|
||||
if (salt.isNull) {
|
||||
throw NativeCryptoException(
|
||||
message: 'Salt cannot be null.',
|
||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
throw NativeCryptoException(
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: secret_key.dart
|
||||
// Created Date: 28/12/2021 13:36:54
|
||||
// Last Modified: 26/05/2022 19:26:35
|
||||
// Last Modified: 26/05/2022 23:13:10
|
||||
// -----
|
||||
// Copyright (c) 2021
|
||||
|
||||
@ -28,6 +28,10 @@ class SecretKey extends BaseKey {
|
||||
|
||||
static Future<SecretKey> fromSecureRandom(int bitsCount) async {
|
||||
Uint8List? key;
|
||||
if (bitsCount == 0) {
|
||||
return SecretKey(Uint8List(0));
|
||||
}
|
||||
|
||||
try {
|
||||
key = await platform.generateSecretKey(bitsCount);
|
||||
} catch (e, s) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: extensions.dart
|
||||
// Created Date: 26/05/2022 12:12:48
|
||||
// Last Modified: 26/05/2022 18:52:48
|
||||
// Last Modified: 27/05/2022 12:26:55
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -19,7 +19,7 @@ extension ObjectX on Object? {
|
||||
|
||||
/// Returns `true` if the object is **not** `null`.
|
||||
bool get isNotNull => this != null;
|
||||
|
||||
|
||||
/// Prints the object to the console.
|
||||
void log() => developer.log(toString());
|
||||
}
|
||||
@ -30,14 +30,10 @@ 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]);
|
||||
removeAt(i);
|
||||
}
|
||||
return first;
|
||||
Uint8List combine() {
|
||||
if (isEmpty) return Uint8List(0);
|
||||
return reduce((value, element) => value.plus(element));
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +86,16 @@ extension Uint8ListX on Uint8List {
|
||||
}
|
||||
|
||||
/// Returns a concatenation of this with the other [Uint8List].
|
||||
Uint8List operator +(final Uint8List other) =>
|
||||
[...this, ...other].toTypedList();
|
||||
Uint8List plus(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);
|
||||
}
|
||||
}
|
||||
|
@ -3,23 +3,48 @@
|
||||
// -----
|
||||
// File: hash_algorithm.dart
|
||||
// Created Date: 23/05/2022 22:01:59
|
||||
// Last Modified: 26/05/2022 18:53:38
|
||||
// Last Modified: 26/05/2022 22:59:04
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
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.
|
||||
enum HashAlgorithm {
|
||||
sha256,
|
||||
sha384,
|
||||
sha512;
|
||||
|
||||
|
||||
/// Digest the [data] using this [HashAlgorithm].
|
||||
Future<Uint8List> digest(Uint8List data) async {
|
||||
final Uint8List hash = (await platform.digest(data, name)) ?? Uint8List(0);
|
||||
Uint8List? hash;
|
||||
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;
|
||||
}
|
||||
|
323
packages/native_crypto/test/src/aes_cipher_test.dart
Normal file
323
packages/native_crypto/test/src/aes_cipher_test.dart
Normal file
@ -0,0 +1,323 @@
|
||||
// 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',
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -157,7 +157,7 @@ void main() {
|
||||
expect(cipherText.cipherAlgorithm, CipherAlgorithm.aes);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
group('Lengths', () {
|
||||
test('get.ivLength returns the expected value', () {
|
||||
final CipherText cipherText = CipherText.fromBytes(
|
||||
|
@ -3,7 +3,7 @@
|
||||
// -----
|
||||
// File: cipher_text_wrapper_test.dart
|
||||
// Created Date: 26/05/2022 21:35:41
|
||||
// Last Modified: 26/05/2022 22:27:31
|
||||
// Last Modified: 27/05/2022 13:46:54
|
||||
// -----
|
||||
// Copyright (c) 2022
|
||||
|
||||
@ -293,7 +293,6 @@ void main() {
|
||||
final wrapper = CipherTextWrapper.fromBytes(
|
||||
Uint8List.fromList([1, 2, 3]),
|
||||
ivLength: 1,
|
||||
messageLength: 1,
|
||||
tagLength: 1,
|
||||
);
|
||||
expect(wrapper.isSingle, isTrue);
|
||||
@ -301,11 +300,10 @@ void main() {
|
||||
});
|
||||
|
||||
test('creates list from bytes when too big', () {
|
||||
Cipher.bytesCountPerChunk = 3;
|
||||
Cipher.bytesCountPerChunk = 1;
|
||||
final wrapper = CipherTextWrapper.fromBytes(
|
||||
Uint8List.fromList([1, 2, 3, 4, 5, 6]),
|
||||
ivLength: 1,
|
||||
messageLength: 1,
|
||||
tagLength: 1,
|
||||
);
|
||||
expect(wrapper.isList, isTrue);
|
||||
@ -317,11 +315,35 @@ void main() {
|
||||
CipherTextWrapper.fromBytes(
|
||||
Uint8List.fromList([1, 2, 3]),
|
||||
ivLength: 1,
|
||||
messageLength: 1,
|
||||
tagLength: 1,
|
||||
chunkSize: 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 #'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ void main() {
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test('decrypt', () async {
|
||||
await nativeCrypto.decrypt(
|
||||
Uint8List(0),
|
||||
@ -171,6 +171,5 @@ void main() {
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user