Skip to content

Commit

Permalink
Merge pull request #744 from atsign-foundation/at_chops_faster_aes
Browse files Browse the repository at this point in the history
feat: at_chops faster aes impl
  • Loading branch information
XavierChanth authored Feb 18, 2025
2 parents d928ed2 + 8ac9637 commit 2de26f5
Show file tree
Hide file tree
Showing 18 changed files with 539 additions and 64 deletions.
2 changes: 2 additions & 0 deletions packages/at_chops/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 3.0.0
- feat: Faster AES encryption/decryption using better_crypto
## 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
Expand Down
6 changes: 3 additions & 3 deletions packages/at_chops/example/at_chops_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ void main(List<String> args) async {
final data = 'Hello World';
//1.1 encrypt the data using [atEncryptionKeyPair.publicKey]
final encryptionResult =
atChops.encryptString(data, EncryptionKeyType.rsa2048);
await atChops.encryptString(data, EncryptionKeyType.rsa2048);

//1.2 decrypt the data using [atEncryptionKeyPair.privateKey]
final decryptionResult =
atChops.decryptString(encryptionResult.result, EncryptionKeyType.rsa2048);
final decryptionResult = await atChops.decryptString(
encryptionResult.result, EncryptionKeyType.rsa2048);
assert(data == decryptionResult.result, true);

// 2 - Signing and data verification using asymmetric key pair
Expand Down
44 changes: 44 additions & 0 deletions packages/at_chops/lib/src/algorithm/aes_ctr_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:at_chops/at_chops.dart';
import 'package:at_commons/at_commons.dart';
import 'package:better_cryptography/better_cryptography.dart';

/// A factory class to create AES-CTR encryption algorithms based on the key length.
///
/// The `AesCtrFactory` class provides a static method to create an instance of
/// the `AesCtr` encryption algorithm. The key length of the provided `AESKey`
/// determines the specific variant of AES-CTR to be instantiated.
class AesCtrFactory {
/// Creates an `AesCtr` encryption algorithm based on the key length of the given [aesKey].
///
/// The `aesKey` must have a length of 16, 24, or 32 bytes to correspond to AES-128, AES-192,
/// or AES-256 respectively. A `MacAlgorithm.empty` is used for each variant.
///
/// Throws an [AtEncryptionException] if the provided key length is invalid.
///
/// Example usage:
/// ```dart
/// AESKey aesKey =AESKey.generate(32);//pass the length in bytes
/// AesCtr encryptionAlgo = AesCtrFactory.createEncryptionAlgo(aesKey);
/// ```
///
/// - [aesKey]: An instance of `AESKey` containing the encryption key.
/// - Returns: An instance of `AesCtr` configured for the appropriate key length.
///
/// Supported key lengths:
/// - 16 bytes for AES-128
/// - 24 bytes for AES-192
/// - 32 bytes for AES-256
static AesCtr createEncryptionAlgo(AESKey aesKey) {
switch (aesKey.getLength()) {
case 16:
return AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty);
case 24:
return AesCtr.with192bits(macAlgorithm: MacAlgorithm.empty);
case 32:
return AesCtr.with256bits(macAlgorithm: MacAlgorithm.empty);
default:
throw AtEncryptionException(
'Invalid AES key length. Valid lengths are 16/24/32 bytes');
}
}
}
47 changes: 34 additions & 13 deletions packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
import 'dart:async';
import 'dart:typed_data';

import 'dart:convert';
import 'package:at_chops/src/algorithm/aes_ctr_factory.dart';
import 'package:at_chops/src/algorithm/padding/padding.dart';
import 'package:at_chops/at_chops.dart';
import 'package:at_chops/src/algorithm/at_algorithm.dart';
import 'package:at_chops/src/algorithm/padding/padding_params.dart';
import 'package:at_chops/src/algorithm/padding/pkcs7padding.dart';
import 'package:at_commons/at_commons.dart';
import 'package:encrypt/encrypt.dart';
import 'package:better_cryptography/better_cryptography.dart';

