Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/version_and_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
app-version-check:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
# https://github.com/marketplace/actions/checkout
- uses: actions/checkout@main
Expand Down Expand Up @@ -42,7 +42,7 @@ jobs:
exit 1
run-unit-tests:
needs: app-version-check
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
# https://github.com/marketplace/actions/checkout
- uses: actions/checkout@main
Expand Down
1 change: 1 addition & 0 deletions lib/cryptography_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ library cryptography_utils;

export 'src/bip/bip.dart';
export 'src/cdsa/cdsa.dart';
export 'src/encryption/aes/aes256_randomized.dart';
export 'src/hash/hmac.dart';
export 'src/hash/keccak/keccak.dart';
export 'src/hash/pbkdf2.dart';
Expand Down
93 changes: 93 additions & 0 deletions lib/src/encryption/aes/aes256_randomized.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:cryptography_utils/cryptography_utils.dart';
import 'package:cryptography_utils/src/encryption/aes/aes_ctr.dart';
import 'package:cryptography_utils/src/encryption/aes/aes_iv.dart';
import 'package:cryptography_utils/src/encryption/aes/aes_key.dart';
import 'package:cryptography_utils/src/encryption/aes/invalid_password_exception.dart';
import 'package:cryptography_utils/src/encryption/cipher/cipher_text.dart';
import 'package:cryptography_utils/src/utils/secure_random.dart';

