From e2fbb0b5f7aefccfaab1187340a742286a07a9bf Mon Sep 17 00:00:00 2001 From: Sitaram Kalluri Date: Mon, 11 Nov 2024 18:45:19 +0530 Subject: [PATCH 1/2] feat: at_chops : Support for password protected atKeys file --- packages/at_chops/CHANGELOG.md | 7 +- packages/at_chops/lib/at_chops.dart | 31 +++-- .../src/algorithm/aes_encryption_algo.dart | 73 ++++++++++- .../at_chops/lib/src/algorithm/algo_type.dart | 15 ++- .../src/algorithm/argon2id_hashing_algo.dart | 52 ++++++++ .../lib/src/algorithm/at_algorithm.dart | 23 ++-- .../algorithm/at_hashing_algo_factory.dart | 29 +++++ .../src/algorithm/default_hashing_algo.dart | 5 +- packages/at_chops/lib/src/at_chops_impl.dart | 9 +- packages/at_chops/lib/src/at_keys_crypto.dart | 117 ++++++++++++++++++ .../at_chops/lib/src/model/at_encrypted.dart | 68 ++++++++++ .../at_chops/lib/src/model/hash_params.dart | 36 ++++++ packages/at_chops/lib/types.dart | 3 - packages/at_chops/pubspec.yaml | 3 +- .../at_chops/test/at_keys_crypto_test.dart | 52 ++++++++ .../test/rsa_encryption_algo_test.dart | 3 +- 16 files changed, 491 insertions(+), 35 deletions(-) create mode 100644 packages/at_chops/lib/src/algorithm/argon2id_hashing_algo.dart create mode 100644 packages/at_chops/lib/src/algorithm/at_hashing_algo_factory.dart create mode 100644 packages/at_chops/lib/src/at_keys_crypto.dart create mode 100644 packages/at_chops/lib/src/model/at_encrypted.dart create mode 100644 packages/at_chops/lib/src/model/hash_params.dart create mode 100644 packages/at_chops/test/at_keys_crypto_test.dart diff --git a/packages/at_chops/CHANGELOG.md b/packages/at_chops/CHANGELOG.md index 73c6637c..36fc5d03 100644 --- a/packages/at_chops/CHANGELOG.md +++ b/packages/at_chops/CHANGELOG.md @@ -1,5 +1,10 @@ +## 2.2.0 +- feat: Implement "argon2id" hashing algorithm to generate hash from a given passphrase. +- feat: Add generics to "AtEncryptionAlgorithm" and "AtHashingAlgorithm" to support multiple data types in their + implementations. ## 2.1.0 -- feat: New library available called `at_chops_types` which provides type definitions for using custom algorithms with at_chops +- feat: New library available called `at_chops_types` which provides type definitions for using custom algorithms with + at_chops ## 2.0.1 - fix: throw Exception when input IV is null for decryption(with Symmetric Encryption) - build[deps]: Upgraded the following packages: diff --git a/packages/at_chops/lib/at_chops.dart b/packages/at_chops/lib/at_chops.dart index 3689dd60..9f985cc8 100644 --- a/packages/at_chops/lib/at_chops.dart +++ b/packages/at_chops/lib/at_chops.dart @@ -1,25 +1,34 @@ library at_chops; +export 'src/algorithm/aes_encryption_algo.dart'; +export 'src/algorithm/algo_type.dart'; +export 'src/algorithm/at_iv.dart'; +export 'src/algorithm/default_signing_algo.dart'; +export 'src/algorithm/ecc_signing_algo.dart'; +export 'src/algorithm/pkam_signing_algo.dart'; +export 'src/algorithm/rsa_encryption_algo.dart'; export 'src/at_chops_base.dart'; export 'src/at_chops_impl.dart'; + +// Class to encrypt/decrypt atKeys file based on the password specified. +export 'src/at_keys_crypto.dart'; +export 'src/key/at_key_pair.dart'; +export 'src/key/at_private_key.dart'; +export 'src/key/at_public_key.dart'; +export 'src/key/impl/aes_key.dart'; export 'src/key/impl/at_chops_keys.dart'; export 'src/key/impl/at_encryption_key_pair.dart'; export 'src/key/impl/at_pkam_key_pair.dart'; -export 'src/key/impl/aes_key.dart'; export 'src/key/key_type.dart'; export 'src/metadata/at_signing_input.dart'; export 'src/metadata/encryption_metadata.dart'; export 'src/metadata/encryption_result.dart'; export 'src/metadata/signing_metadata.dart'; export 'src/metadata/signing_result.dart'; + +// A model class which represents the encrypted AtKeys with a passphrase. +export 'src/model/at_encrypted.dart'; + +// Class representing the hashing parameters to pass to an hashing algorithm. +export 'src/model/hash_params.dart' hide HashParams; export 'src/util/at_chops_util.dart'; -export 'src/algorithm/algo_type.dart'; -export 'src/algorithm/at_iv.dart'; -export 'src/algorithm/aes_encryption_algo.dart'; -export 'src/algorithm/rsa_encryption_algo.dart'; -export 'src/algorithm/default_signing_algo.dart'; -export 'src/algorithm/pkam_signing_algo.dart'; -export 'src/algorithm/ecc_signing_algo.dart'; -export 'src/key/at_key_pair.dart'; -export 'src/key/at_public_key.dart'; -export 'src/key/at_private_key.dart'; diff --git a/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart b/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart index 32d0f8c9..f512e8ca 100644 --- a/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart +++ b/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart @@ -1,12 +1,16 @@ import 'dart:typed_data'; +import 'package:at_chops/at_chops.dart'; import 'package:at_chops/src/algorithm/at_algorithm.dart'; -import 'package:at_chops/src/algorithm/at_iv.dart'; -import 'package:at_chops/src/key/impl/aes_key.dart'; +import 'package:at_commons/at_commons.dart'; import 'package:encrypt/encrypt.dart'; -class AESEncryptionAlgo implements SymmetricEncryptionAlgorithm { +/// A class that provides AES encryption and decryption for Uint8List, +/// implementing the [SymmetricEncryptionAlgorithm] interface. +class AESEncryptionAlgo + implements SymmetricEncryptionAlgorithm { final AESKey _aesKey; + AESEncryptionAlgo(this._aesKey); @override @@ -33,3 +37,66 @@ class AESEncryptionAlgo implements SymmetricEncryptionAlgorithm { return IV(Uint8List(16)); } } + +/// A class that provides AES encryption and decryption for strings, +/// implementing the [SymmetricEncryptionAlgorithm] interface. +/// +/// This class uses an [AESKey] to perform encryption and decryption of strings. +/// The key and an [InitialisationVector] (IV) are used for encryption, and the +/// same key must be used for decryption. +class StringAESEncryptor + implements SymmetricEncryptionAlgorithm { + /// The AES key used for encryption and decryption. + final AESKey _aesKey; + + /// Constructs an instance of [StringAESEncryptor] with the provided [_aesKey]. + /// + /// [_aesKey]: The key used for AES encryption and decryption, represented + /// in Base64 format. + StringAESEncryptor(this._aesKey); + + /// Decrypts the given [encryptedData] using the provided [iv] (Initialisation Vector). + /// + /// The [iv] used for encryption must be the same for decryption. If [iv] is + /// not provided, an [AtDecryptionException] will be thrown, as the IV is + /// mandatory for the AES decryption process. + /// + /// - [encryptedData]: The Base64-encoded string that represents the encrypted data. + /// - [iv]: The Initialisation Vector used during encryption. Must be the same + /// IV that was used to encrypt the data. + /// + /// Returns a [String] that represents the decrypted data. + /// + /// Throws an [AtDecryptionException] if the [iv] is missing. + + @override + String decrypt(String encryptedData, {InitialisationVector? iv}) { + // The IV used for encryption, the same IV must be used for decryption. + if (iv == null) { + throw AtDecryptionException( + 'Initialisation Vector (IV) is required for decryption'); + } + var aesEncrypter = Encrypter(AES(Key.fromBase64(_aesKey.key))); + return aesEncrypter.decrypt(Encrypted.fromBase64(encryptedData), + iv: IV(iv.ivBytes)); + } + + /// Encrypts the given [plainData] using AES encryption and an optional [iv]. + /// + /// If no [iv] is provided, a random 16-byte IV will be generated using + /// [AtChopsUtil.generateRandomIV]. The resulting encrypted data will be + /// Base64-encoded. + /// + /// - [plainData]: The string that needs to be encrypted. + /// - [iv]: The Initialisation Vector used for encryption. If not provided, + /// a random 16-byte IV will be generated. + /// + /// Returns a [String] that contains the encrypted data, encoded in Base64 format. + @override + String encrypt(String plainData, {InitialisationVector? iv}) { + iv ??= AtChopsUtil.generateRandomIV(16); + var aesEncrypter = Encrypter(AES(Key.fromBase64(_aesKey.key))); + final encrypted = aesEncrypter.encrypt(plainData, iv: IV(iv.ivBytes)); + return encrypted.base64; + } +} diff --git a/packages/at_chops/lib/src/algorithm/algo_type.dart b/packages/at_chops/lib/src/algorithm/algo_type.dart index 83dcb0f4..2e109180 100644 --- a/packages/at_chops/lib/src/algorithm/algo_type.dart +++ b/packages/at_chops/lib/src/algorithm/algo_type.dart @@ -1,4 +1,17 @@ // ignore: constant_identifier_names +import 'package:at_commons/at_commons.dart'; + enum SigningAlgoType { ecc_secp256r1, rsa2048, rsa4096 } -enum HashingAlgoType { sha256, sha512, md5 } +enum HashingAlgoType { + sha256, + sha512, + md5, + argon2id; + + static HashingAlgoType fromString(String name) { + return HashingAlgoType.values.firstWhere( + (algo) => algo.name == name.toLowerCase(), + orElse: () => throw AtException('Invalid hashing algo type')); + } +} diff --git a/packages/at_chops/lib/src/algorithm/argon2id_hashing_algo.dart b/packages/at_chops/lib/src/algorithm/argon2id_hashing_algo.dart new file mode 100644 index 00000000..567ab995 --- /dev/null +++ b/packages/at_chops/lib/src/algorithm/argon2id_hashing_algo.dart @@ -0,0 +1,52 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:at_chops/src/algorithm/at_algorithm.dart'; +import 'package:at_chops/src/model/hash_params.dart'; +import 'package:cryptography/cryptography.dart'; + +/// A class that implements the Argon2id hashing algorithm for password hashing. +/// +/// This class provides a method to hash a given password using the Argon2id +/// algorithm, which is a memory-hard, CPU-intensive key derivation function +/// suitable for password hashing and encryption key derivation. +/// +/// The class uses the `cryptography` package's `Argon2id` algorithm for deriving +/// a key from a password and encodes the result into a Base64 string. +class Argon2idHashingAlgo implements AtHashingAlgorithm { + /// Hashes a given password using the Argon2id algorithm. + /// + /// The [password] parameter is required, and it represents the password or + /// passphrase to be hashed. + /// + /// The [hashParams] parameter is optional. It allows customizing the Argon2id + /// parameters, such as: + /// - [HashParams.parallelism]: The degree of parallelism (threads) to use. + /// - [HashParams.memory]: The amount of memory (in KB) to use. + /// - [HashParams.iterations]: The number of iterations (time cost) to apply. + /// - [HashParams.hashLength]: The length of the resulting hash (in bytes). + /// + /// If [hashParams] is not provided, default values will be used. + /// + /// The method returns a [Future] that resolves to a Base64-encoded string + /// representing the hashed value of the input password. + /// + /// Throws: + /// - [ArgumentError] if the provided password is null or empty. + /// + /// Returns a Base64-encoded string representing the derived key. + @override + Future hash(String password, {ArgonHashParams? hashParams}) async { + hashParams ??= ArgonHashParams(); + final argon2id = Argon2id( + parallelism: hashParams.parallelism, + memory: hashParams.memory, + iterations: hashParams.iterations, + hashLength: hashParams.hashLength); + + SecretKey secretKey = await argon2id.deriveKeyFromPassword( + password: password, nonce: password.codeUnits); + + return Base64Encoder().convert(await secretKey.extractBytes()); + } +} diff --git a/packages/at_chops/lib/src/algorithm/at_algorithm.dart b/packages/at_chops/lib/src/algorithm/at_algorithm.dart index 62588a3f..f41a6d71 100644 --- a/packages/at_chops/lib/src/algorithm/at_algorithm.dart +++ b/packages/at_chops/lib/src/algorithm/at_algorithm.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; @@ -5,26 +6,30 @@ import 'package:at_chops/src/algorithm/at_iv.dart'; import 'package:at_chops/src/key/at_key_pair.dart'; import 'package:at_chops/src/key/at_private_key.dart'; import 'package:at_chops/src/key/at_public_key.dart'; +import 'package:at_chops/src/model/hash_params.dart'; /// Interface for encrypting and decrypting data. Check [DefaultEncryptionAlgo] for sample implementation. -abstract class AtEncryptionAlgorithm { +abstract class AtEncryptionAlgorithm { /// Encrypts the passed bytes. Bytes are passed as [Uint8List]. Encode String data type to [Uint8List] using [utf8.encode]. - Uint8List encrypt(Uint8List plainData); + V encrypt(T plainData); /// Decrypts the passed encrypted bytes. - Uint8List decrypt(Uint8List encryptedData); + V decrypt(T encryptedData); } /// Interface for symmetric encryption algorithms. Check [AESEncryptionAlgo] for sample implementation. -abstract class SymmetricEncryptionAlgorithm extends AtEncryptionAlgorithm { +abstract class SymmetricEncryptionAlgorithm + extends AtEncryptionAlgorithm { @override - Uint8List encrypt(Uint8List plainData, {InitialisationVector iv}); + V encrypt(T plainData, {InitialisationVector iv}); + @override - Uint8List decrypt(Uint8List encryptedData, {InitialisationVector iv}); + V decrypt(T encryptedData, {InitialisationVector iv}); } /// Interface for asymmetric encryption algorithms. Check [DefaultEncryptionAlgo] for sample implementation. -abstract class ASymmetricEncryptionAlgorithm extends AtEncryptionAlgorithm { +abstract class ASymmetricEncryptionAlgorithm + extends AtEncryptionAlgorithm { AtPublicKey? atPublicKey; AtPrivateKey? atPrivateKey; @@ -48,7 +53,7 @@ abstract class AtSigningAlgorithm { } /// Interface for hashing data. Refer [DefaultHash] for sample implementation. -abstract class AtHashingAlgorithm { +abstract class AtHashingAlgorithm { /// Hashes the passed data - String hash(Uint8List data); + FutureOr hash(K data, {covariant HashParams? hashParams}); } diff --git a/packages/at_chops/lib/src/algorithm/at_hashing_algo_factory.dart b/packages/at_chops/lib/src/algorithm/at_hashing_algo_factory.dart new file mode 100644 index 00000000..6aa2f7da --- /dev/null +++ b/packages/at_chops/lib/src/algorithm/at_hashing_algo_factory.dart @@ -0,0 +1,29 @@ +import 'package:at_chops/src/algorithm/algo_type.dart'; +import 'package:at_chops/src/algorithm/argon2id_hashing_algo.dart'; +import 'package:at_chops/src/algorithm/at_algorithm.dart'; +import 'package:at_chops/src/algorithm/default_hashing_algo.dart'; +import 'package:at_commons/at_commons.dart'; + +/// A factory class for creating instances of different hashing algorithms +/// based on the specified [HashingAlgoType]. +/// +/// The [AtHashingAlgorithmFactory] class provides a static method +/// [getHashingAlgorithm] which returns the appropriate hashing algorithm +/// implementation corresponding to the provided [HashingAlgoType]. +class AtHashingAlgorithmFactory { + /// Returns an instance of [AtHashingAlgorithm] based on the provided [HashingAlgoType]. + /// + /// The method supports the following hashing algorithms: + /// - [HashingAlgoType.md5]: returns an instance of [DefaultHash] (MD5 hashing). + /// - [HashingAlgoType.argon2id]: returns an instance of [Argon2idHashingAlgo] (Argon2id hashing). + /// + /// Throws an [AtException] if an unsupported hashing algorithm is passed. + static AtHashingAlgorithm getHashingAlgorithm(HashingAlgoType algoType) { + switch (algoType) { + case HashingAlgoType.argon2id: + return Argon2idHashingAlgo(); + default: + throw AtException('Unsupported hashing algorithm'); + } + } +} diff --git a/packages/at_chops/lib/src/algorithm/default_hashing_algo.dart b/packages/at_chops/lib/src/algorithm/default_hashing_algo.dart index eb057a9d..b1d62083 100644 --- a/packages/at_chops/lib/src/algorithm/default_hashing_algo.dart +++ b/packages/at_chops/lib/src/algorithm/default_hashing_algo.dart @@ -1,9 +1,10 @@ import 'package:at_chops/src/algorithm/at_algorithm.dart'; +import 'package:at_chops/src/model/hash_params.dart'; import 'package:crypto/crypto.dart'; -class DefaultHash implements AtHashingAlgorithm { +class DefaultHash implements AtHashingAlgorithm, String> { @override - String hash(List data) { + String hash(List data, {HashParams? hashParams}) { return md5.convert(data).toString(); } } diff --git a/packages/at_chops/lib/src/at_chops_impl.dart b/packages/at_chops/lib/src/at_chops_impl.dart index 67d33daa..55b18450 100644 --- a/packages/at_chops/lib/src/at_chops_impl.dart +++ b/packages/at_chops/lib/src/at_chops_impl.dart @@ -7,10 +7,10 @@ import 'package:at_chops/src/algorithm/aes_encryption_algo.dart'; import 'package:at_chops/src/algorithm/algo_type.dart'; import 'package:at_chops/src/algorithm/at_algorithm.dart'; import 'package:at_chops/src/algorithm/at_iv.dart'; -import 'package:at_chops/src/algorithm/rsa_encryption_algo.dart'; import 'package:at_chops/src/algorithm/default_signing_algo.dart'; import 'package:at_chops/src/algorithm/ecc_signing_algo.dart'; import 'package:at_chops/src/algorithm/pkam_signing_algo.dart'; +import 'package:at_chops/src/algorithm/rsa_encryption_algo.dart'; import 'package:at_chops/src/at_chops_base.dart'; import 'package:at_chops/src/key/at_key_pair.dart'; import 'package:at_chops/src/key/impl/aes_key.dart'; @@ -25,6 +25,8 @@ import 'package:at_chops/src/metadata/signing_result.dart'; import 'package:at_commons/at_commons.dart'; import 'package:at_utils/at_logger.dart'; +import 'algorithm/default_hashing_algo.dart'; + class AtChopsImpl extends AtChops { AtChopsImpl(super.atChopsKeys); @@ -143,7 +145,10 @@ class AtChopsImpl extends AtChops { @override String hash(Uint8List signedData, AtHashingAlgorithm hashingAlgorithm) { - return hashingAlgorithm.hash(signedData); + if (hashingAlgorithm.runtimeType == DefaultHash) { + return DefaultHash().hash(signedData); + } + throw AtException('$hashingAlgorithm is not supported'); } @override diff --git a/packages/at_chops/lib/src/at_keys_crypto.dart b/packages/at_chops/lib/src/at_keys_crypto.dart new file mode 100644 index 00000000..fe2764a6 --- /dev/null +++ b/packages/at_chops/lib/src/at_keys_crypto.dart @@ -0,0 +1,117 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:at_chops/at_chops.dart'; +import 'package:at_chops/src/algorithm/at_algorithm.dart'; +import 'package:at_chops/src/algorithm/at_hashing_algo_factory.dart'; +import 'package:at_chops/src/model/hash_params.dart'; +import 'package:at_commons/at_commons.dart'; + +/// An abstract class that provides cryptographic operations for AtKeys using +/// specific hashing algorithms. +/// +/// This class allows for encryption and decryption of AtKeys +/// with a passphrase, using the provided hashing algorithm type. +abstract class AtKeysCrypto { + /// Returns an instance of [_AtKeysCryptoImpl] based on the + /// provided [hashingAlgoType]. + /// + /// The [hashingAlgoType] parameter determines the hashing + /// algorithm to be used in the cryptographic operations. + static AtKeysCrypto fromHashingAlgorithm(HashingAlgoType hashingAlgoType) => + _AtKeysCryptoImpl(hashingAlgoType); + + /// Encrypts the given [plainAtKeys] using the provided [passPhrase] and + /// optional [hashParams]. + /// + /// This method returns an [AtEncrypted] object, which contains the encrypted + /// AtKeys. + /// + /// - [plainAtKeys]: The plain text AtKeys to be encrypted. + /// - [passPhrase]: The passphrase used for encryption. + /// - [hashParams]: Optional parameters used for hashing in the + /// encryption process. + /// + /// Returns a [FutureOr] that resolves to [AtEncrypted] on success. + FutureOr encrypt(String plainAtKeys, String passPhrase, + {HashParams? hashParams}); + + /// Decrypts the given [atEncrypted] object back to its original + /// plain text format using the provided [passPhrase] and + /// optional [hashParams]. + /// + /// - [atEncrypted]: The encrypted AtKeys object to be decrypted. + /// - [passPhrase]: The passphrase used for decryption. + /// - [hashParams]: Optional parameters used for hashing in the + /// decryption process. + /// + /// Returns a [FutureOr] that resolves to a [String] containing + /// the decrypted AtKeys on success. + FutureOr decrypt(AtEncrypted atEncrypted, String passPhrase, + {HashParams? hashParams}); +} + +/// The implementation class of [AtKeysCrypto]. The implementation classes is marked private. +/// Use [AtKeysCrypto.fromHashingAlgorithm] to get an instance of [_AtKeysCryptoImpl]. +class _AtKeysCryptoImpl implements AtKeysCrypto { + final HashingAlgoType _hashingAlgoType; + + _AtKeysCryptoImpl(this._hashingAlgoType); + + @override + Future encrypt(String plainAtKeys, String passPhrase, + {HashParams? hashParams}) async { + // 1. Generate hash key based on the hashing algo type: + String hashKey = + await _getHashKey(passPhrase, _hashingAlgoType, hashParams: hashParams); + + AESKey aesKey = AESKey(hashKey); + StringAESEncryptor atEncryptionAlgorithm = StringAESEncryptor(aesKey); + + InitialisationVector iv = AtChopsUtil.generateRandomIV(16); + String encryptedContent = + atEncryptionAlgorithm.encrypt(plainAtKeys, iv: iv); + + return AtEncrypted() + ..content = encryptedContent + ..iv = base64Encode(iv.ivBytes) + ..hashingAlgoType = _hashingAlgoType; + } + + @override + Future decrypt(AtEncrypted atEncrypted, String passPhrase, + {HashParams? hashParams}) async { + if (atEncrypted.iv.isNullOrEmpty) { + throw AtDecryptionException( + 'Initialization vector is required for decryption'); + } + if (atEncrypted.content.isNullOrEmpty) { + throw AtDecryptionException('Cannot decrypt empty or null content'); + } + + // 1. Generate hash key based on the hashing algo type: + String hashKey = + await _getHashKey(passPhrase, _hashingAlgoType, hashParams: hashParams); + AESKey aesKey = AESKey(hashKey); + StringAESEncryptor atEncryptionAlgorithm = StringAESEncryptor(aesKey); + + Uint8List iv = base64Decode(atEncrypted.iv!); + InitialisationVector initialisationVector = InitialisationVector(iv); + + return atEncryptionAlgorithm.decrypt(atEncrypted.content!, + iv: initialisationVector); + } + + /// Generates a hashed key based on the provided [passPhrase] and + /// [hashingAlgoType], with optional [hashParams] for certain algorithms. + /// + /// Returns a [Future] that resolves to a [String] representing the + /// hashed key. + Future _getHashKey(String passPhrase, HashingAlgoType hashingAlgoType, + {HashParams? hashParams}) async { + AtHashingAlgorithm atHashingAlgorithm = + AtHashingAlgorithmFactory.getHashingAlgorithm(hashingAlgoType); + return await atHashingAlgorithm.hash(passPhrase, hashParams: hashParams); + } +} diff --git a/packages/at_chops/lib/src/model/at_encrypted.dart b/packages/at_chops/lib/src/model/at_encrypted.dart new file mode 100644 index 00000000..52815d04 --- /dev/null +++ b/packages/at_chops/lib/src/model/at_encrypted.dart @@ -0,0 +1,68 @@ +import 'dart:convert'; + +import 'package:at_chops/at_chops.dart'; + +/// A class that represents encrypted content, along with metadata such as +/// initialization vector (IV) and the hashing algorithm used. +/// +/// This class is used to serialize and deserialize encrypted data for +/// transmission or storage. It provides methods to convert the object +/// to JSON format and parse it back from JSON. +class AtEncrypted { + /// The encrypted content, typically represented as a Base64 string. + String? content; + + /// The initialization vector (IV) used during encryption, which adds randomness + /// to the encryption process and ensures the same content results in different + /// ciphertexts. + String? iv; + + /// The type of hashing algorithm used for encryption, represented by an + /// enum of type [HashingAlgoType]. + HashingAlgoType? hashingAlgoType; + + /// Converts this [AtEncrypted] instance into a JSON-compatible map. + /// + /// The returned map includes the encrypted content, IV, and the name of the + /// hashing algorithm used. The [hashingAlgoType] is converted to its string name. + /// + /// Returns a [Map] representing the encrypted data. + Map toJson() { + return { + 'content': content, + 'iv': iv, + 'hashingAlgoType': hashingAlgoType?.name + }; + } + + /// Creates an [AtEncrypted] instance from a JSON-compatible map. + /// + /// This method takes a [Map] as input and assigns the corresponding values + /// to the [content], [iv], and [hashingAlgoType] fields. If the hashing + /// algorithm is provided as a string, it is converted back to its enum type + /// using [HashingAlgoType.fromString]. + /// + /// Returns an [AtEncrypted] object populated with data from the map. + static AtEncrypted fromJson(Map map) { + AtEncrypted atEncrypted = AtEncrypted() + ..content = map['content'] + ..iv = map['iv']; + + if (map['hashingAlgoType'] != null && map['hashingAlgoType']!.isNotEmpty) { + atEncrypted.hashingAlgoType = + HashingAlgoType.fromString(map['hashingAlgoType']!); + } + return atEncrypted; + } + + /// Returns the string representation of this [AtEncrypted] instance. + /// + /// This method converts the object into its JSON string form by calling + /// [toJson] and encoding the resulting map using [jsonEncode]. + /// + /// Returns the JSON string representation of the object. + @override + String toString() { + return jsonEncode(toJson()); + } +} diff --git a/packages/at_chops/lib/src/model/hash_params.dart b/packages/at_chops/lib/src/model/hash_params.dart new file mode 100644 index 00000000..70def9a3 --- /dev/null +++ b/packages/at_chops/lib/src/model/hash_params.dart @@ -0,0 +1,36 @@ +/// A class that holds the parameters for configuring a hashing algorithm. +/// +/// This class is used to customize the behavior of a hashing algorithm by +/// providing control over key parameters such as parallelism, memory usage, +/// iteration count, and the length of the resulting hash. +/// +/// These parameters are particularly useful when working with algorithms +/// like Argon2id, which can be adjusted for performance and security needs. +abstract class HashParams {} + +class ArgonHashParams extends HashParams { + /// The degree of parallelism, representing the number of threads used during hashing. + /// + /// The default value is 2, meaning the hashing algorithm will use 2 threads. + int parallelism = 2; + + /// The amount of memory (in KB) to be used during the hashing process. + /// + /// The default value is 10,000 KB (10 MB). Increasing the memory value + /// can make the hashing process more resistant to brute-force attacks. + int memory = 10000; + + /// The number of iterations (time cost) applied during the hashing process. + /// + /// The default value is 2. A higher iteration count increases the time + /// required to compute the hash, providing greater security. + int iterations = 2; + + /// The length of the resulting hash in bytes. + /// + /// The default value is 32 bytes. This value controls the size of the + /// derived hash or key. + int hashLength = 32; +} + +class DefaultHashParams extends HashParams {} diff --git a/packages/at_chops/lib/types.dart b/packages/at_chops/lib/types.dart index 1c7525a6..cdd3cc84 100644 --- a/packages/at_chops/lib/types.dart +++ b/packages/at_chops/lib/types.dart @@ -6,14 +6,12 @@ library at_chops_types; // Algorithm interfaces export 'src/algorithm/at_algorithm.dart'; export 'src/algorithm/at_iv.dart'; - // Key interfaces export 'src/key/at_key_pair.dart'; export 'src/key/at_private_key.dart'; export 'src/key/at_public_key.dart'; export 'src/key/key_names.dart'; export 'src/key/key_type.dart'; - // Metadata Interfaces export 'src/metadata/at_signing_input.dart'; @@ -21,6 +19,5 @@ export 'src/metadata/encryption_metadata.dart'; export 'src/metadata/encryption_result.dart'; export 'src/metadata/signing_metadata.dart'; export 'src/metadata/signing_result.dart'; - // Util export 'src/util/at_chops_util.dart'; diff --git a/packages/at_chops/pubspec.yaml b/packages/at_chops/pubspec.yaml index 73eac488..2935f4e2 100644 --- a/packages/at_chops/pubspec.yaml +++ b/packages/at_chops/pubspec.yaml @@ -1,6 +1,6 @@ name: at_chops description: Package for at_protocol cryptographic and hashing operations -version: 2.1.0 +version: 2.2.0 repository: https://github.com/atsign-foundation/at_libraries environment: @@ -17,6 +17,7 @@ dependencies: pointycastle: ^3.7.4 at_commons: ^5.0.0 at_utils: ^3.0.19 + cryptography: ^2.7.0 dev_dependencies: lints: ^3.0.0 diff --git a/packages/at_chops/test/at_keys_crypto_test.dart b/packages/at_chops/test/at_keys_crypto_test.dart new file mode 100644 index 00000000..cd9b017c --- /dev/null +++ b/packages/at_chops/test/at_keys_crypto_test.dart @@ -0,0 +1,52 @@ +import 'dart:convert'; + +import 'package:at_chops/at_chops.dart'; +import 'package:test/test.dart'; + +void main() { + group('A group of tests to verify atKeys encryption and decryption', () { + test( + 'A test to verify the encryption and decryption of atKeys using a passphrase with the argon2id algorithm', + () async { + String plainAtKeys = jsonEncode({ + 'pkamPublicKey': 'dummy_pkam_public_key', + 'pkamPrivateKey': 'dummy_private_key' + }); + String passPhrase = 'abcd'; + + AtKeysCrypto atKeysCrypto = + AtKeysCrypto.fromHashingAlgorithm(HashingAlgoType.argon2id); + + AtEncrypted atEncrypted = + await atKeysCrypto.encrypt(plainAtKeys, passPhrase); + expect(atEncrypted.content?.isNotEmpty, true); + expect(atEncrypted.iv?.isNotEmpty, true); + expect(atEncrypted.hashingAlgoType, HashingAlgoType.argon2id); + + String decryptedAtKeys = + await atKeysCrypto.decrypt(atEncrypted, passPhrase); + Map dummyAtKeys = jsonDecode(decryptedAtKeys); + expect(dummyAtKeys['pkamPublicKey'], 'dummy_pkam_public_key'); + expect(dummyAtKeys['pkamPrivateKey'], 'dummy_private_key'); + }); + + test('A test to verify the decryption fails when pass phrase is modified', + () async { + String plainAtKeys = jsonEncode({ + 'pkamPublicKey': 'dummy_pkam_public_key', + 'pkamPrivateKey': 'dummy_private_key' + }); + + AtKeysCrypto atKeysCrypto = + AtKeysCrypto.fromHashingAlgorithm(HashingAlgoType.argon2id); + + AtEncrypted atEncrypted = await atKeysCrypto.encrypt(plainAtKeys, 'abcd'); + expect(atEncrypted.content?.isNotEmpty, true); + expect(atEncrypted.iv?.isNotEmpty, true); + expect(atEncrypted.hashingAlgoType, HashingAlgoType.argon2id); + + expect(() async => await atKeysCrypto.decrypt(atEncrypted, 'abcde'), + throwsA(predicate((dynamic e) => e is ArgumentError))); + }); + }); +} diff --git a/packages/at_chops/test/rsa_encryption_algo_test.dart b/packages/at_chops/test/rsa_encryption_algo_test.dart index 1306fa1f..2a51bab0 100644 --- a/packages/at_chops/test/rsa_encryption_algo_test.dart +++ b/packages/at_chops/test/rsa_encryption_algo_test.dart @@ -50,8 +50,7 @@ void main() { () { test('Test asymmetric encryption/decryption using rsa 2048 key pair', () { var rsa2048KeyPair = AtChopsUtil.generateAtEncryptionKeyPair(); - var defaultEncryptionAlgo = - RsaEncryptionAlgo.fromKeyPair(rsa2048KeyPair); + var defaultEncryptionAlgo = RsaEncryptionAlgo.fromKeyPair(rsa2048KeyPair); var dataToEncrypt = 'Hello World12!@'; var encryptedData = defaultEncryptionAlgo.encrypt(utf8.encode(dataToEncrypt)); From 280ec882f2eadc76e07baa800705fd73c3f91c41 Mon Sep 17 00:00:00 2001 From: Sitaram Kalluri Date: Wed, 13 Nov 2024 11:34:51 +0530 Subject: [PATCH 2/2] fix: at_chops : Throw exception if IV is not set during encryption in StringAESEncryptor. --- .../lib/src/algorithm/aes_encryption_algo.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart b/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart index f512e8ca..06c42f8f 100644 --- a/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart +++ b/packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart @@ -62,7 +62,7 @@ class StringAESEncryptor /// mandatory for the AES decryption process. /// /// - [encryptedData]: The Base64-encoded string that represents the encrypted data. - /// - [iv]: The Initialisation Vector used during encryption. Must be the same + /// - [iv]: The Initialisation Vector used during decryption. Must be the same /// IV that was used to encrypt the data. /// /// Returns a [String] that represents the decrypted data. @@ -82,19 +82,21 @@ class StringAESEncryptor } /// Encrypts the given [plainData] using AES encryption and an optional [iv]. - /// - /// If no [iv] is provided, a random 16-byte IV will be generated using - /// [AtChopsUtil.generateRandomIV]. The resulting encrypted data will be - /// Base64-encoded. + /// The resulting encrypted data will be Base64-encoded. /// /// - [plainData]: The string that needs to be encrypted. /// - [iv]: The Initialisation Vector used for encryption. If not provided, - /// a random 16-byte IV will be generated. + /// AtEncryptionException will be thrown. /// /// Returns a [String] that contains the encrypted data, encoded in Base64 format. + /// + /// Throws an [AtEncryptionException] if the [iv] is missing. @override String encrypt(String plainData, {InitialisationVector? iv}) { - iv ??= AtChopsUtil.generateRandomIV(16); + if (iv == null) { + throw AtEncryptionException( + 'Initialisation Vector (IV) is required for encryption'); + } var aesEncrypter = Encrypter(AES(Key.fromBase64(_aesKey.key))); final encrypted = aesEncrypter.encrypt(plainData, iv: IV(iv.ivBytes)); return encrypted.base64;