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:
|
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: |
|
||||||
|
@ -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
|
benchmarkStatus.appendln(
|
||||||
.appendln('[Benchmark] ${size}MiB => Decryption took $benchmark ms');
|
'[Benchmark] ${size}MiB => Decryption took $benchmark ms');
|
||||||
csvLine.write(';$benchmark');
|
csvLine.write(';$benchmark');
|
||||||
}
|
}
|
||||||
csv += csvLine.toString() + '\n';
|
csv += csvLine.toString() + '\n';
|
||||||
|
@ -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: 26/05/2022 20:39:37
|
// Last Modified: 27/05/2022 16:42:10
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2021
|
// Copyright (c) 2021
|
||||||
|
|
||||||
@ -26,7 +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()..text = 'PlainText';
|
final TextEditingController _plainTextController = TextEditingController()
|
||||||
|
..text = 'PlainText';
|
||||||
CipherTextWrapper? cipherText;
|
CipherTextWrapper? cipherText;
|
||||||
|
|
||||||
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
|
Future<void> _encrypt(WidgetRef ref, Cipher cipher) async {
|
||||||
@ -58,9 +59,8 @@ class CipherPage extends ConsumerWidget {
|
|||||||
// Recreate cipher text with altered data
|
// Recreate cipher text with altered data
|
||||||
cipherText = CipherTextWrapper.fromBytes(
|
cipherText = CipherTextWrapper.fromBytes(
|
||||||
_altered,
|
_altered,
|
||||||
12,
|
ivLength: AESMode.gcm.ivLength,
|
||||||
_altered.length - 28,
|
tagLength: AESMode.gcm.tagLength,
|
||||||
16,
|
|
||||||
);
|
);
|
||||||
encryptionStatus.print('String successfully encrypted:\n');
|
encryptionStatus.print('String successfully encrypted:\n');
|
||||||
|
|
||||||
|
@ -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 20:30:31
|
// Last Modified: 26/05/2022 21:09:47
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2021
|
// Copyright (c) 2021
|
||||||
|
|
||||||
@ -26,8 +26,10 @@ 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()..text = 'Password';
|
final TextEditingController _pwdTextController = TextEditingController()
|
||||||
final TextEditingController _messageTextController = TextEditingController()..text = 'Message';
|
..text = 'Password';
|
||||||
|
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;
|
||||||
@ -50,7 +52,11 @@ 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(32, 1000, algorithm: HashAlgorithm.sha512);
|
Pbkdf2 _pbkdf2 = Pbkdf2(
|
||||||
|
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.');
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
// -----
|
// -----
|
||||||
// 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: 24/05/2022 17:15:22
|
// Last Modified: 27/05/2022 17:36:31
|
||||||
// -----
|
// -----
|
||||||
// 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';
|
||||||
|
@ -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: 26/05/2022 19:43:22
|
// Last Modified: 27/05/2022 12:13:28
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// 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/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.
|
||||||
@ -42,7 +46,6 @@ class AES implements Cipher {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!mode.supportedPaddings.contains(padding)) {
|
if (!mode.supportedPaddings.contains(padding)) {
|
||||||
throw NativeCryptoException(
|
throw NativeCryptoException(
|
||||||
message: 'Invalid padding! '
|
message: 'Invalid padding! '
|
||||||
@ -52,13 +55,25 @@ class AES implements Cipher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> _decrypt(CipherText cipherText,
|
Future<Uint8List> _decrypt(
|
||||||
{int chunkCount = 0,}) async {
|
CipherText cipherText, {
|
||||||
final Uint8List? decrypted = await platform.decrypt(
|
int chunkCount = 0,
|
||||||
cipherText.bytes,
|
}) async {
|
||||||
_key.bytes,
|
Uint8List? decrypted;
|
||||||
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(
|
||||||
@ -76,11 +91,21 @@ class AES implements Cipher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async {
|
Future<CipherText> _encrypt(Uint8List data, {int chunkCount = 0}) async {
|
||||||
final Uint8List? encrypted = await platform.encrypt(
|
Uint8List? encrypted;
|
||||||
data,
|
|
||||||
_key.bytes,
|
try {
|
||||||
algorithm.name,
|
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) {
|
if (encrypted.isNull) {
|
||||||
throw NativeCryptoException(
|
throw NativeCryptoException(
|
||||||
@ -93,13 +118,21 @@ class AES implements Cipher {
|
|||||||
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
|
code: NativeCryptoExceptionCode.platform_returned_empty_data.code,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return CipherText.fromBytes(
|
try {
|
||||||
12,
|
return CipherText.fromBytes(
|
||||||
encrypted.length - 28,
|
encrypted,
|
||||||
16,
|
ivLength: 12,
|
||||||
CipherAlgorithm.aes,
|
messageLength: encrypted.length - 28,
|
||||||
encrypted,
|
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
|
@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();
|
||||||
|
@ -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 18:41:31
|
// Last Modified: 26/05/2022 21:03:26
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
@ -11,10 +11,20 @@ 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]);
|
gcm([AESPadding.none], 12, 16);
|
||||||
|
|
||||||
/// 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;
|
||||||
|
|
||||||
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
|
// 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 19:43:57
|
// Last Modified: 27/05/2022 12:09:47
|
||||||
// -----
|
// -----
|
||||||
// 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,16 +45,35 @@ class CipherText extends ByteArray {
|
|||||||
);
|
);
|
||||||
|
|
||||||
factory CipherText.fromBytes(
|
factory CipherText.fromBytes(
|
||||||
int ivLength,
|
Uint8List bytes, {
|
||||||
int messageLength,
|
required int ivLength,
|
||||||
int tagLength,
|
required 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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 20:32:38
|
// Last Modified: 27/05/2022 13:43:29
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
@ -49,39 +49,47 @@ 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, {
|
||||||
int ivLength,
|
required int ivLength,
|
||||||
int messageLength,
|
required int tagLength,
|
||||||
int tagLength, {
|
|
||||||
CipherAlgorithm? cipherAlgorithm,
|
CipherAlgorithm? cipherAlgorithm,
|
||||||
int? chunkSize,
|
int? chunkSize,
|
||||||
}) {
|
}) {
|
||||||
chunkSize ??= Cipher.bytesCountPerChunk;
|
chunkSize ??= Cipher.bytesCountPerChunk;
|
||||||
Cipher.bytesCountPerChunk = chunkSize;
|
Cipher.bytesCountPerChunk = chunkSize;
|
||||||
|
|
||||||
if (bytes.length <= chunkSize) {
|
final int messageLength = bytes.length - ivLength - tagLength;
|
||||||
|
|
||||||
|
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) {
|
for (var i = 0; i < bytes.length; i += chunkSize + ivLength + tagLength) {
|
||||||
final chunk = bytes.sublist(i, i + chunkSize);
|
final chunk = bytes.trySublist(i, i + chunkSize + ivLength + tagLength);
|
||||||
cipherTexts.add(
|
|
||||||
CipherText.fromBytes(
|
try {
|
||||||
ivLength,
|
cipherTexts.add(
|
||||||
messageLength,
|
CipherText.fromBytes(
|
||||||
tagLength,
|
chunk,
|
||||||
cipherAlgorithm,
|
ivLength: ivLength,
|
||||||
chunk,
|
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);
|
return CipherTextWrapper.list(cipherTexts);
|
||||||
}
|
}
|
||||||
@ -128,7 +136,7 @@ class CipherTextWrapper {
|
|||||||
if (isSingle) {
|
if (isSingle) {
|
||||||
return single.bytes;
|
return single.bytes;
|
||||||
} else {
|
} else {
|
||||||
return list.map((cipherText) => cipherText.bytes).toList().sum();
|
return list.map((cipherText) => cipherText.bytes).toList().combine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 17:38:26
|
// Last Modified: 26/05/2022 21:21:07
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2021
|
// 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
|
/// 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 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].
|
/// that can be processed by the [Cipher].
|
||||||
static int get bytesCountPerChunk => Cipher._bytesCountPerChunk;
|
static int get bytesCountPerChunk => Cipher._bytesCountPerChunk;
|
||||||
|
|
||||||
@ -31,11 +35,10 @@ 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.
|
||||||
|
@ -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
|
||||||
|
@ -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 18:51:59
|
// Last Modified: 26/05/2022 23:19:46
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2021
|
// Copyright (c) 2021
|
||||||
|
|
||||||
@ -29,31 +29,64 @@ class Pbkdf2 extends KeyDerivation {
|
|||||||
@override
|
@override
|
||||||
KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2;
|
KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2;
|
||||||
|
|
||||||
Pbkdf2(
|
Pbkdf2({
|
||||||
int keyBytesCount,
|
required int keyBytesCount,
|
||||||
int iterations, {
|
required 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) {
|
||||||
@override
|
|
||||||
Future<SecretKey> derive({String? password, String? salt}) async {
|
|
||||||
if (password == null || salt == null) {
|
|
||||||
throw NativeCryptoException(
|
throw NativeCryptoException(
|
||||||
message: 'Password and salt cannot be null. '
|
message: 'keyBytesCount must be positive.',
|
||||||
'Here is the password: $password, here is the salt: $salt',
|
|
||||||
code: NativeCryptoExceptionCode.invalid_argument.code,
|
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Uint8List? derivation = await platform.pbkdf2(
|
if (iterations <= 0) {
|
||||||
password,
|
throw NativeCryptoException(
|
||||||
salt,
|
message: 'iterations must be strictly positive.',
|
||||||
_keyBytesCount,
|
code: NativeCryptoExceptionCode.invalid_argument.code,
|
||||||
_iterations,
|
);
|
||||||
_hash.name,
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
@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) {
|
if (derivation.isNull) {
|
||||||
throw NativeCryptoException(
|
throw NativeCryptoException(
|
||||||
|
@ -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 19:26:35
|
// Last Modified: 26/05/2022 23:13:10
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2021
|
// Copyright (c) 2021
|
||||||
|
|
||||||
@ -28,6 +28,10 @@ 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) {
|
||||||
|
@ -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 18:52:48
|
// Last Modified: 27/05/2022 12:26:55
|
||||||
// -----
|
// -----
|
||||||
// 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,14 +30,10 @@ 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 combine() {
|
||||||
for (var i = 1; i < length; i++) {
|
if (isEmpty) return Uint8List(0);
|
||||||
first.addAll(this[i]);
|
return reduce((value, element) => value.plus(element));
|
||||||
removeAt(i);
|
|
||||||
}
|
|
||||||
return first;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +86,16 @@ extension Uint8ListX on Uint8List {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a concatenation of this with the other [Uint8List].
|
/// Returns a concatenation of this with the other [Uint8List].
|
||||||
Uint8List operator +(final Uint8List other) =>
|
Uint8List plus(final Uint8List other) => [...this, ...other].toTypedList();
|
||||||
[...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
|
// 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 18:53:38
|
// Last Modified: 26/05/2022 22:59:04
|
||||||
// -----
|
// -----
|
||||||
// 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 {
|
||||||
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;
|
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);
|
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(
|
||||||
|
@ -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: 26/05/2022 22:27:31
|
// Last Modified: 27/05/2022 13:46:54
|
||||||
// -----
|
// -----
|
||||||
// Copyright (c) 2022
|
// Copyright (c) 2022
|
||||||
|
|
||||||
@ -293,7 +293,6 @@ 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);
|
||||||
@ -301,11 +300,10 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('creates list from bytes when too big', () {
|
test('creates list from bytes when too big', () {
|
||||||
Cipher.bytesCountPerChunk = 3;
|
Cipher.bytesCountPerChunk = 1;
|
||||||
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);
|
||||||
@ -317,11 +315,35 @@ 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 #'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ void main() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('decrypt', () async {
|
test('decrypt', () async {
|
||||||
await nativeCrypto.decrypt(
|
await nativeCrypto.decrypt(
|
||||||
Uint8List(0),
|
Uint8List(0),
|
||||||
@ -171,6 +171,5 @@ void main() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user