/// The `AES256Randomized` class provides secure encryption and decryption of text (strings)
/// using the AES-256 algorithm in CTR mode. The class enables confidential storage of textual data
/// and ensuring that access is only possible with the correct password.
///
/// Moreover, the use of a random salt (of length 16) and the SHA-256 hash function enhances security and makes attacks significantly more difficult.
///
class AES256Randomized {
/// Encrypts the provided plaintext using the given password.
/// A random 16-byte salt is generated, and the password is hashed using SHA-256 to produce the encryption key.
/// The salt and password hash are combined and hashed again to derive a secure value,
/// from which the initialization vector (IV), and checksum are extracted.
/// The plaintext is then encrypted using AES-256 in CTR mode and returned as a Base64-encoded string,
/// with the salt prepended and the checksum appended.
static String encrypt(String password, String decryptedString) {
Uint8List decryptedUint8List = utf8.encode(decryptedString);
Uint8List randomUint8List = SecureRandom.getBytes(length: 16, max: 255);

Uint8List hashedPasswordUint8List = Sha256().convert(utf8.encode(password)).byteList;
Uint8List securePasswordUint8List = Sha256().convert(randomUint8List + hashedPasswordUint8List).byteList;

AESKey aesKey = AESKey(hashedPasswordUint8List);

Uint8List initializationVectorUint8List = Uint8List.fromList(securePasswordUint8List.getRange(0, 16).toList());
AESIV aesIV = AESIV(initializationVectorUint8List);

Uint8List encryptedUint8List = AESctr.encrypt(decryptedUint8List, aesKey, aesIV).uint8List;

List<int> checksumUint8List = securePasswordUint8List.getRange(securePasswordUint8List.length - 4, securePasswordUint8List.length).toList();
List<int> encryptedStringUint8List = randomUint8List + encryptedUint8List + checksumUint8List;
String encryptedString = base64Encode(encryptedStringUint8List);

return encryptedString;
}

/// Decrypts the provided encrypted string using the specified password.
/// The password is hashed using SHA-256 to produce the encryption key.
/// The salt and checksum are extracted from the encrypted input.
/// Then, the salt and password hashes are combined and hashed again to derive a secure value,
/// from which the initialization vector (IV) is reconstructed and the checksum is validated.
/// The encrypted data is then decrypted using AES-256 in CTR mode,
/// and the resulting plaintext is returned as a string.
///
/// Throws [InvalidPasswordException] if decryption fails, for example due to an incorrect password.
static String decrypt(String password, String encryptedString) {
try {
Uint8List hashedPasswordUint8List = Sha256().convert(utf8.encode(password)).byteList;

Uint8List encryptedStringUint8List = base64Decode(encryptedString);
Uint8List randomUint8List = Uint8List.fromList(encryptedStringUint8List.getRange(0, 16).toList());
CipherText cipherText = CipherText(Uint8List.fromList(encryptedStringUint8List.getRange(16, encryptedStringUint8List.length - 4).toList()));
Uint8List securePasswordUint8List = Sha256().convert(randomUint8List + hashedPasswordUint8List).byteList;

AESKey aesKey = AESKey(hashedPasswordUint8List);

Uint8List initializationVectorUint8List = Uint8List.fromList(securePasswordUint8List.getRange(0, 16).toList());
AESIV aesIV = AESIV(initializationVectorUint8List);
Uint8List decryptedUint8List = AESctr.decrypt(cipherText, aesKey, aesIV);
String decryptedString = utf8.decode(decryptedUint8List);
return decryptedString;
} catch (e) {
throw InvalidPasswordException('Decryption failed: ${e.toString()}');
}
}

/// Validates whether the provided [password] matches the checksum of the [encryptedString].
/// This function checks if the password-derived checksum matches the stored one in the encrypted payload,
/// without actually decrypting the content.
/// Returns `true` if the password is valid; otherwise `false`.
static bool isPasswordValid(String password, String encryptedString) {
Uint8List hashedPasswordUint8List = Sha256().convert(utf8.encode(password)).byteList;

Uint8List encryptedStringUint8List = base64Decode(encryptedString);
Uint8List randomUint8List = Uint8List.fromList(encryptedStringUint8List.getRange(0, 16).toList());
List<int> expectedChecksumUint8List =
encryptedStringUint8List.getRange(encryptedStringUint8List.length - 4, encryptedStringUint8List.length).toList();

Uint8List securePasswordUint8List = Sha256().convert(randomUint8List + hashedPasswordUint8List).byteList;
List<int> actualChecksumUint8List = securePasswordUint8List.getRange(securePasswordUint8List.length - 4, securePasswordUint8List.length).toList();

return actualChecksumUint8List.toString() == expectedChecksumUint8List.toString();
}
}
84 changes: 84 additions & 0 deletions lib/src/encryption/aes/aes_constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
class AESConstants {
static const List<int> rConList = <int>[
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, //
0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6,
0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91
];

static const List<int> sList = <int>[
99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, //
215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212,
162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247,
204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24,
150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9, 131, 44,
26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83,
209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88,
207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80,
60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182,
218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23,
196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42,
144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10,
73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200,
55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174,
8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75,
189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87,
185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148,
155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191,
230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22
];

static const List<int> t0List = <int>[
0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, //
0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102,
0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d,
0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa,
0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41,
0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453,
0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d,
0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83,
0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2,
0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795,
0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a,
0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df,
0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912,
0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc,
0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7,
0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413,
0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040,
0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d,
0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0,
0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed,
0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a,
0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78,
0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080,
0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1,
0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020,
0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18,
0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488,
0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a,
0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0,
0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54,
0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b,
0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad,
0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992,
0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd,
0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3,
0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda,
0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8,
0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4,
0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a,
0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697,
0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96,
0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c,
0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7,
0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969,
0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9,
0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9,
0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715,
0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5,
0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65,
0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929,
0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d,
0x3a16162c
];
}
113 changes: 113 additions & 0 deletions lib/src/encryption/aes/aes_ctr.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//Copyright (c) 2018, Leo Cavalcante
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import 'dart:typed_data';

import 'package:cryptography_utils/src/encryption/aes/aes_engine.dart';
import 'package:cryptography_utils/src/encryption/aes/aes_iv.dart';
import 'package:cryptography_utils/src/encryption/aes/aes_key.dart';
import 'package:cryptography_utils/src/encryption/cipher/block_cipher/ctr_block_cipher.dart';
import 'package:cryptography_utils/src/encryption/cipher/block_cipher/padded_block_cipher.dart';
import 'package:cryptography_utils/src/encryption/cipher/block_cipher/padding/pkcs7_padding.dart';
import 'package:cryptography_utils/src/encryption/cipher/cipher_key_with_iv.dart';
import 'package:cryptography_utils/src/encryption/cipher/cipher_mode.dart';
import 'package:cryptography_utils/src/encryption/cipher/cipher_text.dart';

