aesUtils.dart 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import 'dart:convert';
  2. import 'dart:typed_data';
  3. import 'package:encrypt/encrypt.dart' as encrypt;
  4. import 'package:pointycastle/export.dart';
  5. import '../utils/hexUtils.dart';
  6. import 'cryptData.dart';
  7. // AES CBC 加密结果,存储 IV 和 加密数据
  8. class CryptCBCData {
  9. final CryptData iv;
  10. final CryptData encrypted;
  11. CryptData? salt;
  12. CryptCBCData({
  13. required this.iv,
  14. required this.encrypted,
  15. this.salt,
  16. });
  17. CryptCBCData.fromBase64({
  18. required String base64iv,
  19. required String base64encrypted,
  20. String? base64salt,
  21. }) : iv = CryptData.fromBase64(base64iv),
  22. encrypted = CryptData.fromBase64(base64encrypted),
  23. salt = base64salt != null ? CryptData.fromBase64(base64salt) : null;
  24. }
  25. /// ------------------- AES 编解码工具类 -------------------
  26. ///
  27. class AESUtils {
  28. /// ---------- CBC 模式 ----------
  29. /// 默认密钥长度(字节)
  30. static const int _defaultKeyLength = 32; // AES-256
  31. /// 默认 IV 长度(字节)
  32. static const int _ivLength = 16;
  33. /// 生成随机密钥
  34. /// [keyLength] 密钥长度:16(AES-128), 24(AES-192), 32(AES-256)
  35. static CryptData generateKey({int keyLength = _defaultKeyLength}) {
  36. if (![16, 24, 32].contains(keyLength)) {
  37. throw ArgumentError('密钥长度必须是 16、24 或 32 字节');
  38. }
  39. final secureRandom = SecureRandom('Fortuna');
  40. secureRandom.seed(KeyParameter(Uint8List.fromList(
  41. List.generate(32, (_) => DateTime.now().microsecond))));
  42. return CryptData(secureRandom.nextBytes(keyLength));
  43. }
  44. /// 生成随机 IV
  45. static CryptData generateIV() {
  46. final secureRandom = SecureRandom('Fortuna');
  47. secureRandom.seed(KeyParameter(Uint8List.fromList(
  48. List.generate(32, (_) => DateTime.now().microsecond))));
  49. return CryptData(secureRandom.nextBytes(_ivLength));
  50. }
  51. /// AES 加密
  52. /// [plainText] 明文
  53. /// [key] 密钥(16/24/32 字节)
  54. /// [iv] 初始化向量(16 字节),如果为空则自动生成
  55. /// 返回包含 IV 和密文的 Map,格式为 {'iv': iv_hex, 'encrypted': encrypted_base64}
  56. static CryptCBCData encryptCBC(String plainText, CryptData key, {CryptData? iv}) {
  57. try {
  58. // 验证密钥长度
  59. if (![16, 24, 32].contains(key.bytes.length)) {
  60. throw ArgumentError('密钥长度必须是 16、24 或 32 字节');
  61. }
  62. // 如果未提供 IV,则生成新的 IV
  63. final usedIv = iv ?? generateIV();
  64. // 验证 IV 长度
  65. if (usedIv.bytes.length != _ivLength) {
  66. throw ArgumentError('IV 长度必须是 16 字节');
  67. }
  68. // 创建加密器
  69. final encrypter = encrypt.Encrypter(
  70. encrypt.AES(
  71. encrypt.Key(key.bytes),
  72. mode: encrypt.AESMode.cbc,
  73. padding: 'PKCS7',
  74. ),
  75. );
  76. // 执行加密
  77. final encrypted = encrypter.encrypt(plainText, iv: encrypt.IV(usedIv.bytes));
  78. // 返回 IV 和密文(IV用十六进制,密文用base64)
  79. return CryptCBCData(
  80. iv: CryptData(usedIv.bytes),
  81. encrypted: CryptData(encrypted.bytes),
  82. );
  83. } catch (e) {
  84. throw Exception('AES encrypt failed: $e');
  85. }
  86. }
  87. /// AES 解密
  88. /// [encryptedText] 密文(base64 格式)
  89. /// [key] 密钥(16/24/32 字节)
  90. /// [iv] 初始化向量(16 字节)
  91. /// 返回解密后的明文字符串
  92. // static String decryptCBC(String encryptedText, Uint8List key, Uint8List iv) {
  93. static String decryptCBC(CryptCBCData encrypted, CryptData key) {
  94. try {
  95. // 验证密钥长度
  96. if (![16, 24, 32].contains(key.bytes.length)) {
  97. throw ArgumentError('key must be 16 or 24 or 32 bytes');
  98. }
  99. // 验证 IV 长度
  100. if (encrypted.iv.bytes.length != _ivLength) {
  101. throw ArgumentError('IV must be 16 bytes');
  102. }
  103. // 创建解密器
  104. final encrypter = encrypt.Encrypter(
  105. encrypt.AES(
  106. encrypt.Key(key.bytes),
  107. mode: encrypt.AESMode.cbc,
  108. padding: 'PKCS7',
  109. ),
  110. );
  111. // 执行解密
  112. final decrypted = encrypter.decrypt64(
  113. encrypted.encrypted.base64,
  114. iv: encrypt.IV(encrypted.iv.bytes),
  115. );
  116. return decrypted;
  117. } catch (e) {
  118. throw Exception('AES decrypt failed: $e');
  119. }
  120. }
  121. /// 简化的加密方法 - 自动处理密钥派生
  122. /// [plainText] 明文
  123. /// [password] 密码字符串,将用于派生密钥
  124. /// [salt] 盐值(可选,十六进制格式)
  125. /// 返回包含 IV、盐和密文的 CryptCBCData
  126. static CryptCBCData encryptCBCWithPassword(
  127. String plainText,
  128. String password,
  129. {String? salt}
  130. ) {
  131. try {
  132. // 如果没有提供盐值,则生成随机盐值(十六进制格式)
  133. final usedSalt = salt ?? bytesToHex(generateIV().bytes, include0x: false);
  134. // 从密码派生密钥(使用 PBKDF2)
  135. final derivedKey = _deriveKeyFromPassword(password, usedSalt);
  136. // 执行加密
  137. final result = encryptCBC(plainText, derivedKey);
  138. // 将盐值添加到结果中
  139. result.salt = CryptData.fromHex(usedSalt);
  140. return result;
  141. } catch (e) {
  142. throw Exception('AES CBC encrypt with password failed: $e');
  143. }
  144. }
  145. /// 简化的解密方法 - 使用密码派生密钥
  146. /// [encrypted] 包含 IV、盐和密文的 CryptCBCData
  147. /// [password] 密码字符串
  148. /// 返回解密后的明文字符串
  149. static String decryptCBCWithPassword(
  150. CryptCBCData encrypted,
  151. String password,
  152. ) {
  153. try {
  154. // 验证盐值是否存在
  155. if (encrypted.salt == null) {
  156. throw ArgumentError('Salt is required for password-based decryption');
  157. }
  158. // 将盐值转换为十六进制字符串
  159. final saltHex = bytesToHex(encrypted.salt!.bytes, include0x: false);
  160. // 从密码派生密钥
  161. final derivedKey = _deriveKeyFromPassword(password, saltHex);
  162. // 执行解密
  163. return decryptCBC(encrypted, derivedKey);
  164. } catch (e) {
  165. throw Exception('AES CBC decrypt with password failed: $e');
  166. }
  167. }
  168. /// 从密码派生密钥(使用 PBKDF2)
  169. static CryptData _deriveKeyFromPassword(String password, String salt, {int keyLength = 32}) {
  170. final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
  171. final params = Pbkdf2Parameters(
  172. hexToBytes(salt),
  173. 100000, // 迭代次数
  174. keyLength,
  175. );
  176. pbkdf2.init(params);
  177. final key = Uint8List(keyLength);
  178. pbkdf2.deriveKey(Uint8List.fromList(utf8.encode(password)), 0, key, 0);
  179. return CryptData(key);
  180. }
  181. /// 验证密钥格式是否正确
  182. static bool isValidKey(Uint8List key) {
  183. return [16, 24, 32].contains(key.length);
  184. }
  185. /// 验证 IV 格式是否正确
  186. static bool isValidIV(Uint8List iv) {
  187. return iv.length == _ivLength;
  188. }
  189. /// ---------- ECB 模式 ----------
  190. /// * ECB 模式不安全,现在服务器使用,提供该方法,但不建议广泛使用
  191. /// *
  192. /// ECB 模式加密 - 使用 CryptData 密钥
  193. /// [plainText] 明文
  194. /// [key] 密钥(CryptData,16/24/32 字节)
  195. /// 返回 CryptData 格式的密文
  196. static CryptData encryptECB(String plainText, CryptData key) {
  197. try {
  198. // 验证密钥长度
  199. if (![16, 24, 32].contains(key.bytes.length)) {
  200. throw ArgumentError('key must be 16 or 24 or 32 bytes');
  201. }
  202. // 创建加密器(ECB 模式不需要 IV)
  203. final encrypter = encrypt.Encrypter(
  204. encrypt.AES(
  205. encrypt.Key(key.bytes),
  206. mode: encrypt.AESMode.ecb,
  207. padding: 'PKCS7',
  208. ),
  209. );
  210. // 执行加密并返回 CryptData 格式的密文
  211. final encrypted = encrypter.encrypt(plainText);
  212. return CryptData(encrypted.bytes);
  213. } catch (e) {
  214. throw Exception('AES ECB encrypt failed: $e');
  215. }
  216. }
  217. /// ECB 模式解密 - 使用 CryptData 密钥
  218. /// [encrypted] CryptData 格式的密文
  219. /// [key] 密钥(CryptData,16/24/32 字节)
  220. /// 返回解密后的明文字符串
  221. static String decryptECB(CryptData encrypted, CryptData key) {
  222. try {
  223. // 验证密钥长度
  224. if (![16, 24, 32].contains(key.bytes.length)) {
  225. throw ArgumentError('密钥长度必须是 16、24 或 32 字节(当前 ${key.bytes.length} 字节)');
  226. }
  227. // 创建解密器(ECB 模式不需要 IV)
  228. final encrypter = encrypt.Encrypter(
  229. encrypt.AES(
  230. encrypt.Key(key.bytes),
  231. mode: encrypt.AESMode.ecb,
  232. padding: 'PKCS7',
  233. ),
  234. );
  235. // 执行解密
  236. final decrypted = encrypter.decryptBytes(encrypt.Encrypted(encrypted.bytes));
  237. return utf8.decode(decrypted);
  238. } catch (e) {
  239. throw Exception('AES ECB 解密失败: $e');
  240. }
  241. }
  242. }
  243. // 废弃
  244. // CryptCBCData encryptAesCbc(String plainText, CryptData password) {
  245. // final iv = encrypt.IV.fromLength(16); // AES-CBC 必需的16字节IV
  246. // final key = encrypt.Key(password.byte);
  247. // final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
  248. //
  249. // final encrypted = encrypter.encrypt(plainText, iv: iv);
  250. //
  251. // return CryptCBCData(iv: CryptData(iv.bytes), encrypted: CryptData(encrypted.bytes));
  252. // }
  253. //
  254. //
  255. // String decryptAesCbc(CryptCBCData encrypted, CryptData password) {
  256. // final iv = encrypt.IV(encrypted.iv.byte);
  257. // final key = encrypt.Key(password.byte);
  258. // final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
  259. //
  260. // final decrypted = encrypter.decrypt(encrypt.Encrypted(encrypted.encrypted.byte), iv: iv);
  261. //
  262. // return decrypted;
  263. // }