在 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");