diff --git a/packages/native_crypto/example/lib/pages/cipher_page.dart b/packages/native_crypto/example/lib/pages/cipher_page.dart index 5733563..81b3fc4 100644 --- a/packages/native_crypto/example/lib/pages/cipher_page.dart +++ b/packages/native_crypto/example/lib/pages/cipher_page.dart @@ -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: 26/05/2022 21:07:54 // ----- // 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 _encrypt(WidgetRef ref, Cipher cipher) async { @@ -58,9 +59,10 @@ class CipherPage extends ConsumerWidget { // Recreate cipher text with altered data cipherText = CipherTextWrapper.fromBytes( _altered, - 12, - _altered.length - 28, - 16, + ivLength: AESMode.gcm.ivLength, + messageLength: + _altered.length - (AESMode.gcm.ivLength + AESMode.gcm.tagLength), + tagLength: AESMode.gcm.tagLength, ); encryptionStatus.print('String successfully encrypted:\n'); diff --git a/packages/native_crypto/example/lib/pages/kdf_page.dart b/packages/native_crypto/example/lib/pages/kdf_page.dart index da04947..371d883 100644 --- a/packages/native_crypto/example/lib/pages/kdf_page.dart +++ b/packages/native_crypto/example/lib/pages/kdf_page.dart @@ -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 _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.'); diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes.dart b/packages/native_crypto/lib/src/ciphers/aes/aes.dart index 9a28105..49644fd 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes.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: 26/05/2022 21:07:01 // ----- // 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. @@ -94,11 +98,11 @@ class AES implements Cipher { ); } else { return CipherText.fromBytes( - 12, - encrypted.length - 28, - 16, - CipherAlgorithm.aes, encrypted, + ivLength: 12, + messageLength: encrypted.length - 28, + tagLength: 16, + cipherAlgorithm: CipherAlgorithm.aes, ); } } diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart index a16d414..4bbc7c4 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart @@ -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 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, + ]); } diff --git a/packages/native_crypto/lib/src/core/cipher_text.dart b/packages/native_crypto/lib/src/core/cipher_text.dart index d20f344..e0c62e3 100644 --- a/packages/native_crypto/lib/src/core/cipher_text.dart +++ b/packages/native_crypto/lib/src/core/cipher_text.dart @@ -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: 26/05/2022 22:20:40 // ----- // 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,33 @@ class CipherText extends ByteArray { ); factory CipherText.fromBytes( - int ivLength, - int messageLength, - int tagLength, + Uint8List bytes, { + required int ivLength, + required int messageLength, + required int tagLength, CipherAlgorithm? cipherAlgorithm, - Uint8List bytes, - ) { + }) { + 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, ); } diff --git a/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart b/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart index 08dc915..285ba6a 100644 --- a/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart +++ b/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart @@ -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: 26/05/2022 22:11:42 // ----- // Copyright (c) 2022 @@ -49,10 +49,10 @@ 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 messageLength, + required int tagLength, CipherAlgorithm? cipherAlgorithm, int? chunkSize, }) { @@ -62,11 +62,11 @@ class CipherTextWrapper { if (bytes.length <= chunkSize) { return CipherTextWrapper.single( CipherText.fromBytes( - ivLength, - messageLength, - tagLength, - cipherAlgorithm, bytes, + ivLength: ivLength, + messageLength: messageLength, + tagLength: tagLength, + cipherAlgorithm: cipherAlgorithm, ), ); } else { @@ -75,11 +75,11 @@ class CipherTextWrapper { final chunk = bytes.sublist(i, i + chunkSize); cipherTexts.add( CipherText.fromBytes( - ivLength, - messageLength, - tagLength, - cipherAlgorithm, chunk, + ivLength: ivLength, + messageLength: messageLength, + tagLength: tagLength, + cipherAlgorithm: cipherAlgorithm, ), ); } @@ -128,7 +128,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(); } } diff --git a/packages/native_crypto/lib/src/interfaces/cipher.dart b/packages/native_crypto/lib/src/interfaces/cipher.dart index c95f34b..5d87661 100644 --- a/packages/native_crypto/lib/src/interfaces/cipher.dart +++ b/packages/native_crypto/lib/src/interfaces/cipher.dart @@ -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 @@ -20,7 +20,11 @@ import 'package:native_crypto/src/utils/cipher_algorithm.dart'; /// /// 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 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]. diff --git a/packages/native_crypto/lib/src/kdf/pbkdf2.dart b/packages/native_crypto/lib/src/kdf/pbkdf2.dart index ae7d3b4..8ccdadd 100644 --- a/packages/native_crypto/lib/src/kdf/pbkdf2.dart +++ b/packages/native_crypto/lib/src/kdf/pbkdf2.dart @@ -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 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 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( diff --git a/packages/native_crypto/lib/src/keys/secret_key.dart b/packages/native_crypto/lib/src/keys/secret_key.dart index f660181..e30b87b 100644 --- a/packages/native_crypto/lib/src/keys/secret_key.dart +++ b/packages/native_crypto/lib/src/keys/secret_key.dart @@ -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 fromSecureRandom(int bitsCount) async { Uint8List? key; + if (bitsCount == 0) { + return SecretKey(Uint8List(0)); + } + try { key = await platform.generateSecretKey(bitsCount); } catch (e, s) { diff --git a/packages/native_crypto/lib/src/utils/extensions.dart b/packages/native_crypto/lib/src/utils/extensions.dart index b6399e0..5daa30b 100644 --- a/packages/native_crypto/lib/src/utils/extensions.dart +++ b/packages/native_crypto/lib/src/utils/extensions.dart @@ -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: 26/05/2022 22:15:33 // ----- // 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 { } extension ListUint8ListX on List { - /// 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,5 @@ 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(); } diff --git a/packages/native_crypto/lib/src/utils/hash_algorithm.dart b/packages/native_crypto/lib/src/utils/hash_algorithm.dart index d9c6ced..4538ef5 100644 --- a/packages/native_crypto/lib/src/utils/hash_algorithm.dart +++ b/packages/native_crypto/lib/src/utils/hash_algorithm.dart @@ -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 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; }