Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: at_chops faster aes impl #744

Open
wants to merge 13 commits into
base: trunk
Choose a base branch
from
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 @@ -11,20 +11,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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, relevant RFC: RFC 5652

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
Loading