使用Node.js中的Crypto进行加密没有得到预期结果。

3

我有一个旧的嵌入式设备,需要使用特定的密码算法、密钥和初始向量进行加密。一款现有的 .NET Framework 应用程序创建了一个 AES-128-CBC 加密的字符串,现在我正在使用 Node.js 的 Crypto 库将其重写。

但是,当我在 Node.js 中使用相同的密钥和初始向量对字符串进行加密时,输出结果与 .NET 中不同。我已经使用在线工具双重检查了加密结果,在线工具得出的结果与 .NET 相同,因此问题出在 Node.js 的 Crypto 库上。

.NET 的 C# 代码如下(这里为了演示目的,密钥和初始向量已被替换):

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 Crypto中指定零填充吗?


1
零填充是不安全的,而NodeJS 自动提供PKCS填充,可以关闭。因此,如果您必须实现它,例如像您的情况一样保持兼容性,那么您必须自己实现填充。快速搜索发现这个这个,应该能给您一个想法。如果您不想自己实现,也可以在npm上搜索。 - Attila
1个回答

3
如您已经注意到的那样,填充不匹配。在 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, '\0');
}

输出结果为:

gE1twb8LV/44oO/6UKwwCg==

这与C#代码的输出相匹配。


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


感谢您提供精确和明确的答案。这解决了我的问题。是的,我知道与零填充和IV相关的安全问题。不幸的是,我必须支持嵌入式设备固件的向后兼容性,所以我无法避免它... - Scopperloit
如何在那之后解密结果? - Lalmi Mohammed Issam
@LalmiMohammedIssam - 在评论的背景下,无法提供详细答案。但可以肯定的是,你必须使用createDecipheriv()函数。试一试吧。如果遇到困难,请在新问题中发布你的代码并描述你的问题。谢谢。 - Topaco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接