Node.js 中的 Crypto 加密未给出预期结果

Encryption with Crypto in Node.js does not give expected result

我有一个旧的嵌入式设备,它需要一个带有给定密码、密钥和 IV 的加密密码。用 .NET 框架编写的现有应用程序创建了一个 AES-128-CBC 加密字符串,我正在 Node.js 使用 Crypto 库重写它。

但是,当我在 Node.js 中使用相同的密钥和 IV 加密字符串时,输出与在 .NET 中不同。我用在线工具仔细检查了加密,它们产生与 .NET 相同的结果,所以 Node.js Crypto 是奇数。

.NET 的 C# 代码(密钥和 IV 已被替换为演示目的):

byte[] encrypted;
        
        using (AesManaged aes = new AesManaged())
        {
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.Zeros;
            aes.Key = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
            aes.IV = new byte[] { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 };

            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        swEncrypt.Write("admin");
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        } 
        return encrypted;

这给出了 Base64 字符串 gE1twb8LV/44oO/6UKwwCg==。 我用在线工具测试了相同的密码、密钥和 IV,它们给出了相同的输出。

我的Node.js代码:

const crypto = require('crypto');

const key = Buffer.from('00112233445566778899aabbccddeeff', 'hex');
const iv = Buffer.from('aabbccddeeff00112233445566778899', 'hex');

const plaintext = 'admin';
var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
cipher.update(Buffer.from('admin'));
var encrypted = cipher.final().toString('base64');

这给出了 Base64 字符串 4TT9E4WDH/TmlKjJdVFeHQ==

我已经尝试了所有我能想到的方法,但没有成功。 我注意到 .NET 代码指定了零填充,但我无法找到在 Node.js Crypto 中设置它的方法。我猜这可能是它不提供相同输出的原因。

有人知道如何在 Node.js 加密中指定零填充吗?

正如您已经注意到的,填充不匹配。在 C# 代码中指定零填充,在 NodeJS 代码中默认使用 PKCS#7。由于NodeJS的crypto模块不支持零填充,你必须自己实现并禁用默认填充。

此外,必须为 update()final() 调用定义输入和输出编码,并且必须连接这些部分。在以下 NodeJS 代码中,UTF-8 被指定为输入编码,Base64 被指定为输出编码:

const crypto = require('crypto');

const key = Buffer.from('00112233445566778899aabbccddeeff', 'hex');
const iv = Buffer.from('aabbccddeeff00112233445566778899', 'hex');

const plaintext = 'admin';
var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
cipher.setAutoPadding(false); // Disable default PKCS#7 padding
var paddedPlaintext = zeroPad(plaintext, 16); // Zero pad plaintext
var encrypted = cipher.update(paddedPlaintext, 'utf8', 'base64'); // Specify input-/outputencoding
encrypted += cipher.final('base64'); // Specify outputencoding and concatenate
console.log(encrypted); // gE1twb8LV/44oO/6UKwwCg==

function zeroPad(text, bs){
    var padLength = text.length;
    if (text.length % bs > 0){
      padLength += bs - text.length % bs;
    }
    return text.padEnd(padLength, '[=10=]');
}

输出:

gE1twb8LV/44oO/6UKwwCg==

与 C# 代码的输出匹配。


请注意,静态 IV 是不安全的。为每次加密使用随机生成的 IV 更安全。当然,出于测试目的,这很好。
此外,零填充是不可靠的。更好的选择是 PKCS#7 填充(这是 crypto 默认值)。