将 AES 从 Java 解密为 node.js

decryptAES from Java to node.js

我有一个Java的AES加解密算法,需要在Java脚本中实现解密。

public String encryptWithAES(String value, String key, String encoding) throws NoSuchAlgorithmException, NoSuchPaddingException, DecoderException, UnsupportedEncodingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
    byte[] keyBytes = Hex.decodeHex(key.toLowerCase().toCharArray());
    byte[] dataToSend = value.getBytes(encoding);
    Cipher c = Cipher.getInstance("AES");
    SecretKeySpec k = new SecretKeySpec(keyBytes, "AES");
    c.init(1, k);
    byte[] encryptedData = c.doFinal(dataToSend);
    return new String(Hex.encodeHex(encryptedData));
}

public String decryptAES(String encrypted, String key, String encoding) throws NoSuchAlgorithmException, NoSuchPaddingException, DecoderException, UnsupportedEncodingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
    byte[] keyBytes = Hex.decodeHex(key.toCharArray());
    byte[] encryptedData = Hex.decodeHex(encrypted.toCharArray());
    Cipher c = Cipher.getInstance("AES");
    SecretKeySpec k = new SecretKeySpec(keyBytes, "AES");
    c.init(2, k);
    byte[] dencryptedData = c.doFinal(encryptedData);
    return new String(dencryptedData, encoding);
}

我在网上找了很多例子,但是没有人给我一个合适的结果。

现在我尝试用那个密码解密:

var algos = ['aes-128-cbc', 'aes-128-cfb', 'aes-128-cfb1', 'aes-128-cfb8', 'aes-128-ctr', 'aes-128-ecb', 'aes-128-gcm', 'aes-128-ofb', 'aes-128-xts', 'aes-256-cbc', 'aes-256-cfb', 'aes-256-cfb1', 'aes-256-cfb8', 'aes-256-ctr', 'aes-256-ecb', 'aes-256-gcm', 'aes-256-ofb', 'aes-256-xts', 'aes128', 'aes256'];
//var masterData - string like 'c07abe96dde490b3aba7d2f21a43ba94960619ff110ffb53433f0ff39f4cf138e48511b1fb4030'
//const ENCRYPTION_KEY - string like '94960619ff110ffb53433f0ff39f4cf1' with 32 symbols

function decrypt(text, algorithm) {
    var decipher = crypto.createDecipher(algorithm, ENCRYPTION_KEY);

    var dec = decipher.update(text, 'hex', 'utf8');

    dec += decipher.final('utf8');
    return dec;
}

algos.map(algo => {
    try {
        const dec = decrypt(masterData, algo);

        console.log(algo, dec);
    } catch (error) {
        console.log(algo, 'error', error);
    }
});

编码后的字符串是 JSON.

这确实是不安全的代码,切勿使用。

Cipher.getInstance("AES"); 未完全指定,可能会根据默认的安全提供程序产生不同的密码。它很可能会导致 "AES/ECB/PKCS5Padding",但不一定如此。如果它发生变化,您将失去不同 JVM 之间的兼容性。
始终使用完全限定的密码字符串。

因此,您应该尝试的第一件事是将您的密码限制为使用 ECB 模式的密码。 Node.js' 加密模块自动应用 PKCS#5/PKCS#7 填充,因此您无需在这方面做任何特殊操作。

接下来是关键。您的示例密钥长度为 32 个字符。如果我们查看 Java 代码,它应该是十六进制编码的,这意味着密钥实际上只有 16 个字节(八位字节)或 128 位长。在使用它之前,您需要从 Hex 中解码密钥。

另一个问题是 crypto.createDecipher 需要密码作为参数,但您似乎有密钥。在这种情况下,您需要使用 crypto.createDecipheriv。请不要混淆密码和密钥。它们具有截然不同的属性,但好处是您通常可以从密码中导出密钥。有关更多信息,请参阅:How to securely hash passwords?

正如我之前所说,Java 使用填充。许多操作模式 需要 填充以加密任意长的明文。其中之一是欧洲央行模式。填充增加 1 到 16 个字节(16 个字节是 AES 的块大小)。这意味着您的密文必须是 32 个字符的倍数(十六进制编码)。如果不是,则密文无效。

未经测试的代码,因为示例密文无效:

var crypto = require('crypto');

var masterData = '...'; // multiple of 32 characters!
var ENCRYPTION_KEY = '94960619ff110ffb53433f0ff39f4cf1';

function decrypt(text, key) {
    var decipher = crypto.createDecipheriv('aes-128-ecb', new Buffer(key, 'hex'), '');

    var dec = decipher.update(text, 'hex', 'utf8');

    dec += decipher.final('utf8');
    return dec;
}

console.log(decrypt(masterData, ENCRYPTION_KEY));

这段代码真的很不安全:使用ECB模式,没有随机化,没有认证!您根本不应该使用此加密代码。