Pārlūkot izejas kodu

update. aes utils.

JamesZhange 2 mēneši atpakaļ
vecāks
revīzija
34da2aef5b

+ 304 - 29
lib/mathUtils/aesUtils.dart

@@ -1,54 +1,329 @@
 
 import 'dart:convert';
 import 'dart:typed_data';
-import 'package:encrypt/encrypt.dart';
+import 'package:encrypt/encrypt.dart' as encrypt;
+import 'package:pointycastle/export.dart';
+import '../utils/hexUtils.dart';
+import 'cryptData.dart';
 
 
-class CryptData {
-  final Uint8List _byte;
 
-  CryptData(Uint8List byte) : _byte = byte;
-  CryptData.fromUtf8(String base64data) : _byte = base64Decode(base64data);
-  CryptData.fromBase64(String base64data) : _byte = base64Decode(base64data);
-  CryptData.fromHex(String hexData) : _byte = decodeHexString(hexData);
-
-  Uint8List get byte => _byte;
-  String get base64 => base64Encode(_byte);
-}
-
-class EncryptedAesCbc {
+// AES CBC 加密结果,存储 IV 和 加密数据
+class CryptCBCData {
   final CryptData iv;
   final CryptData encrypted;
+  CryptData? salt;
 
-  EncryptedAesCbc({
+  CryptCBCData({
     required this.iv,
     required this.encrypted,
+    this.salt,
   });
 
-  EncryptedAesCbc.fromBase64({
+  CryptCBCData.fromBase64({
     required String base64iv,
     required String base64encrypted,
-  }) : iv = CryptData.fromBase64(base64iv), encrypted = CryptData.fromBase64(base64encrypted);
+    String? base64salt,
+  }) : iv = CryptData.fromBase64(base64iv),
+        encrypted = CryptData.fromBase64(base64encrypted),
+        salt = base64salt != null ? CryptData.fromBase64(base64salt) : null;
 
 }
 
-EncryptedAesCbc encryptAesCbc(String plainText, CryptData password) {
-  final iv = IV.fromLength(16); // AES-CBC 必需的16字节IV
-  final key = Key(password.byte);
-  final encrypter = Encrypter(AES(key, mode: AESMode.cbc));
 
-  final encrypted = encrypter.encrypt(plainText, iv: iv);
 
-  return EncryptedAesCbc(iv: CryptData(iv.bytes), encrypted: CryptData(encrypted.bytes));
-}
 
+/// ------------------- AES 编解码工具类 -------------------
+///
+
+class AESUtils {
+
+
+  /// ---------- CBC 模式 ----------
+
+  /// 默认密钥长度(字节)
+  static const int _defaultKeyLength = 32; // AES-256
+
+  /// 默认 IV 长度(字节)
+  static const int _ivLength = 16;
+
+  /// 生成随机密钥
+  /// [keyLength] 密钥长度:16(AES-128), 24(AES-192), 32(AES-256)
+  static CryptData generateKey({int keyLength = _defaultKeyLength}) {
+    if (![16, 24, 32].contains(keyLength)) {
+      throw ArgumentError('密钥长度必须是 16、24 或 32 字节');
+    }
+
+    final secureRandom = SecureRandom('Fortuna');
+    secureRandom.seed(KeyParameter(Uint8List.fromList(
+        List.generate(32, (_) => DateTime.now().microsecond))));
+
+    return CryptData(secureRandom.nextBytes(keyLength));
+  }
+
+  /// 生成随机 IV
+  static CryptData generateIV() {
+    final secureRandom = SecureRandom('Fortuna');
+    secureRandom.seed(KeyParameter(Uint8List.fromList(
+        List.generate(32, (_) => DateTime.now().microsecond))));
+
+    return CryptData(secureRandom.nextBytes(_ivLength));
+  }
+
+  /// AES 加密
+  /// [plainText] 明文
+  /// [key] 密钥(16/24/32 字节)
+  /// [iv] 初始化向量(16 字节),如果为空则自动生成
+  /// 返回包含 IV 和密文的 Map,格式为 {'iv': iv_hex, 'encrypted': encrypted_base64}
+  static CryptCBCData encryptCBC(String plainText, CryptData key, {CryptData? iv}) {
+    try {
+      // 验证密钥长度
+      if (![16, 24, 32].contains(key.bytes.length)) {
+        throw ArgumentError('密钥长度必须是 16、24 或 32 字节');
+      }
+
+      // 如果未提供 IV,则生成新的 IV
+      final usedIv = iv ?? generateIV();
+
+      // 验证 IV 长度
+      if (usedIv.bytes.length != _ivLength) {
+        throw ArgumentError('IV 长度必须是 16 字节');
+      }
+
+      // 创建加密器
+      final encrypter = encrypt.Encrypter(
+        encrypt.AES(
+          encrypt.Key(key.bytes),
+          mode: encrypt.AESMode.cbc,
+          padding: 'PKCS7',
+        ),
+      );
+
+      // 执行加密
+      final encrypted = encrypter.encrypt(plainText, iv: encrypt.IV(usedIv.bytes));
+
+      // 返回 IV 和密文(IV用十六进制,密文用base64)
+      return CryptCBCData(
+        iv: CryptData(usedIv.bytes),
+        encrypted: CryptData(encrypted.bytes),
+      );
+
+    } catch (e) {
+      throw Exception('AES encrypt failed: $e');
+    }
+  }
+
+  /// AES 解密
+  /// [encryptedText] 密文(base64 格式)
+  /// [key] 密钥(16/24/32 字节)
+  /// [iv] 初始化向量(16 字节)
+  /// 返回解密后的明文字符串
+  // static String decryptCBC(String encryptedText, Uint8List key, Uint8List iv) {
+  static String decryptCBC(CryptCBCData encrypted, CryptData key) {
+    try {
+      // 验证密钥长度
+      if (![16, 24, 32].contains(key.bytes.length)) {
+        throw ArgumentError('key must be 16 or 24 or 32 bytes');
+      }
+
+      // 验证 IV 长度
+      if (encrypted.iv.bytes.length != _ivLength) {
+        throw ArgumentError('IV must be 16 bytes');
+      }
+
+      // 创建解密器
+      final encrypter = encrypt.Encrypter(
+        encrypt.AES(
+          encrypt.Key(key.bytes),
+          mode: encrypt.AESMode.cbc,
+          padding: 'PKCS7',
+        ),
+      );
+
+      // 执行解密
+      final decrypted = encrypter.decrypt64(
+        encrypted.encrypted.base64,
+        iv: encrypt.IV(encrypted.iv.bytes),
+      );
+
+      return decrypted;
+    } catch (e) {
+      throw Exception('AES decrypt failed: $e');
+    }
+  }
+
+
+  /// 简化的加密方法 - 自动处理密钥派生
+  /// [plainText] 明文
+  /// [password] 密码字符串,将用于派生密钥
+  /// [salt] 盐值(可选,十六进制格式)
+  /// 返回包含 IV、盐和密文的 CryptCBCData
+  static CryptCBCData encryptCBCWithPassword(
+    String plainText,
+    String password,
+    {String? salt}
+  ) {
+    try {
+      // 如果没有提供盐值,则生成随机盐值(十六进制格式)
+      final usedSalt = salt ?? bytesToHex(generateIV().bytes, include0x: false);
 
-String decryptAesCbc(EncryptedAesCbc encrypted, CryptData password) {
-  final iv = IV(encrypted.iv.byte);
-  final key = Key(password.byte);
-  final encrypter = Encrypter(AES(key, mode: AESMode.cbc));
+      // 从密码派生密钥(使用 PBKDF2)
+      final derivedKey = _deriveKeyFromPassword(password, usedSalt);
 
-  final decrypted = encrypter.decrypt(Encrypted(encrypted.encrypted.byte), iv: iv);
+      // 执行加密
+      final result = encryptCBC(plainText, derivedKey);
+
+      // 将盐值添加到结果中
+      result.salt = CryptData.fromHex(usedSalt);
+
+      return result;
+    } catch (e) {
+      throw Exception('AES CBC encrypt with password failed: $e');
+    }
+  }
+
+  /// 简化的解密方法 - 使用密码派生密钥
+  /// [encrypted] 包含 IV、盐和密文的 CryptCBCData
+  /// [password] 密码字符串
+  /// 返回解密后的明文字符串
+  static String decryptCBCWithPassword(
+    CryptCBCData encrypted,
+    String password,
+  ) {
+    try {
+      // 验证盐值是否存在
+      if (encrypted.salt == null) {
+        throw ArgumentError('Salt is required for password-based decryption');
+      }
+
+      // 将盐值转换为十六进制字符串
+      final saltHex = bytesToHex(encrypted.salt!.bytes, include0x: false);
+
+      // 从密码派生密钥
+      final derivedKey = _deriveKeyFromPassword(password, saltHex);
+
+      // 执行解密
+      return decryptCBC(encrypted, derivedKey);
+    } catch (e) {
+      throw Exception('AES CBC decrypt with password failed: $e');
+    }
+  }
+
+  /// 从密码派生密钥(使用 PBKDF2)
+  static CryptData _deriveKeyFromPassword(String password, String salt, {int keyLength = 32}) {
+    final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
+    final params = Pbkdf2Parameters(
+      hexToBytes(salt),
+      100000, // 迭代次数
+      keyLength,
+    );
+
+    pbkdf2.init(params);
+
+    final key = Uint8List(keyLength);
+    pbkdf2.deriveKey(Uint8List.fromList(utf8.encode(password)), 0, key, 0);
+
+    return CryptData(key);
+  }
+
+  /// 验证密钥格式是否正确
+  static bool isValidKey(Uint8List key) {
+    return [16, 24, 32].contains(key.length);
+  }
+
+  /// 验证 IV 格式是否正确
+  static bool isValidIV(Uint8List iv) {
+    return iv.length == _ivLength;
+  }
+
+
+
+
+  /// ---------- ECB 模式 ----------
+  /// * ECB 模式不安全,现在服务器使用,提供该方法,但不建议广泛使用
+  /// *
+
+
+  /// ECB 模式加密 - 使用 CryptData 密钥
+  /// [plainText] 明文
+  /// [key] 密钥(CryptData,16/24/32 字节)
+  /// 返回 CryptData 格式的密文
+  static CryptData encryptECB(String plainText, CryptData key) {
+    try {
+      // 验证密钥长度
+      if (![16, 24, 32].contains(key.bytes.length)) {
+        throw ArgumentError('key must be 16 or 24 or 32 bytes');
+      }
+
+      // 创建加密器(ECB 模式不需要 IV)
+      final encrypter = encrypt.Encrypter(
+        encrypt.AES(
+          encrypt.Key(key.bytes),
+          mode: encrypt.AESMode.ecb,
+          padding: 'PKCS7',
+        ),
+      );
+
+      // 执行加密并返回 CryptData 格式的密文
+      final encrypted = encrypter.encrypt(plainText);
+      return CryptData(encrypted.bytes);
+    } catch (e) {
+      throw Exception('AES ECB encrypt failed: $e');
+    }
+  }
+
+  /// ECB 模式解密 - 使用 CryptData 密钥
+  /// [encrypted] CryptData 格式的密文
+  /// [key] 密钥(CryptData,16/24/32 字节)
+  /// 返回解密后的明文字符串
+  static String decryptECB(CryptData encrypted, CryptData key) {
+    try {
+      // 验证密钥长度
+      if (![16, 24, 32].contains(key.bytes.length)) {
+        throw ArgumentError('密钥长度必须是 16、24 或 32 字节(当前 ${key.bytes.length} 字节)');
+      }
+
+      // 创建解密器(ECB 模式不需要 IV)
+      final encrypter = encrypt.Encrypter(
+        encrypt.AES(
+          encrypt.Key(key.bytes),
+          mode: encrypt.AESMode.ecb,
+          padding: 'PKCS7',
+        ),
+      );
+
+      // 执行解密
+      final decrypted = encrypter.decryptBytes(encrypt.Encrypted(encrypted.bytes));
+      return utf8.decode(decrypted);
+    } catch (e) {
+      throw Exception('AES ECB 解密失败: $e');
+    }
+  }
 
-  return decrypted;
 }
+
+
+
+// 废弃
+// CryptCBCData encryptAesCbc(String plainText, CryptData password) {
+//   final iv = encrypt.IV.fromLength(16); // AES-CBC 必需的16字节IV
+//   final key = encrypt.Key(password.byte);
+//   final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
+//
+//   final encrypted = encrypter.encrypt(plainText, iv: iv);
+//
+//   return CryptCBCData(iv: CryptData(iv.bytes), encrypted: CryptData(encrypted.bytes));
+// }
+//
+//
+// String decryptAesCbc(CryptCBCData encrypted, CryptData password) {
+//   final iv = encrypt.IV(encrypted.iv.byte);
+//   final key = encrypt.Key(password.byte);
+//   final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
+//
+//   final decrypted = encrypter.decrypt(encrypt.Encrypted(encrypted.encrypted.byte), iv: iv);
+//
+//   return decrypted;
+// }
+
+

+ 22 - 0
lib/mathUtils/cryptData.dart

@@ -0,0 +1,22 @@
+
+import 'dart:convert';
+import 'dart:typed_data';
+import 'package:encrypt/encrypt.dart' as encrypt;
+import '../utils/hexUtils.dart';
+
+
+/// ------------ CryptData ------------
+/// * 一个存放 Uint8List 数组的类
+
+class CryptData {
+  final Uint8List _bytes;
+
+  CryptData(Uint8List byte) : _bytes = byte;
+  CryptData.fromUtf8(String base64data) : _bytes = base64Decode(base64data);
+  CryptData.fromBase64(String base64data) : _bytes = base64Decode(base64data);
+  CryptData.fromHex(String hexData) : _bytes = hexToBytes(hexData);
+
+  Uint8List get bytes => _bytes;
+  String get base64 => base64Encode(_bytes);
+  String get hex => bytesToHex(_bytes);
+}

+ 14 - 1
lib/mathUtils/keyUtils.dart

@@ -1,6 +1,6 @@
 
 import 'dart:convert';
-
+import 'dart:typed_data';
 import 'dart:math';
 import 'package:pointycastle/pointycastle.dart';
 import 'package:pointycastle/key_derivators/pbkdf2.dart';
@@ -92,6 +92,19 @@ String passwordWithPBKDF2({
 }
 
 
+Uint8List randomKeyList({int length = 32}) {
+  if (![16, 24, 32].contains(length)) {
+    throw ArgumentError('Key length must be 16, 24 or 32 byte');
+  }
+
+  final secureRandom = SecureRandom('Fortuna');
+  secureRandom.seed(KeyParameter(Uint8List.fromList(
+      List.generate(length, (_) => DateTime.now().microsecond))));
+
+  return secureRandom.nextBytes(length);
+}
+
+
 //
 // /// ---------- 使用 HMAC-SHA256 实现 PBKDF2 ----------
 // ///

+ 1 - 0
lib/mathUtils/mathUtils.dart

@@ -1,4 +1,5 @@
 
+export 'cryptData.dart';
 export 'keyUtils.dart';
 export 'aesUtils.dart';