在 Flutter 上解密 AES 输入,在网络上使用 cryptoJS AES
decrypt AES input on Flutter, when on web use cryptoJS AES
在网络上,我使用 CryptoJS 来解密 JS:
CryptoJS.AES.decrypt(inputBase64, key).toString(CryptoJS.enc.Utf8);
示例:
input: "tzfwnxVwE/qNoaWRRfqLp11ZyhB4UtKO+0/Lvv5B7eE="
key: "20190225165436_15230006321670000_15510884759030000"
在 flutter 中我找不到任何库可以用任何长度的密钥解密。
我知道“对于 AES,NIST 选择了 Rijndael 家族的三个成员,每个成员的块大小为 128 位,但具有三种不同的密钥长度:128、192 和 256 位。
“
但我不知道如何将任意长度的密钥转换为 128 位格式?
当您将字符串作为密钥传递给 CryptoJS 时,它会将其视为密码并使用密钥派生函数从中生成密钥 - 在本例中为 PBKDF2。它生成一个 256 位密钥和一个 128 位初始化向量 (IV)。然后它将那些用于 encryption/decryption。您还需要找出 CryptoJS 使用的链接方法(可能是密码块链接 (CBC))以及它使用的填充方法(以确保纯文本是 128 位块的整数 - 可能是 PKCS#7)。
CryptoJS 有这种 "works out of the box" 模式,但它在幕后做了什么并不是特别清楚 - 您需要阅读源代码或搜索文档。
当试图在两个 systems/languages 之间进行互操作时,最好由你来掌控事情,而不是让一端做出武断的决定。这样您就可以确保两端的设置相同。
因此,您可以选择:
- 使用 PBKDF2 从字符串中生成 128 位密钥和 128 位 IV
密码 - 例如,9999 轮
- 使用 PKCS#7 填充
- 在 CBC 模式下使用 AES
pointycastle 包在 Dart 中支持以上所有内容。看起来 CryptoJS 也支持所有这些。
从密码开始,确保您可以在 JS 和 Dart 中生成相同的密钥和 IV。然后继续创建密码。
还要记住,永远不要 使用相同的 key/IV 对加密两条消息。例如,使用消息序列号稍微更改每条消息的 IV。
使用 nodejs 脚本加密示例:
var key = CryptoJS.PBKDF2("123456", "123456", {
keySize: 256 / 32
});
var iv = CryptoJS.PBKDF2("123456", "123456", {
keySize: 128 / 32
});
var encrypted = CryptoJS.AES.encrypt('my message', key, { iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
使用 dart 进行描述
import 'package:encrypt/encrypt.dart' as aes;
import 'package:crypto/crypto.dart';
import 'package:hex/hex.dart';
import 'package:password_hash/pbkdf2.dart';
void main(List<String> arguments) {
String encrypted = 'HbsmGAigiIWmU3MNZAf8+w==';
final generator = PBKDF2(hashAlgorithm: sha1);
final key = aes.Key.fromBase16(HEX.encode(generator.generateKey("123456", "123456", 1, 32)));
final iv = aes.IV.fromBase16(HEX.encode(generator.generateKey("123456", "123456", 1, 16)));
final encrypter = aes.Encrypter(aes.AES(key, mode: aes.AESMode.cbc, padding: 'PKCS7'));
final decrypted = encrypter.decrypt64(encrypted, iv:iv);
print(decrypted);
}
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:tuple/tuple.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
String encryptAESCryptoJS(String plainText, String passphrase) {
try {
final salt = genRandomWithNonZero(8);
var keyndIV = deriveKeyAndIV(passphrase, salt);
final key = encrypt.Key(keyndIV.item1);
final iv = encrypt.IV(keyndIV.item2);
final encrypter = encrypt.Encrypter(
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
final encrypted = encrypter.encrypt(plainText, iv: iv);
Uint8List encryptedBytesWithSalt = Uint8List.fromList(
createUint8ListFromString("Salted__") + salt + encrypted.bytes);
return base64.encode(encryptedBytesWithSalt);
} catch (error) {
throw error;
}
}
String decryptAESCryptoJS(String encrypted, String passphrase) {
try {
Uint8List encryptedBytesWithSalt = base64.decode(encrypted);
Uint8List encryptedBytes =
encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length);
final salt = encryptedBytesWithSalt.sublist(8, 16);
var keyndIV = deriveKeyAndIV(passphrase, salt);
final key = encrypt.Key(keyndIV.item1);
final iv = encrypt.IV(keyndIV.item2);
final encrypter = encrypt.Encrypter(
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
final decrypted =
encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv);
return decrypted;
} catch (error) {
throw error;
}
}
Tuple2<Uint8List, Uint8List> deriveKeyAndIV(String passphrase, Uint8List salt) {
var password = createUint8ListFromString(passphrase);
Uint8List concatenatedHashes = Uint8List(0);
Uint8List currentHash = Uint8List(0);
bool enoughBytesForKey = false;
Uint8List preHash = Uint8List(0);
while (!enoughBytesForKey) {
int preHashLength = currentHash.length + password.length + salt.length;
if (currentHash.length > 0)
preHash = Uint8List.fromList(
currentHash + password + salt);
else
preHash = Uint8List.fromList(
password + salt);
currentHash = md5.convert(preHash).bytes;
concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash);
if (concatenatedHashes.length >= 48) enoughBytesForKey = true;
}
var keyBtyes = concatenatedHashes.sublist(0, 32);
var ivBtyes = concatenatedHashes.sublist(32, 48);
return new Tuple2(keyBtyes, ivBtyes);
}
Uint8List createUint8ListFromString(String s) {
var ret = new Uint8List(s.length);
for (var i = 0; i < s.length; i++) {
ret[i] = s.codeUnitAt(i);
}
return ret;
}
Uint8List genRandomWithNonZero(int seedLength) {
final random = Random.secure();
const int randomMax = 245;
final Uint8List uint8list = Uint8List(seedLength);
for (int i=0; i < seedLength; i++) {
uint8list[i] = random.nextInt(randomMax)+1;
}
return uint8list;
}
用法
import 'package:app/utils/cryptojs_aes_encryption_helper.dart';
String plainText = 'PlainText is Me';
var encrypted = encryptAESCryptoJS(plainText, "password");
var decrypted = decryptAESCryptoJS(encrypted, "password");
在网络上,我使用 CryptoJS 来解密 JS:
CryptoJS.AES.decrypt(inputBase64, key).toString(CryptoJS.enc.Utf8);
示例:
input: "tzfwnxVwE/qNoaWRRfqLp11ZyhB4UtKO+0/Lvv5B7eE=" key: "20190225165436_15230006321670000_15510884759030000"
在 flutter 中我找不到任何库可以用任何长度的密钥解密。
我知道“对于 AES,NIST 选择了 Rijndael 家族的三个成员,每个成员的块大小为 128 位,但具有三种不同的密钥长度:128、192 和 256 位。 “
但我不知道如何将任意长度的密钥转换为 128 位格式?
当您将字符串作为密钥传递给 CryptoJS 时,它会将其视为密码并使用密钥派生函数从中生成密钥 - 在本例中为 PBKDF2。它生成一个 256 位密钥和一个 128 位初始化向量 (IV)。然后它将那些用于 encryption/decryption。您还需要找出 CryptoJS 使用的链接方法(可能是密码块链接 (CBC))以及它使用的填充方法(以确保纯文本是 128 位块的整数 - 可能是 PKCS#7)。
CryptoJS 有这种 "works out of the box" 模式,但它在幕后做了什么并不是特别清楚 - 您需要阅读源代码或搜索文档。
当试图在两个 systems/languages 之间进行互操作时,最好由你来掌控事情,而不是让一端做出武断的决定。这样您就可以确保两端的设置相同。
因此,您可以选择:
- 使用 PBKDF2 从字符串中生成 128 位密钥和 128 位 IV 密码 - 例如,9999 轮
- 使用 PKCS#7 填充
- 在 CBC 模式下使用 AES
pointycastle 包在 Dart 中支持以上所有内容。看起来 CryptoJS 也支持所有这些。
从密码开始,确保您可以在 JS 和 Dart 中生成相同的密钥和 IV。然后继续创建密码。
还要记住,永远不要 使用相同的 key/IV 对加密两条消息。例如,使用消息序列号稍微更改每条消息的 IV。
使用 nodejs 脚本加密示例:
var key = CryptoJS.PBKDF2("123456", "123456", {
keySize: 256 / 32
});
var iv = CryptoJS.PBKDF2("123456", "123456", {
keySize: 128 / 32
});
var encrypted = CryptoJS.AES.encrypt('my message', key, { iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
使用 dart 进行描述
import 'package:encrypt/encrypt.dart' as aes;
import 'package:crypto/crypto.dart';
import 'package:hex/hex.dart';
import 'package:password_hash/pbkdf2.dart';
void main(List<String> arguments) {
String encrypted = 'HbsmGAigiIWmU3MNZAf8+w==';
final generator = PBKDF2(hashAlgorithm: sha1);
final key = aes.Key.fromBase16(HEX.encode(generator.generateKey("123456", "123456", 1, 32)));
final iv = aes.IV.fromBase16(HEX.encode(generator.generateKey("123456", "123456", 1, 16)));
final encrypter = aes.Encrypter(aes.AES(key, mode: aes.AESMode.cbc, padding: 'PKCS7'));
final decrypted = encrypter.decrypt64(encrypted, iv:iv);
print(decrypted);
}
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:tuple/tuple.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
String encryptAESCryptoJS(String plainText, String passphrase) {
try {
final salt = genRandomWithNonZero(8);
var keyndIV = deriveKeyAndIV(passphrase, salt);
final key = encrypt.Key(keyndIV.item1);
final iv = encrypt.IV(keyndIV.item2);
final encrypter = encrypt.Encrypter(
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
final encrypted = encrypter.encrypt(plainText, iv: iv);
Uint8List encryptedBytesWithSalt = Uint8List.fromList(
createUint8ListFromString("Salted__") + salt + encrypted.bytes);
return base64.encode(encryptedBytesWithSalt);
} catch (error) {
throw error;
}
}
String decryptAESCryptoJS(String encrypted, String passphrase) {
try {
Uint8List encryptedBytesWithSalt = base64.decode(encrypted);
Uint8List encryptedBytes =
encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length);
final salt = encryptedBytesWithSalt.sublist(8, 16);
var keyndIV = deriveKeyAndIV(passphrase, salt);
final key = encrypt.Key(keyndIV.item1);
final iv = encrypt.IV(keyndIV.item2);
final encrypter = encrypt.Encrypter(
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
final decrypted =
encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv);
return decrypted;
} catch (error) {
throw error;
}
}
Tuple2<Uint8List, Uint8List> deriveKeyAndIV(String passphrase, Uint8List salt) {
var password = createUint8ListFromString(passphrase);
Uint8List concatenatedHashes = Uint8List(0);
Uint8List currentHash = Uint8List(0);
bool enoughBytesForKey = false;
Uint8List preHash = Uint8List(0);
while (!enoughBytesForKey) {
int preHashLength = currentHash.length + password.length + salt.length;
if (currentHash.length > 0)
preHash = Uint8List.fromList(
currentHash + password + salt);
else
preHash = Uint8List.fromList(
password + salt);
currentHash = md5.convert(preHash).bytes;
concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash);
if (concatenatedHashes.length >= 48) enoughBytesForKey = true;
}
var keyBtyes = concatenatedHashes.sublist(0, 32);
var ivBtyes = concatenatedHashes.sublist(32, 48);
return new Tuple2(keyBtyes, ivBtyes);
}
Uint8List createUint8ListFromString(String s) {
var ret = new Uint8List(s.length);
for (var i = 0; i < s.length; i++) {
ret[i] = s.codeUnitAt(i);
}
return ret;
}
Uint8List genRandomWithNonZero(int seedLength) {
final random = Random.secure();
const int randomMax = 245;
final Uint8List uint8list = Uint8List(seedLength);
for (int i=0; i < seedLength; i++) {
uint8list[i] = random.nextInt(randomMax)+1;
}
return uint8list;
}
用法
import 'package:app/utils/cryptojs_aes_encryption_helper.dart';
String plainText = 'PlainText is Me';
var encrypted = encryptAESCryptoJS(plainText, "password");
var decrypted = decryptAESCryptoJS(encrypted, "password");