From 48ebabb54ca64d2c7ea2cb71c7d2d24b3d13bf25 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Thu, 26 May 2022 20:42:53 +0200 Subject: [PATCH] feat: rework bytearray and memory optimization, simplify API --- .../example/lib/pages/benchmark_page.dart | 10 +- .../example/lib/pages/cipher_page.dart | 41 +++-- .../example/lib/pages/kdf_page.dart | 10 +- packages/native_crypto/example/lib/utils.dart | 52 ------ .../native_crypto/lib/native_crypto_ext.dart | 11 ++ .../lib/src/builders/aes_builder.dart | 49 ------ .../lib/src/builders/builders.dart | 4 +- .../lib/src/builders/decryption_builder.dart | 46 ++++++ .../lib/src/ciphers/aes/aes.dart | 131 ++++++++++------ .../lib/src/ciphers/aes/aes_key_size.dart | 6 +- .../lib/src/ciphers/aes/aes_mode.dart | 13 +- .../lib/src/core/cipher_text.dart | 148 ++++++------------ .../lib/src/core/cipher_text_list.dart | 26 --- .../lib/src/core/cipher_text_wrapper.dart | 116 ++++++++++++-- packages/native_crypto/lib/src/core/core.dart | 4 +- .../lib/src/interfaces/base_key.dart | 22 +++ .../lib/src/interfaces/byte_array.dart | 11 +- .../lib/src/interfaces/cipher.dart | 30 ++-- .../lib/src/interfaces/interfaces.dart | 4 +- .../native_crypto/lib/src/interfaces/key.dart | 18 --- .../lib/src/interfaces/keyderivation.dart | 11 +- .../native_crypto/lib/src/kdf/pbkdf2.dart | 51 ++++-- .../lib/src/keys/secret_key.dart | 50 +++--- .../lib/src/utils/cipher_algorithm.dart | 4 +- .../lib/src/utils/extensions.dart | 4 +- .../lib/src/utils/hash_algorithm.dart | 5 +- .../lib/src/utils/kdf_algorithm.dart | 3 +- .../test/src/secret_key_test.dart | 12 +- 28 files changed, 485 insertions(+), 407 deletions(-) delete mode 100644 packages/native_crypto/example/lib/utils.dart create mode 100644 packages/native_crypto/lib/native_crypto_ext.dart delete mode 100644 packages/native_crypto/lib/src/builders/aes_builder.dart create mode 100644 packages/native_crypto/lib/src/builders/decryption_builder.dart delete mode 100644 packages/native_crypto/lib/src/core/cipher_text_list.dart create mode 100644 packages/native_crypto/lib/src/interfaces/base_key.dart delete mode 100644 packages/native_crypto/lib/src/interfaces/key.dart diff --git a/packages/native_crypto/example/lib/pages/benchmark_page.dart b/packages/native_crypto/example/lib/pages/benchmark_page.dart index 6cf3e63..ad1074f 100644 --- a/packages/native_crypto/example/lib/pages/benchmark_page.dart +++ b/packages/native_crypto/example/lib/pages/benchmark_page.dart @@ -3,7 +3,7 @@ // ----- // File: benchmark_page.dart // Created Date: 28/12/2021 15:12:39 -// Last Modified: 25/05/2022 17:16:12 +// Last Modified: 26/05/2022 20:19:28 // ----- // Copyright (c) 2021 @@ -40,7 +40,7 @@ class BenchmarkPage extends ConsumerWidget { return; } - List testedSizes = [2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50]; + List testedSizes = [2, 4, 8, 16, 32, 64, 128, 256]; int multiplier = pow(2, 20).toInt(); // MiB benchmarkStatus.print("[Benchmark] Sizes: ${testedSizes.join('/')}MiB\n"); @@ -86,7 +86,7 @@ class BenchmarkPage extends ConsumerWidget { if (usePc) { pc.decrypt(encryptedBigFile as Uint8List, state.secretKey.bytes); } else { - await cipher.decrypt(encryptedBigFile as CipherText); + await cipher.decrypt(encryptedBigFile as CipherTextWrapper); } after = DateTime.now(); benchmark = @@ -105,7 +105,7 @@ class BenchmarkPage extends ConsumerWidget { .appendln('[Benchmark] Finished: ${sum}MiB in $benchmark ms'); benchmarkStatus.appendln('[Benchmark] Check the console for csv data'); benchmarkStatus.appendln(csv); - print(csv); + debugPrint(csv); } void _clear() { @@ -135,7 +135,7 @@ class BenchmarkPage extends ConsumerWidget { } keyContent.print(state.secretKey.bytes.toString()); - AES cipher = AES(state.secretKey, AESMode.gcm); + AES cipher = AES(state.secretKey); return SingleChildScrollView( child: Padding( diff --git a/packages/native_crypto/example/lib/pages/cipher_page.dart b/packages/native_crypto/example/lib/pages/cipher_page.dart index e953a87..5733563 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: 25/05/2022 10:49:30 +// Last Modified: 26/05/2022 20:39:37 // ----- // Copyright (c) 2021 @@ -12,10 +12,10 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto/native_crypto_ext.dart'; import 'package:native_crypto_example/widgets/button.dart'; import '../session.dart'; -import '../utils.dart'; import '../widgets/output.dart'; // ignore: must_be_immutable @@ -26,8 +26,8 @@ class CipherPage extends ConsumerWidget { final Output encryptionStatus = Output(); final Output decryptionStatus = Output(); - final TextEditingController _plainTextController = TextEditingController(); - CipherText? cipherText; + final TextEditingController _plainTextController = TextEditingController()..text = 'PlainText'; + CipherTextWrapper? cipherText; Future _encrypt(WidgetRef ref, Cipher cipher) async { Session state = ref.read(sessionProvider.state).state; @@ -41,13 +41,10 @@ class CipherPage extends ConsumerWidget { } else { var stringToBytes = plainText.toBytes(); cipherText = await cipher.encrypt(stringToBytes); - encryptionStatus.print('String successfully encrypted.\n'); - encryptionStatus.append("Nonce: " + - cipherText!.iv.toString() + - "\nData: " + - cipherText!.data.toString() + - "\nTag: " + - cipherText!.tag.toString()); + encryptionStatus.print('String successfully encrypted:\n'); + + CipherText unwrap = cipherText!.unwrap(); + encryptionStatus.append(unwrap.base16); } } @@ -56,17 +53,19 @@ class CipherPage extends ConsumerWidget { decryptionStatus.print('Encrypt before altering CipherText!'); } else { // Add 1 to the first byte - Uint8List _altered = cipherText!.data; + Uint8List _altered = cipherText!.unwrap().bytes; _altered[0] += 1; // Recreate cipher text with altered data - cipherText = CipherText(cipherText!.iv, _altered, cipherText!.tag); - encryptionStatus.print('String successfully encrypted.\n'); - encryptionStatus.append("Nonce: " + - cipherText!.iv.toString() + - "\nData: " + - cipherText!.data.toString() + - "\nTag: " + - cipherText!.tag.toString()); + cipherText = CipherTextWrapper.fromBytes( + _altered, + 12, + _altered.length - 28, + 16, + ); + encryptionStatus.print('String successfully encrypted:\n'); + + CipherText unwrap = cipherText!.unwrap(); + encryptionStatus.appendln(unwrap.base16); decryptionStatus.print('CipherText altered!\nDecryption will fail.'); } } @@ -114,7 +113,7 @@ class CipherPage extends ConsumerWidget { } keyContent.print(state.secretKey.bytes.toString()); - AES cipher = AES(state.secretKey, AESMode.gcm); + AES cipher = AES(state.secretKey); return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(8.0), diff --git a/packages/native_crypto/example/lib/pages/kdf_page.dart b/packages/native_crypto/example/lib/pages/kdf_page.dart index 6eb9011..da04947 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: 23/05/2022 22:49:06 +// Last Modified: 26/05/2022 20:30:31 // ----- // Copyright (c) 2021 @@ -12,10 +12,10 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto/native_crypto_ext.dart'; import 'package:native_crypto_example/widgets/button.dart'; import '../session.dart'; -import '../utils.dart'; import '../widgets/output.dart'; class KdfPage extends ConsumerWidget { @@ -26,8 +26,8 @@ class KdfPage extends ConsumerWidget { final Output pbkdf2Status = Output(); final Output hashStatus = Output(large: true); - final TextEditingController _pwdTextController = TextEditingController(); - final TextEditingController _messageTextController = TextEditingController(); + final TextEditingController _pwdTextController = TextEditingController()..text = 'Password'; + final TextEditingController _messageTextController = TextEditingController()..text = 'Message'; Future _generate(WidgetRef ref) async { Session state = ref.read(sessionProvider.state).state; @@ -66,7 +66,7 @@ class KdfPage extends ConsumerWidget { } else { Uint8List hash = await hasher.digest(message.toBytes()); hashStatus.print( - 'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.hex)}'); + 'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.base16)}'); } } diff --git a/packages/native_crypto/example/lib/utils.dart b/packages/native_crypto/example/lib/utils.dart deleted file mode 100644 index d93a923..0000000 --- a/packages/native_crypto/example/lib/utils.dart +++ /dev/null @@ -1,52 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: utils.dart -// Created Date: 16/12/2021 16:28:00 -// Last Modified: 28/12/2021 14:40:21 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; -import 'dart:convert'; - -enum Encoding { utf16, base64, hex } - -extension StringX on String { - Uint8List toBytes({final from = Encoding.utf16}) { - Uint8List bytes = Uint8List(0); - switch (from) { - case Encoding.utf16: - bytes = Uint8List.fromList(runes.toList()); - break; - case Encoding.base64: - bytes = base64.decode(this); - break; - case Encoding.hex: - bytes = Uint8List.fromList( - List.generate( - length ~/ 2, - (i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16), - ).toList(), - ); - } - return bytes; - } -} - -extension Uint8ListX on Uint8List { - String toStr({final to = Encoding.utf16}) { - String str = ""; - switch (to) { - case Encoding.utf16: - str = String.fromCharCodes(this); - break; - case Encoding.base64: - str = base64.encode(this); - break; - case Encoding.hex: - str = map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(); - } - return str; - } -} diff --git a/packages/native_crypto/lib/native_crypto_ext.dart b/packages/native_crypto/lib/native_crypto_ext.dart new file mode 100644 index 0000000..0e9502e --- /dev/null +++ b/packages/native_crypto/lib/native_crypto_ext.dart @@ -0,0 +1,11 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: native_crypto_ext.dart +// Created Date: 26/05/2022 19:36:54 +// Last Modified: 26/05/2022 19:38:44 +// ----- +// Copyright (c) 2022 + +export 'src/utils/encoding.dart'; +export 'src/utils/extensions.dart'; diff --git a/packages/native_crypto/lib/src/builders/aes_builder.dart b/packages/native_crypto/lib/src/builders/aes_builder.dart deleted file mode 100644 index e256d29..0000000 --- a/packages/native_crypto/lib/src/builders/aes_builder.dart +++ /dev/null @@ -1,49 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: aes_builder.dart -// Created Date: 28/12/2021 12:03:11 -// Last Modified: 25/05/2022 10:47:11 -// ----- -// Copyright (c) 2021 - -import 'package:native_crypto/src/ciphers/aes/aes.dart'; -import 'package:native_crypto/src/interfaces/builder.dart'; -import 'package:native_crypto/src/keys/secret_key.dart'; -import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; - -class AESBuilder implements Builder { - SecretKey? _sk; - Future? _fsk; - AESMode _mode = AESMode.gcm; - - AESBuilder withGeneratedKey(int bitsCount) { - _fsk = SecretKey.fromSecureRandom(bitsCount); - return this; - } - - AESBuilder withKey(SecretKey secretKey) { - _sk = secretKey; - return this; - } - - AESBuilder using(AESMode mode) { - _mode = mode; - return this; - } - - @override - Future build() async { - if (_sk == null) { - if (_fsk == null) { - throw const CipherInitException( - message: 'You must specify or generate a secret key.', - code: 'missing_key', - ); - } else { - _sk = await _fsk; - } - } - return AES(_sk!, _mode); - } -} diff --git a/packages/native_crypto/lib/src/builders/builders.dart b/packages/native_crypto/lib/src/builders/builders.dart index e20a4a6..d846197 100644 --- a/packages/native_crypto/lib/src/builders/builders.dart +++ b/packages/native_crypto/lib/src/builders/builders.dart @@ -3,8 +3,8 @@ // ----- // File: builders.dart // Created Date: 23/05/2022 22:56:03 -// Last Modified: 23/05/2022 22:56:12 +// Last Modified: 26/05/2022 19:22:19 // ----- // Copyright (c) 2022 -export 'aes_builder.dart'; +export 'decryption_builder.dart'; diff --git a/packages/native_crypto/lib/src/builders/decryption_builder.dart b/packages/native_crypto/lib/src/builders/decryption_builder.dart new file mode 100644 index 0000000..998fdcf --- /dev/null +++ b/packages/native_crypto/lib/src/builders/decryption_builder.dart @@ -0,0 +1,46 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: decryption_builder.dart +// Created Date: 26/05/2022 19:07:52 +// Last Modified: 26/05/2022 19:21:00 +// ----- +// Copyright (c) 2022 + +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; +import 'package:native_crypto/src/interfaces/cipher.dart'; + +class DecryptionBuilder extends StatelessWidget { + final Cipher cipher; + final CipherTextWrapper data; + final Widget Function(BuildContext context) onLoading; + final Widget Function(BuildContext context, Object error) onError; + final Widget Function(BuildContext context, Uint8List plainText) onSuccess; + + const DecryptionBuilder({ + super.key, + required this.cipher, + required this.data, + required this.onLoading, + required this.onError, + required this.onSuccess, + }); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: cipher.decrypt(data), + builder: (context, snapshot) { + if (snapshot.hasData) { + return onSuccess(context, snapshot.data!); + } else if (snapshot.hasError) { + return onError(context, snapshot.error!); + } + return onLoading(context); + }, + ); + } +} diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes.dart b/packages/native_crypto/lib/src/ciphers/aes/aes.dart index 267c91b..9a28105 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: 25/05/2022 21:17:10 +// Last Modified: 26/05/2022 19:43:22 // ----- // Copyright (c) 2022 @@ -13,88 +13,120 @@ import 'package:native_crypto/src/ciphers/aes/aes_key_size.dart'; import 'package:native_crypto/src/ciphers/aes/aes_mode.dart'; import 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; import 'package:native_crypto/src/core/cipher_text.dart'; -import 'package:native_crypto/src/core/cipher_text_list.dart'; +import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; import 'package:native_crypto/src/interfaces/cipher.dart'; import 'package:native_crypto/src/keys/secret_key.dart'; import 'package:native_crypto/src/platform.dart'; import 'package:native_crypto/src/utils/cipher_algorithm.dart'; +import 'package:native_crypto/src/utils/extensions.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; -export 'package:native_crypto/src/ciphers/aes/aes_key_size.dart'; -export 'package:native_crypto/src/ciphers/aes/aes_mode.dart'; -export 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; - +/// An AES cipher. +/// +/// [AES] is a [Cipher] that can be used to encrypt or decrypt data. class AES implements Cipher { - final SecretKey key; + final SecretKey _key; final AESMode mode; final AESPadding padding; @override CipherAlgorithm get algorithm => CipherAlgorithm.aes; - AES(this.key, this.mode, {this.padding = AESPadding.none}) { - if (!AESKeySize.supportedSizes.contains(key.bytes.length * 8)) { - throw const CipherInitException( - message: 'Invalid key length!', - code: 'invalid_key_length', + AES(SecretKey key, [this.mode = AESMode.gcm, this.padding = AESPadding.none]) + : _key = key { + if (!AESKeySize.supportedSizes.contains(key.bitLength)) { + throw NativeCryptoException( + message: 'Invalid key size! ' + 'Expected: ${AESKeySize.supportedSizes.join(', ')} bits', + code: NativeCryptoExceptionCode.invalid_key_length.code, ); } - final Map> _supported = { - AESMode.gcm: [AESPadding.none], - }; - if (!_supported[mode]!.contains(padding)) { - throw const CipherInitException( - message: 'Invalid padding!', - code: 'invalid_padding', + if (!mode.supportedPaddings.contains(padding)) { + throw NativeCryptoException( + message: 'Invalid padding! ' + 'Expected: ${mode.supportedPaddings.join(', ')}', + code: NativeCryptoExceptionCode.invalid_padding.code, ); } } - Future _decrypt(CipherText cipherText) async { - return await platform.decryptAsList( - [cipherText.iv, cipherText.payload], - key.bytes, - algorithm.name, - ) ?? - Uint8List(0); - } - - Future _encrypt(Uint8List data) async { - final List cipherText = - await platform.encryptAsList(data, key.bytes, algorithm.name) ?? - List.empty(); - return CipherText.fromPairIvAndBytes( - cipherText, - dataLength: cipherText.last.length - 16, - tagLength: 16, + Future _decrypt(CipherText cipherText, + {int chunkCount = 0,}) async { + final Uint8List? decrypted = await platform.decrypt( + cipherText.bytes, + _key.bytes, + algorithm.name, ); + + if (decrypted.isNull) { + throw NativeCryptoException( + message: 'Platform returned null when decrypting chunk #$chunkCount', + code: NativeCryptoExceptionCode.platform_returned_null.code, + ); + } else if (decrypted!.isEmpty) { + throw NativeCryptoException( + message: 'Platform returned no data when decrypting chunk #$chunkCount', + code: NativeCryptoExceptionCode.platform_returned_empty_data.code, + ); + } else { + return decrypted; + } + } + + Future _encrypt(Uint8List data, {int chunkCount = 0}) async { + final Uint8List? encrypted = await platform.encrypt( + data, + _key.bytes, + algorithm.name, + ); + + if (encrypted.isNull) { + throw NativeCryptoException( + message: 'Platform returned null when encrypting chunk #$chunkCount', + code: NativeCryptoExceptionCode.platform_returned_null.code, + ); + } else if (encrypted!.isEmpty) { + throw NativeCryptoException( + message: 'Platform returned no data when encrypting chunk #$chunkCount', + code: NativeCryptoExceptionCode.platform_returned_empty_data.code, + ); + } else { + return CipherText.fromBytes( + 12, + encrypted.length - 28, + 16, + CipherAlgorithm.aes, + encrypted, + ); + } } @override - Future decrypt(CipherText cipherText) async { + Future decrypt(CipherTextWrapper cipherText) async { final BytesBuilder decryptedData = BytesBuilder(copy: false); - if (cipherText is CipherTextList) { - for (final CipherText ct in cipherText.list) { - decryptedData.add(await _decrypt(ct)); + if (cipherText.isList) { + int chunkCount = 0; + for (final CipherText chunk in cipherText.list) { + decryptedData.add(await _decrypt(chunk, chunkCount: chunkCount++)); } } else { - decryptedData.add(await _decrypt(cipherText)); + decryptedData.add(await _decrypt(cipherText.single)); } return decryptedData.toBytes(); } @override - Future encrypt(Uint8List data) async { + Future encrypt(Uint8List data) async { + CipherTextWrapper cipherTextWrapper; Uint8List dataToEncrypt; + final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); - final CipherTextList cipherTextList = CipherTextList(); - - if (data.length > Cipher.bytesCountPerChunk) { - final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); + if (chunkNb > 1) { + cipherTextWrapper = CipherTextWrapper.empty(); for (var i = 0; i < chunkNb; i++) { dataToEncrypt = i < (chunkNb - 1) ? data.sublist( @@ -102,11 +134,12 @@ class AES implements Cipher { (i + 1) * Cipher.bytesCountPerChunk, ) : data.sublist(i * Cipher.bytesCountPerChunk); - cipherTextList.add(await _encrypt(dataToEncrypt)); + cipherTextWrapper.add(await _encrypt(dataToEncrypt, chunkCount: i)); } } else { - return _encrypt(data); + cipherTextWrapper = CipherTextWrapper.single(await _encrypt(data)); } - return cipherTextList; + + return cipherTextWrapper; } } diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart index 8018f0b..befa22f 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart @@ -3,7 +3,7 @@ // ----- // File: aes_key_size.dart // Created Date: 23/05/2022 22:10:07 -// Last Modified: 23/05/2022 22:33:32 +// Last Modified: 26/05/2022 18:45:01 // ----- // Copyright (c) 2022 @@ -13,9 +13,13 @@ enum AESKeySize { bits192(192), bits256(256); + /// Returns the number of bits supported by an [AESKeySize]. static final List supportedSizes = [128, 192, 256]; + /// Returns the number of bits in this [AESKeySize]. final int bits; + + /// Returns the number of bytes in this [AESKeySize]. int get bytes => bits ~/ 8; const AESKeySize(this.bits); 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 c1d98cb..a16d414 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart @@ -3,9 +3,18 @@ // ----- // File: aes_mode.dart // Created Date: 23/05/2022 22:09:16 -// Last Modified: 25/05/2022 09:23:54 +// Last Modified: 26/05/2022 18:41:31 // ----- // Copyright (c) 2022 +import 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; + /// Defines the AES modes of operation. -enum AESMode { gcm } +enum AESMode { + gcm([AESPadding.none]); + + /// Returns the list of supported [AESPadding] for this [AESMode]. + final List supportedPaddings; + + const AESMode(this.supportedPaddings); +} diff --git a/packages/native_crypto/lib/src/core/cipher_text.dart b/packages/native_crypto/lib/src/core/cipher_text.dart index 76a9399..d20f344 100644 --- a/packages/native_crypto/lib/src/core/cipher_text.dart +++ b/packages/native_crypto/lib/src/core/cipher_text.dart @@ -3,49 +3,78 @@ // ----- // File: cipher_text.dart // Created Date: 16/12/2021 16:59:53 -// Last Modified: 26/05/2022 16:22:49 +// Last Modified: 26/05/2022 19:43:57 // ----- // Copyright (c) 2021 import 'dart:typed_data'; +import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; +import 'package:native_crypto/src/interfaces/byte_array.dart'; +import 'package:native_crypto/src/interfaces/cipher.dart'; import 'package:native_crypto/src/utils/cipher_algorithm.dart'; import 'package:native_crypto/src/utils/extensions.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; -/// Represents a cipher text in Native Crypto. +/// Represents a cipher text in NativeCrypto. /// -/// It is represented as a [List] of [Uint8List] like: +/// [CipherText] is a [ByteArray] that can be used to store encrypted data. +/// It is represented like: /// ```txt -/// [[NONCE], [MESSAGE + TAG]] +/// [IV + MESSAGE + TAG] /// ``` /// where: -/// - `[NONCE]` is a [Uint8List] of length [CipherText.ivLength] -/// - `[MESSAGE + TAG]` is a [Uint8List] of length [CipherText.dataLength] +/// - IV's length is [CipherText.ivLength] bytes. +/// - MESSAGE's length is [CipherText.messageLength] bytes. +/// - TAG's length is [CipherText.tagLength] bytes. /// -/// To -/// -/// So accessing just the Message or just the Tag is costly and should be -/// done only when needed. -class CipherText { +/// Check [CipherTextWrapper] for more information. +class CipherText extends ByteArray { final int _ivLength; final int _messageLength; final int _tagLength; final CipherAlgorithm? _cipherAlgorithm; - final Uint8List? _iv; - final Uint8List? _data; // Contains the message + tag (if any) - - CipherText._( + const CipherText._( this._ivLength, this._messageLength, this._tagLength, this._cipherAlgorithm, - this._iv, - this._data, + super.bytes, ); + factory CipherText.fromBytes( + int ivLength, + int messageLength, + int tagLength, + CipherAlgorithm? cipherAlgorithm, + Uint8List bytes, + ) { + if (bytes.length != ivLength + messageLength + tagLength) { + throw NativeCryptoException( + message: 'Invalid cipher text length! ' + 'Expected: ${ivLength + messageLength + tagLength} bytes', + code: NativeCryptoExceptionCode.invalid_argument.code, + ); + } + + if (messageLength > Cipher.bytesCountPerChunk) { + throw NativeCryptoException( + message: 'Cipher text is too big! Consider using chunks.', + code: NativeCryptoExceptionCode.invalid_argument.code, + ); + } + + return CipherText._( + ivLength, + messageLength, + tagLength, + cipherAlgorithm, + bytes, + ); + } + /// Gets the [CipherAlgorithm] used to encrypt the [CipherText]. CipherAlgorithm get cipherAlgorithm { if (_cipherAlgorithm.isNotNull) { @@ -58,89 +87,12 @@ class CipherText { } } - /// Gets the [Uint8List] of the [CipherText]'s IV. - Uint8List get iv { - if (_iv.isNotNull) { - return _iv!; - } else { - throw NativeCryptoException( - message: 'IV is not specified', - code: NativeCryptoExceptionCode.invalid_data.code, - ); - } - } - /// Gets the length of the [CipherText]'s IV. int get ivLength => _ivLength; - /// Gets the [Uint8List] of the [CipherText]'s data. - Uint8List get data { - if (_data.isNotNull) { - return _data!; - } else { - throw NativeCryptoException( - message: 'Data is not specified', - code: NativeCryptoExceptionCode.invalid_data.code, - ); - } - } + /// Gets the length of the [CipherText]'s Message. + int get messageLength => _messageLength; - /// Gets the length of the [CipherText]'s data. - int get dataLength => _messageLength + _tagLength; - - // CipherText.fromBytes( - // Uint8List bytes, { - // required int ivLength, - // required int dataLength, - // int tagLength = 0, - // }) : _ivLength = ivLength, - // _dataLength = dataLength, - // _tagLength = tagLength, - // _iv = bytes.sublist(0, ivLength), - // super(bytes.sublist(ivLength, bytes.length - tagLength)); - - // const CipherText.fromIvAndBytes( - // Uint8List iv, - // super.data, { - // required int dataLength, - // int tagLength = 0, - // }) : _ivLength = iv.length, - // _dataLength = dataLength, - // _tagLength = tagLength, - // _iv = iv; - - // CipherText.fromPairIvAndBytes( - // List pair, { - // required int dataLength, - // int tagLength = 0, - // }) : _ivLength = pair.first.length, - // _dataLength = dataLength, - // _tagLength = tagLength, - // _iv = pair.first, - // super(pair.last); - - // /// Gets the CipherText IV. - // Uint8List get iv => _iv; - - // /// Gets the CipherText data. - // Uint8List get data => _tagLength > 0 - // ? bytes.sublist(0, _dataLength - _tagLength) - // : bytes; - - // /// Gets the CipherText tag. - // Uint8List get tag => _tagLength > 0 - // ? bytes.sublist(_dataLength - _tagLength, _dataLength) - // : Uint8List(0); - - // /// Gets the CipherText data and tag. - // Uint8List get payload => bytes; - - // /// Gets the CipherText IV length. - // int get ivLength => _ivLength; - - // /// Gets the CipherText data length. - // int get dataLength => _dataLength; - - // /// Gets the CipherText tag length. - // int get tagLength => _tagLength; + /// Gets the length of the [CipherText]'s Tag. + int get tagLength => _tagLength; } diff --git a/packages/native_crypto/lib/src/core/cipher_text_list.dart b/packages/native_crypto/lib/src/core/cipher_text_list.dart deleted file mode 100644 index dab2709..0000000 --- a/packages/native_crypto/lib/src/core/cipher_text_list.dart +++ /dev/null @@ -1,26 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: cipher_text_list.dart -// Created Date: 23/05/2022 22:59:02 -// Last Modified: 24/05/2022 20:18:26 -// ----- -// Copyright (c) 2022 - -import 'dart:typed_data'; - -import 'package:native_crypto/src/core/cipher_text.dart'; - -class CipherTextList extends CipherText { - final List _list; - - CipherTextList() - : _list = [], - super(Uint8List(0), Uint8List(0), Uint8List(0)); - - void add(CipherText cipherText) { - _list.add(cipherText); - } - - List get list => _list; -} 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 a65cc23..08dc915 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 15:53:46 +// Last Modified: 26/05/2022 20:32:38 // ----- // Copyright (c) 2022 @@ -12,31 +12,85 @@ import 'dart:typed_data'; import 'package:native_crypto/native_crypto.dart'; import 'package:native_crypto/src/utils/extensions.dart'; +/// Wrapper for [CipherText] +/// +/// Typically, this object is the result of an encryption operation. +/// For decryption you have to build this before using it. class CipherTextWrapper { final CipherText? _single; final List? _list; CipherTextWrapper._(this._single, this._list); + /// Creates a [CipherTextWrapper] from a [CipherText]. factory CipherTextWrapper.single(CipherText cipherText) => CipherTextWrapper._(cipherText, null); + /// Creates a [CipherTextWrapper] from a [List] of [CipherText]. factory CipherTextWrapper.list(List cipherTexts) => CipherTextWrapper._(null, cipherTexts); + /// Creates an empty [List] in a [CipherTextWrapper]. + /// + /// This is useful when you want to create a [CipherTextWrapper] then + /// fill it with data. + factory CipherTextWrapper.empty() => CipherTextWrapper._(null, []); + + /// Creates a [CipherTextWrapper] from a [Uint8List]. + /// + /// This is a convenience method to create a [CipherTextWrapper] + /// from a [Uint8List]. It tries to detect if the [Uint8List] is a + /// single [CipherText] or a list of [CipherText]. + /// + /// You can customize the chunk size by passing a [chunkSize] parameter. + /// The default chunk size is [Cipher.bytesCountPerChunk]. + /// + /// Throw an [NativeCryptoExceptionCode] with + /// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is + /// not a valid [CipherText] or a [List] of [CipherText]. factory CipherTextWrapper.fromBytes( - // Uint8List bytes, { - // required int ivLength, - // required int dataLength, - // int tagLength = 0, - // int? chunkSize, - // } - ) { - // TODO(hpcl): implement fromBytes - throw UnimplementedError(); + Uint8List bytes, + int ivLength, + int messageLength, + int tagLength, { + CipherAlgorithm? cipherAlgorithm, + int? chunkSize, + }) { + chunkSize ??= Cipher.bytesCountPerChunk; + Cipher.bytesCountPerChunk = chunkSize; + + if (bytes.length <= chunkSize) { + return CipherTextWrapper.single( + CipherText.fromBytes( + ivLength, + messageLength, + tagLength, + cipherAlgorithm, + bytes, + ), + ); + } else { + final cipherTexts = []; + for (var i = 0; i < bytes.length; i += chunkSize) { + final chunk = bytes.sublist(i, i + chunkSize); + cipherTexts.add( + CipherText.fromBytes( + ivLength, + messageLength, + tagLength, + cipherAlgorithm, + chunk, + ), + ); + } + return CipherTextWrapper.list(cipherTexts); + } } + /// Checks if the [CipherText] is a single [CipherText]. bool get isSingle => _single.isNotNull; + + /// Checks if the [CipherText] is a [List] of [CipherText]. bool get isList => _list.isNotNull; /// Gets the [CipherText] if it's a single one. @@ -70,7 +124,7 @@ class CipherTextWrapper { } /// Gets the raw [Uint8List] of the [CipherText] or [List] of [CipherText]. - Uint8List get raw { + Uint8List get bytes { if (isSingle) { return single.bytes; } else { @@ -78,6 +132,9 @@ class CipherTextWrapper { } } + /// Gets the number of parts of the [CipherText] or [List] of [CipherText]. + /// + /// Check [Cipher.bytesCountPerChunk] for more information. int get chunkCount { _single.isNull; if (_single.isNotNull) { @@ -86,4 +143,41 @@ class CipherTextWrapper { return _list?.length ?? 0; } } + + /// Gets the [CipherText] or the [List] of [CipherText]. + /// + /// Throws [NativeCryptoException] with + /// [NativeCryptoExceptionCode.invalid_data] if it's not a single or a list or + /// if [T] is not [CipherText] or [List] of [CipherText]. + T unwrap() { + if (isSingle && T == CipherText) { + return single as T; + } else if (isList && T == List) { + return list as T; + } else { + final String type = + isSingle ? 'CipherText' : (isList ? 'List' : 'unknown'); + throw NativeCryptoException( + message: 'CipherTextWrapper is not a $T but a $type, ' + 'you should use unwrap<$type>()', + code: NativeCryptoExceptionCode.invalid_data.code, + ); + } + } + + void add(CipherText cipherText) { + if (isSingle) { + throw NativeCryptoException( + message: 'CipherTextWrapper is already single', + code: NativeCryptoExceptionCode.invalid_data.code, + ); + } else if (isList) { + _list!.add(cipherText); + } else { + throw NativeCryptoException( + message: 'CipherTextWrapper is not single or list', + code: NativeCryptoExceptionCode.invalid_data.code, + ); + } + } } diff --git a/packages/native_crypto/lib/src/core/core.dart b/packages/native_crypto/lib/src/core/core.dart index 5ab875c..32ad783 100644 --- a/packages/native_crypto/lib/src/core/core.dart +++ b/packages/native_crypto/lib/src/core/core.dart @@ -3,9 +3,9 @@ // ----- // File: core.dart // Created Date: 23/05/2022 23:05:26 -// Last Modified: 25/05/2022 10:44:32 +// Last Modified: 26/05/2022 17:10:25 // ----- // Copyright (c) 2022 export 'cipher_text.dart'; -export 'cipher_text_list.dart'; +export 'cipher_text_wrapper.dart'; diff --git a/packages/native_crypto/lib/src/interfaces/base_key.dart b/packages/native_crypto/lib/src/interfaces/base_key.dart new file mode 100644 index 0000000..e8ac27e --- /dev/null +++ b/packages/native_crypto/lib/src/interfaces/base_key.dart @@ -0,0 +1,22 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: base_key.dart +// Created Date: 16/12/2021 16:28:00 +// Last Modified: 26/05/2022 17:40:38 +// ----- +// Copyright (c) 2021 + +import 'package:native_crypto/src/interfaces/byte_array.dart'; + +/// Represents a key in NativeCrypto. +/// +/// [BaseKey] is a [ByteArray] that can be used to store keys. +/// +/// This interface is implemented by all the key classes. +abstract class BaseKey extends ByteArray { + const BaseKey(super.bytes); + BaseKey.fromBase16(super.encoded) : super.fromBase16(); + BaseKey.fromBase64(super.encoded) : super.fromBase64(); + BaseKey.fromUtf8(super.input) : super.fromUtf8(); +} diff --git a/packages/native_crypto/lib/src/interfaces/byte_array.dart b/packages/native_crypto/lib/src/interfaces/byte_array.dart index d99d115..7cdef3a 100644 --- a/packages/native_crypto/lib/src/interfaces/byte_array.dart +++ b/packages/native_crypto/lib/src/interfaces/byte_array.dart @@ -3,7 +3,7 @@ // ----- // File: byte_array.dart // Created Date: 16/12/2021 17:54:16 -// Last Modified: 26/05/2022 14:25:05 +// Last Modified: 26/05/2022 17:13:27 // ----- // Copyright (c) 2021 @@ -13,6 +13,9 @@ import 'package:flutter/foundation.dart'; import 'package:native_crypto/src/utils/encoding.dart'; import 'package:native_crypto/src/utils/extensions.dart'; +/// Represents a byte array. +/// +/// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods. @immutable abstract class ByteArray { final Uint8List _bytes; @@ -56,6 +59,12 @@ abstract class ByteArray { /// Gets the [ByteArray] bytes as an UTF-16 representation. String get utf16 => _bytes.toStr(); + /// Gets the [ByteArray] length in bytes. + int get length => _bytes.length; + + /// Gets the [ByteArray] length in bits. + int get bitLength => _bytes.length * 8; + @override bool operator ==(Object other) { if (other is ByteArray) { diff --git a/packages/native_crypto/lib/src/interfaces/cipher.dart b/packages/native_crypto/lib/src/interfaces/cipher.dart index 8941ee8..c95f34b 100644 --- a/packages/native_crypto/lib/src/interfaces/cipher.dart +++ b/packages/native_crypto/lib/src/interfaces/cipher.dart @@ -3,44 +3,48 @@ // ----- // File: cipher.dart // Created Date: 16/12/2021 16:28:00 -// Last Modified: 24/05/2022 19:55:38 +// Last Modified: 26/05/2022 17:38:26 // ----- // Copyright (c) 2021 import 'dart:typed_data'; -import 'package:native_crypto/src/core/cipher_text.dart'; +import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; import 'package:native_crypto/src/utils/cipher_algorithm.dart'; -/// Represents a cipher. +/// Represents a cipher in NativeCrypto. /// -/// In cryptography, a cipher is an algorithm for performing encryption +/// In cryptography, a [Cipher] is an algorithm for performing encryption /// or decryption - a series of well-defined steps that can /// be followed as a procedure. +/// +/// This interface is implemented by all the ciphers in NativeCrypto. abstract class Cipher { - /// Returns the size of a chunk of data - /// that can be processed by the cipher. static int _bytesCountPerChunk = 33554432; + /// Returns the size of a chunk of data + /// that can be processed by the [Cipher]. static int get bytesCountPerChunk => Cipher._bytesCountPerChunk; + /// Sets the size of a chunk of data + /// that can be processed by the [Cipher]. static set bytesCountPerChunk(int bytesCount) { _bytesCountPerChunk = bytesCount; } - /// Returns the standard algorithm name for this cipher + /// Returns the standard algorithm for this [Cipher]. CipherAlgorithm get algorithm; - /// Encrypts data. + /// Encrypts the [data]. /// /// Takes [Uint8List] data as parameter. - /// Returns a [CipherText]. - Future encrypt(Uint8List data); + /// Returns a [CipherTextWrapper]. + Future encrypt(Uint8List data); - /// Decrypts cipher text. + /// Decrypts the [cipherText] /// - /// Takes [CipherText] as parameter. + /// Takes [CipherTextWrapper] as parameter. /// And returns plain text data as [Uint8List]. - Future decrypt(CipherText cipherText); + Future decrypt(CipherTextWrapper cipherText); } diff --git a/packages/native_crypto/lib/src/interfaces/interfaces.dart b/packages/native_crypto/lib/src/interfaces/interfaces.dart index 3ff3855..ef47be3 100644 --- a/packages/native_crypto/lib/src/interfaces/interfaces.dart +++ b/packages/native_crypto/lib/src/interfaces/interfaces.dart @@ -3,12 +3,12 @@ // ----- // File: interfaces.dart // Created Date: 23/05/2022 23:03:47 -// Last Modified: 23/05/2022 23:10:15 +// Last Modified: 26/05/2022 17:41:06 // ----- // Copyright (c) 2022 +export 'base_key.dart'; export 'builder.dart'; export 'byte_array.dart'; export 'cipher.dart'; -// export 'key.dart'; export 'keyderivation.dart'; diff --git a/packages/native_crypto/lib/src/interfaces/key.dart b/packages/native_crypto/lib/src/interfaces/key.dart deleted file mode 100644 index 3c99f0c..0000000 --- a/packages/native_crypto/lib/src/interfaces/key.dart +++ /dev/null @@ -1,18 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: key.dart -// Created Date: 16/12/2021 16:28:00 -// Last Modified: 23/05/2022 23:02:10 -// ----- -// Copyright (c) 2021 - -import 'package:native_crypto/src/interfaces/byte_array.dart'; - -/// A class representing a key. -abstract class Key extends ByteArray { - const Key(super.bytes); - Key.fromBase16(super.encoded) : super.fromBase16(); - Key.fromBase64(super.encoded) : super.fromBase64(); - Key.fromUtf8(super.input) : super.fromUtf8(); -} diff --git a/packages/native_crypto/lib/src/interfaces/keyderivation.dart b/packages/native_crypto/lib/src/interfaces/keyderivation.dart index cddb1bc..ebc3841 100644 --- a/packages/native_crypto/lib/src/interfaces/keyderivation.dart +++ b/packages/native_crypto/lib/src/interfaces/keyderivation.dart @@ -3,18 +3,21 @@ // ----- // File: kdf.dart // Created Date: 18/12/2021 11:56:43 -// Last Modified: 23/05/2022 22:37:04 +// Last Modified: 26/05/2022 18:47:15 // ----- // Copyright (c) 2021 import 'package:native_crypto/src/keys/secret_key.dart'; import 'package:native_crypto/src/utils/kdf_algorithm.dart'; -/// Represents a Key Derivation Function +/// Represents a Key Derivation Function (KDF) in NativeCrypto. +/// +/// [KeyDerivation] function is a function that takes some +/// parameters and returns a [SecretKey]. abstract class KeyDerivation { - /// Returns the standard algorithm name for this key derivation function + /// Returns the standard algorithm for this key derivation function KdfAlgorithm get algorithm; - /// Derive key + /// Derive a [SecretKey]. Future derive(); } diff --git a/packages/native_crypto/lib/src/kdf/pbkdf2.dart b/packages/native_crypto/lib/src/kdf/pbkdf2.dart index 7d8acc2..ae7d3b4 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: 25/05/2022 10:45:00 +// Last Modified: 26/05/2022 18:51:59 // ----- // Copyright (c) 2021 @@ -12,10 +12,15 @@ import 'dart:typed_data'; import 'package:native_crypto/src/interfaces/keyderivation.dart'; import 'package:native_crypto/src/keys/secret_key.dart'; import 'package:native_crypto/src/platform.dart'; +import 'package:native_crypto/src/utils/extensions.dart'; import 'package:native_crypto/src/utils/hash_algorithm.dart'; import 'package:native_crypto/src/utils/kdf_algorithm.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; +/// Represent a PBKDF2 Key Derivation Function (KDF) in NativeCrypto. +/// +/// [Pbkdf2] is a function that takes password, salt, iteration count and +/// derive a [SecretKey] of specified length. class Pbkdf2 extends KeyDerivation { final int _keyBytesCount; final int _iterations; @@ -35,20 +40,42 @@ class Pbkdf2 extends KeyDerivation { @override Future derive({String? password, String? salt}) async { if (password == null || salt == null) { - throw const KeyDerivationException( - message: "Password or Salt can't be null!", - code: 'invalid_password_or_salt', + throw NativeCryptoException( + message: 'Password and salt cannot be null. ' + 'Here is the password: $password, here is the salt: $salt', + code: NativeCryptoExceptionCode.invalid_argument.code, ); } - final Uint8List derivation = (await platform.pbkdf2( - password, - salt, - _keyBytesCount, - _iterations, - _hash.name, - )) ?? - Uint8List(0); + final Uint8List? derivation = await platform.pbkdf2( + password, + salt, + _keyBytesCount, + _iterations, + _hash.name, + ); + + if (derivation.isNull) { + throw NativeCryptoException( + message: 'Failed to derive a key! Platform returned null.', + code: NativeCryptoExceptionCode.platform_returned_null.code, + ); + } + + if (derivation!.isEmpty) { + throw NativeCryptoException( + message: 'Failed to derive a key! Platform returned no data.', + code: NativeCryptoExceptionCode.platform_returned_empty_data.code, + ); + } + + if (derivation.length != _keyBytesCount) { + throw NativeCryptoException( + message: 'Failed to derive a key! Platform returned ' + '${derivation.length} bytes, but expected $_keyBytesCount bytes.', + code: NativeCryptoExceptionCode.platform_returned_invalid_data.code, + ); + } return SecretKey(derivation); } diff --git a/packages/native_crypto/lib/src/keys/secret_key.dart b/packages/native_crypto/lib/src/keys/secret_key.dart index 32f539b..f660181 100644 --- a/packages/native_crypto/lib/src/keys/secret_key.dart +++ b/packages/native_crypto/lib/src/keys/secret_key.dart @@ -3,46 +3,54 @@ // ----- // File: secret_key.dart // Created Date: 28/12/2021 13:36:54 -// Last Modified: 26/05/2022 11:56:06 +// Last Modified: 26/05/2022 19:26:35 // ----- // Copyright (c) 2021 import 'dart:typed_data'; -import 'package:native_crypto/src/interfaces/key.dart'; +import 'package:native_crypto/src/interfaces/base_key.dart'; +import 'package:native_crypto/src/interfaces/cipher.dart'; import 'package:native_crypto/src/platform.dart'; +import 'package:native_crypto/src/utils/extensions.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; -/// A class representing a secret key. -/// A secret key is a key that is not accessible by anyone else. -/// It is used to encrypt and decrypt data. -class SecretKey extends Key { +/// Represents a secret key in NativeCrypto. +/// +/// [SecretKey] is a [BaseKey] that can be used to store secret keys. +/// A [SecretKey] is a key that can be used to encrypt or decrypt data with +/// a symmetric [Cipher]. +class SecretKey extends BaseKey { const SecretKey(super.bytes); SecretKey.fromBase16(super.encoded) : super.fromBase16(); SecretKey.fromBase64(super.encoded) : super.fromBase64(); SecretKey.fromUtf8(super.input) : super.fromUtf8(); static Future fromSecureRandom(int bitsCount) async { + Uint8List? key; try { - final Uint8List? _key = await platform.generateSecretKey(bitsCount); - - if (_key == null || _key.isEmpty) { - throw const KeyException( - message: 'Could not generate secret key, platform returned null', - code: 'platform_returned_null', - ); - } - - return SecretKey(_key); + key = await platform.generateSecretKey(bitsCount); } catch (e, s) { - if (e is KeyException) { - rethrow; - } - throw KeyException( + throw NativeCryptoException( message: '$e', - code: 'failed_to_generate_secret_key', + code: NativeCryptoExceptionCode.platform_throws.code, stackTrace: s, ); } + if (key.isNull) { + throw NativeCryptoException( + message: 'Failed to generate a secret key! Platform returned null.', + code: NativeCryptoExceptionCode.platform_returned_null.code, + ); + } + + if (key!.isEmpty) { + throw NativeCryptoException( + message: 'Failed to generate a secret key! ' + 'Platform returned no data.', + code: NativeCryptoExceptionCode.platform_returned_empty_data.code, + ); + } + return SecretKey(key); } } diff --git a/packages/native_crypto/lib/src/utils/cipher_algorithm.dart b/packages/native_crypto/lib/src/utils/cipher_algorithm.dart index 5498bbc..2ba968c 100644 --- a/packages/native_crypto/lib/src/utils/cipher_algorithm.dart +++ b/packages/native_crypto/lib/src/utils/cipher_algorithm.dart @@ -3,9 +3,9 @@ // ----- // File: cipher_algorithm.dart // Created Date: 23/05/2022 22:07:54 -// Last Modified: 23/05/2022 22:33:56 +// Last Modified: 26/05/2022 18:52:32 // ----- // Copyright (c) 2022 /// Represents different cipher algorithms -enum CipherAlgorithm { aes, rsa } +enum CipherAlgorithm { aes } diff --git a/packages/native_crypto/lib/src/utils/extensions.dart b/packages/native_crypto/lib/src/utils/extensions.dart index 91b9949..b6399e0 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 15:49:38 +// Last Modified: 26/05/2022 18:52:48 // ----- // Copyright (c) 2022 @@ -30,8 +30,8 @@ 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]); diff --git a/packages/native_crypto/lib/src/utils/hash_algorithm.dart b/packages/native_crypto/lib/src/utils/hash_algorithm.dart index 4e914cd..d9c6ced 100644 --- a/packages/native_crypto/lib/src/utils/hash_algorithm.dart +++ b/packages/native_crypto/lib/src/utils/hash_algorithm.dart @@ -3,7 +3,7 @@ // ----- // File: hash_algorithm.dart // Created Date: 23/05/2022 22:01:59 -// Last Modified: 23/05/2022 22:47:08 +// Last Modified: 26/05/2022 18:53:38 // ----- // Copyright (c) 2022 @@ -11,12 +11,13 @@ import 'dart:typed_data'; import 'package:native_crypto/src/platform.dart'; +/// Defines the hash algorithms. enum HashAlgorithm { sha256, sha384, sha512; - /// Hashes a message + /// Digest the [data] using this [HashAlgorithm]. Future digest(Uint8List data) async { final Uint8List hash = (await platform.digest(data, name)) ?? Uint8List(0); diff --git a/packages/native_crypto/lib/src/utils/kdf_algorithm.dart b/packages/native_crypto/lib/src/utils/kdf_algorithm.dart index 58ff68a..68d6a76 100644 --- a/packages/native_crypto/lib/src/utils/kdf_algorithm.dart +++ b/packages/native_crypto/lib/src/utils/kdf_algorithm.dart @@ -3,8 +3,9 @@ // ----- // File: kdf_algorithm.dart // Created Date: 23/05/2022 22:36:24 -// Last Modified: 23/05/2022 22:36:36 +// Last Modified: 26/05/2022 18:53:50 // ----- // Copyright (c) 2022 +/// Represents different key derivation functions enum KdfAlgorithm { pbkdf2 } diff --git a/packages/native_crypto/test/src/secret_key_test.dart b/packages/native_crypto/test/src/secret_key_test.dart index 1a98e24..e8deb2e 100644 --- a/packages/native_crypto/test/src/secret_key_test.dart +++ b/packages/native_crypto/test/src/secret_key_test.dart @@ -3,7 +3,7 @@ // ----- // File: secret_key_test.dart // Created Date: 26/05/2022 10:52:41 -// Last Modified: 26/05/2022 12:07:33 +// Last Modified: 26/05/2022 19:24:44 // ----- // Copyright (c) 2022 @@ -42,10 +42,10 @@ void main() { await expectLater( () => SecretKey.fromSecureRandom(5), throwsA( - isA().having( + isA().having( (e) => e.code, 'code', - 'platform_returned_null', + 'platform_returned_empty_data', ), ), ); @@ -59,7 +59,7 @@ void main() { await expectLater( () => SecretKey.fromSecureRandom(5), throwsA( - isA().having( + isA().having( (e) => e.code, 'code', 'platform_returned_null', @@ -81,7 +81,7 @@ void main() { await expectLater( () => SecretKey.fromSecureRandom(5), throwsA( - isA() + isA() .having( (e) => e.message, 'message', @@ -90,7 +90,7 @@ void main() { .having( (e) => e.code, 'code', - 'failed_to_generate_secret_key', + 'platform_throws', ), ), );