/// AES provides encryption and decryption using the AES algorithm in stream cipher mode (SIC/CTR).
/// This implementation wraps the AES engine with a counter-based stream cipher and
/// uses PKCS7 padding to support data of arbitrary length.
///
/// ****************** implemented modes ******************
///
/// CTR (Counter mode) – transforms a block cipher into a stream cipher.
/// It generates a stream block by encrypting a nonce combined with a counter,
/// then XORs the stream with the plaintext. CTR mode allows parallel encryption,
/// random read/write access, and avoids block dependencies, making it suitable for
/// high-performance applications, secure disk encryption, and network protocols.
///
/// ****************** NOT implemented modes ******************
///
/// - ECB (Electronic Codebook) – the simplest block cipher mode, where each plaintext block is encrypted independently.
/// It is generally **not recommended for practical use** due to security weaknesses, but can be useful for educational or testing purposes.
///
/// - CBC (Cipher Block Chaining) - block cipher mode where each plaintext block is XORed with the previous ciphertext block
/// before encryption. Suitable for encrypting files, messages, or large datasets,
/// providing confidentiality by introducing dependency between blocks.
///
/// - OFB (Output feedback)- operates as a stream cipher by generating keystream blocks independent of the plaintext,
/// which are then XORed with the plaintext blocks. Well-suited for network communications and streaming applications.
///
/// - CFB (Cipher feedback ) - also a stream cipher mode that encrypts small increments of plaintext,
/// allowing for encryption of data in units smaller than the block size. An error in one ciphertext byte propagates
/// to several subsequent plaintext bytes during decryption. Generally less efficient compared to other modes.
///
/// - XTS (XEX-based Tweaked Codebook mode with ciphertext Stealing) - block cipher mode for encrypting data on storage devices.
/// It allows encryption of data whose length is not a multiple of the block size **without padding** by using ciphertext stealing.
/// Provides high performance and protection against ciphertext manipulation,
/// commonly used for encrypting sectors on hard drives, SSDs, and USB drives.

// TODO(Balldyna): To support multiple AES modes in the future, this class should be refactored.
// Consider the following steps:
// - introduce an enum (`AesMode`) to represent supported block cipher modes
// - add appropriate try-catch blocks for mode-specific behavior
// - rename this class to reflect its extended capability (e.g., 'AES').

class AESctr {
/// Encrypts the given [uint8List] using the specified [aesKey] and [aesIV].
/// The encryption process uses a padded block cipher with PKCS7 padding
/// and AES in CTR mode to ensure compatibility with inputs of any size.
/// Returns an [CipherText] object containing the encrypted bytes.
static CipherText encrypt(Uint8List uint8List, AESKey aesKey, AESIV aesIV) {
PaddedBlockCipher paddedBlockCipher = PaddedBlockCipher(
blockCipher: CTRBlockCipher(
AESEngine(
cipherMode: CipherMode.encryption,
cipherKeyWithIV: CipherKeyWithIV<AESKey>(aesKey, aesIV.uint8List),
),
),
cipherPadding: Pkcs7Padding(),
);
return CipherText(paddedBlockCipher.process(uint8List));
}

/// Decrypts the given [cipherText] data using the specified [aesKey] and [aesIV].
/// The decryption process matches the encryption configuration,
/// using PKCS7 padding removal and AES in CTR mode.
/// Returns the original plaintext as a [Uint8List].
static Uint8List decrypt(CipherText cipherText, AESKey aesKey, AESIV aesIV) {
PaddedBlockCipher paddedBlockCipher = PaddedBlockCipher(
blockCipher: CTRBlockCipher(
AESEngine(
cipherMode: CipherMode.decryption,
cipherKeyWithIV: CipherKeyWithIV<AESKey>(aesKey, aesIV.uint8List),
),
),
cipherPadding: Pkcs7Padding(),
);
return paddedBlockCipher.process(cipherText.uint8List);
}
}
Loading