/// A class that provides AES encryption and decryption for Uint8List,
/// implementing the [SymmetricEncryptionAlgorithm] interface.
class AESEncryptionAlgo
implements SymmetricEncryptionAlgorithm<Uint8List, Uint8List> {
final AESKey _aesKey;

AESEncryptionAlgo(this._aesKey);
PaddingAlgorithm? paddingAlgo;
AESEncryptionAlgo(this._aesKey) {
paddingAlgo ??= PKCS7Padding(PaddingParams()..blockSize = 16);
}

@override
Uint8List encrypt(Uint8List plainData, {InitialisationVector? iv}) {
var aesEncrypter = Encrypter(AES(Key.fromBase64(_aesKey.key)));
final encrypted =
aesEncrypter.encryptBytes(plainData, iv: _getIVFromBytes(iv?.ivBytes));
return encrypted.bytes;
FutureOr<Uint8List> encrypt(Uint8List plainData,
{InitialisationVector? iv}) async {
final encryptionAlgo = AesCtrFactory.createEncryptionAlgo(_aesKey);
var paddedData = paddingAlgo!.addPadding(plainData);
final secretKey = await encryptionAlgo
.newSecretKeyFromBytes(base64Decode(_aesKey.toString()));
var secretBox = await encryptionAlgo.encrypt(paddedData,
nonce: _getIVFromBytes(iv?.ivBytes)!.bytes, secretKey: secretKey);
return Uint8List.fromList(secretBox.cipherText);
}

@override
Uint8List decrypt(Uint8List encryptedData, {InitialisationVector? iv}) {
var aesKey = AES(Key.fromBase64(_aesKey.toString()));
var decrypter = Encrypter(aesKey);
return Uint8List.fromList(decrypter.decryptBytes(Encrypted(encryptedData),
iv: _getIVFromBytes(iv?.ivBytes)));
FutureOr<Uint8List> decrypt(Uint8List encryptedData,
{InitialisationVector? iv}) async {
final encryptionAlgo = AesCtrFactory.createEncryptionAlgo(_aesKey);
var secretBox = SecretBox(
encryptedData,
nonce: _getIVFromBytes(iv?.ivBytes)!.bytes,
mac: Mac.empty,
);
final secretKey = await encryptionAlgo
.newSecretKeyFromBytes(base64Decode(_aesKey.toString()));
var decryptedBytesWithPadding =
await encryptionAlgo.decrypt(secretBox, secretKey: secretKey);
var decryptedBytes = paddingAlgo!.removePadding(decryptedBytesWithPadding);
return Uint8List.fromList(decryptedBytes);
}

IV? _getIVFromBytes(Uint8List? ivBytes) {
Expand Down
8 changes: 4 additions & 4 deletions packages/at_chops/lib/src/algorithm/at_algorithm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ import 'package:at_chops/src/model/hash_params.dart';
/// Interface for encrypting and decrypting data. Check [DefaultEncryptionAlgo] for sample implementation.
abstract class AtEncryptionAlgorithm<T, V> {
/// Encrypts the passed bytes. Bytes are passed as [Uint8List]. Encode String data type to [Uint8List] using [utf8.encode].
V encrypt(T plainData);
FutureOr<V> encrypt(T plainData);

/// Decrypts the passed encrypted bytes.
V decrypt(T encryptedData);
FutureOr<V> decrypt(T encryptedData);
}

/// Interface for symmetric encryption algorithms. Check [AESEncryptionAlgo] for sample implementation.
abstract class SymmetricEncryptionAlgorithm<T, V>
extends AtEncryptionAlgorithm<T, V> {
@override
V encrypt(T plainData, {InitialisationVector iv});
FutureOr<V> encrypt(T plainData, {InitialisationVector iv});

@override
V decrypt(T encryptedData, {InitialisationVector iv});
FutureOr<V> decrypt(T encryptedData, {InitialisationVector iv});
}

/// Interface for asymmetric encryption algorithms. Check [DefaultEncryptionAlgo] for sample implementation.
Expand Down
38 changes: 38 additions & 0 deletions packages/at_chops/lib/src/algorithm/padding/padding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/// An abstract class that defines a padding algorithm for AES encryption and decryption.
///
/// The `PaddingAlgorithm` class provides methods to add padding bytes during encryption
/// and to remove padding bytes during decryption. Padding ensures that the input data
/// size is compatible with the block size required by AES encryption algorithms.
abstract class PaddingAlgorithm {
/// Adds padding bytes to the given [data] to make its length a multiple of the AES block size.
///
/// This method appends padding bytes to the input data so that its length becomes
/// a multiple of the AES block size (16 bytes). The exact padding scheme is
/// implementation-dependent.
///
/// - [data]: A list of bytes representing the input data to be padded.
/// - Returns: A new list of bytes containing the padded data.
///
/// Example usage:
/// ```dart
/// PaddingAlgorithm paddingAlgorithm = PKCS7Padding();
/// List<int> paddedData = paddingAlgorithm.addPadding([0x01, 0x02, 0x03]);
/// ```
List<int> addPadding(List<int> data);

/// Removes padding bytes from the given [data], restoring it to its original unpadded form.
///
/// This method removes any padding bytes that were added during encryption to return
/// the data to its original state. The exact removal logic depends on the padding
/// scheme used during encryption.
///
/// - [data]: A list of bytes representing the padded input data.
/// - Returns: A new list of bytes containing the original unpadded data.
///
/// Example usage:
/// ```dart
/// PaddingAlgorithm paddingAlgorithm = PKCS7Padding();
/// List<int> unpaddedData = paddingAlgorithm.removePadding([0x01, 0x02, 0x03, 0x05, 0x05, 0x05]);
/// ```
List<int> removePadding(List<int> data);
}
13 changes: 13 additions & 0 deletions packages/at_chops/lib/src/algorithm/padding/padding_params.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// A class that defines parameters for padding algorithms used in AES encryption.
///
/// The `PaddingParams` class provides configurable parameters required for
/// padding algorithms, such as the block size. These parameters are used to
/// ensure that data conforms to the block size required by AES encryption.
class PaddingParams {
/// The block size (in bytes) used for padding.
///
/// The default value is `16`, which corresponds to the block size of AES encryption.
/// This value determines the size to which input data will be padded to ensure
/// compatibility with the encryption algorithm.
int blockSize = 16;
}
49 changes: 49 additions & 0 deletions packages/at_chops/lib/src/algorithm/padding/pkcs7padding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:at_chops/src/algorithm/padding/padding.dart';
import 'package:at_chops/src/algorithm/padding/padding_params.dart';
import 'package:at_commons/at_commons.dart';

class PKCS7Padding implements PaddingAlgorithm {
final PaddingParams _paddingParams;
PKCS7Padding(this._paddingParams);
@override
List<int> addPadding(List<int> data) {
if (_paddingParams.blockSize <= 0 || _paddingParams.blockSize > 255) {
throw AtEncryptionException('Block size must be between 1 and 255.');
}

// Calculate the number of padding bytes needed
int padding =
_paddingParams.blockSize - (data.length % _paddingParams.blockSize);

// Add padding bytes to the data
List<int> paddedData = List.from(data);
paddedData.addAll(List.filled(padding, padding));

return paddedData;
}

@override
List<int> removePadding(List<int> data) {
if (data.isEmpty) {
throw AtDecryptionException('Encrypted data cannot be empty');
}

// Get the value of the last byte (padding length)
int paddingLength = data.last;

// Validate padding length
if (paddingLength <= 0 || paddingLength > data.length) {
throw AtDecryptionException('Invalid padding length');
}

// Check if all padding bytes are valid
for (int i = data.length - paddingLength; i < data.length; i++) {
if (data[i] != paddingLength) {
throw AtDecryptionException('Invalid PKCS7 padding');
}
}

// Return the data without padding
return data.sublist(0, data.length - paddingLength);
}
}
9 changes: 5 additions & 4 deletions packages/at_chops/lib/src/at_chops_base.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:typed_data';

import 'package:at_chops/at_chops.dart';
Expand Down Expand Up @@ -37,7 +38,7 @@ abstract class AtChops {
/// If [encryptionKeyType] is [EncryptionKeyType.rsa2048] then [encryptionAlgorithm] will be set to [RsaEncryptionAlgo]
/// [keyName] specifies which key pair to use if user has multiple key pairs configured.
/// If [keyName] is not passed default encryption/decryption keypair from .atKeys file will be used.
AtEncryptionResult encryptBytes(
FutureOr<AtEncryptionResult> encryptBytes(
Uint8List data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
Expand All @@ -47,7 +48,7 @@ abstract class AtChops {
/// If [encryptionKeyType] is [EncryptionKeyType.rsa2048] then [encryptionAlgorithm] will be set to [RsaEncryptionAlgo]
/// [keyName] specifies which key pair to use if user has multiple key pairs configured.
/// If [keyName] is not passed default encryption/decryption keypair from .atKeys file will be used.
AtEncryptionResult encryptString(
FutureOr<AtEncryptionResult> encryptString(
String data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
Expand All @@ -57,7 +58,7 @@ abstract class AtChops {
/// If [encryptionKeyType] is [EncryptionKeyType.rsa2048] then [encryptionAlgorithm] will be set to [RsaEncryptionAlgo]
/// [keyName] specifies which key pair to use if user has multiple key pairs configured.
/// If [keyName] is not passed default encryption/decryption keypair from .atKeys file will be used.
AtEncryptionResult decryptBytes(
FutureOr<AtEncryptionResult> decryptBytes(
Uint8List data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
Expand All @@ -67,7 +68,7 @@ abstract class AtChops {
/// If [encryptionKeyType] is [EncryptionKeyType.rsa2048] then [encryptionAlgorithm] will be set to [RsaEncryptionAlgo]
/// [keyName] specifies which key pair to use if user has multiple key pairs configured.
/// If [keyName] is not passed default encryption/decryption keypair from .atKeys file will be used.
AtEncryptionResult decryptString(
FutureOr<AtEncryptionResult> decryptString(
String data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
Expand Down
27 changes: 15 additions & 12 deletions packages/at_chops/lib/src/at_chops_impl.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// ignore_for_file: unnecessary_cast

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

Expand Down Expand Up @@ -33,11 +34,11 @@ class AtChopsImpl extends AtChops {
final AtSignLogger _logger = AtSignLogger('AtChopsImpl');

@override
AtEncryptionResult decryptBytes(
FutureOr<AtEncryptionResult> decryptBytes(
Uint8List data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
InitialisationVector? iv}) {
InitialisationVector? iv}) async {
try {
encryptionAlgorithm ??=
_getEncryptionAlgorithm(encryptionKeyType, keyName)!;
Expand All @@ -52,7 +53,8 @@ class AtChopsImpl extends AtChops {
..atEncryptionMetaData = atEncryptionMetaData
..atEncryptionResultType = AtEncryptionResultType.bytes;
if (encryptionAlgorithm is SymmetricEncryptionAlgorithm) {
atEncryptionResult.result = encryptionAlgorithm.decrypt(data, iv: iv!);
atEncryptionResult.result =
await encryptionAlgorithm.decrypt(data, iv: iv!);
atEncryptionMetaData.iv = iv;
} else {
atEncryptionResult.result = encryptionAlgorithm.decrypt(data);
Expand All @@ -70,13 +72,13 @@ class AtChopsImpl extends AtChops {
/// Decode the encrypted string to base64.
/// Decode the encrypted byte to utf8 to support emoji chars.
@override
AtEncryptionResult decryptString(
FutureOr<AtEncryptionResult> decryptString(
String data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
InitialisationVector? iv}) {
InitialisationVector? iv}) async {
try {
final decryptionResult = decryptBytes(
final decryptionResult = await decryptBytes(
base64Decode(data), encryptionKeyType,
encryptionAlgorithm: encryptionAlgorithm, keyName: keyName, iv: iv);
final atEncryptionResult = AtEncryptionResult()
Expand All @@ -90,11 +92,11 @@ class AtChopsImpl extends AtChops {
}

@override
AtEncryptionResult encryptBytes(
FutureOr<AtEncryptionResult> encryptBytes(
Uint8List data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
InitialisationVector? iv}) {
InitialisationVector? iv}) async {
try {
encryptionAlgorithm ??=
_getEncryptionAlgorithm(encryptionKeyType, keyName)!;
Expand All @@ -105,7 +107,8 @@ class AtChopsImpl extends AtChops {
..atEncryptionMetaData = atEncryptionMetaData
..atEncryptionResultType = AtEncryptionResultType.bytes;
if (encryptionAlgorithm is SymmetricEncryptionAlgorithm) {
atEncryptionResult.result = encryptionAlgorithm.encrypt(data, iv: iv!);
atEncryptionResult.result =
await encryptionAlgorithm.encrypt(data, iv: iv!);
atEncryptionMetaData.iv = iv;
} else {
atEncryptionResult.result = encryptionAlgorithm.encrypt(data);
Expand All @@ -123,14 +126,14 @@ class AtChopsImpl extends AtChops {
/// Encode the input string to utf8 to support emoji chars.
/// Encode the encrypted bytes to base64.
@override
AtEncryptionResult encryptString(
FutureOr<AtEncryptionResult> encryptString(
String data, EncryptionKeyType encryptionKeyType,
{AtEncryptionAlgorithm? encryptionAlgorithm,
String? keyName,
InitialisationVector? iv}) {
InitialisationVector? iv}) async {
try {
final utfEncodedData = utf8.encode(data);
final encryptionResult = encryptBytes(
final encryptionResult = await encryptBytes(
Uint8List.fromList(utfEncodedData), encryptionKeyType,
keyName: keyName, encryptionAlgorithm: encryptionAlgorithm, iv: iv);
final atEncryptionResult = AtEncryptionResult()
Expand Down
Loading

0 comments on commit 2de26f5

Please sign in to comment.