You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js crypto AES-256-CTR加密流的C#解密方案问询

用C#解密Node.js crypto.createCipher('aes-256-ctr')加密的流

要搞定这个问题,得先摸透Node.js crypto.createCipher()的底层逻辑——它不是直接把你传的密码当密钥用,而是通过OpenSSL的EVP_BytesToKey算法,从密码和随机盐派生密钥和IV,而且盐会被放在加密数据最开头。下面是具体的实现步骤和代码:

核心细节拆解

Node.js的createCipher('aes-256-ctr', 'my password')实际做了这些事:

  • 生成一个8字节的随机盐
  • 在加密数据开头写入固定标识Salted__(8字节)+ 生成的盐(8字节)
  • 用EVP_BytesToKey算法(默认MD5哈希、迭代1次),从密码和盐生成32字节的AES-256密钥,以及16字节的IV(AES块大小是16字节,CTR模式要求IV长度和块大小一致)
  • 用AES-256-CTR模式加密原始数据,拼在盐的后面

C#解密完整实现

下面是流式解密的代码,包含EVP_BytesToKey算法实现和CTR模式解密逻辑:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public class NodeAesCtrDecryptor
{
    public static void DecryptStream(Stream encryptedStream, Stream outputStream, string password)
    {
        // 1. 读取并验证盐的标识头
        var saltHeader = new byte[8];
        encryptedStream.Read(saltHeader, 0, saltHeader.Length);
        if (!Encoding.ASCII.GetString(saltHeader).Equals("Salted__"))
        {
            throw new InvalidDataException("加密数据格式错误,缺少Salted__标识");
        }

        // 读取8字节盐
        var salt = new byte[8];
        encryptedStream.Read(salt, 0, salt.Length);

        // 2. 用EVP_BytesToKey派生密钥和IV
        var (key, iv) = DeriveKeyAndIv(password, salt);

        // 3. 初始化AES-CTR解密器
        using var aes = Aes.Create();
        aes.Key = key;
        aes.IV = iv;
        aes.Mode = CipherMode.CTR;
        aes.Padding = PaddingMode.None; // CTR是流模式,不需要填充

        using var decryptor = aes.CreateDecryptor();
        // 4. 流式解密并写入输出
        using var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);
        cryptoStream.CopyTo(outputStream);
    }

    // 实现OpenSSL的EVP_BytesToKey算法(MD5哈希、迭代1次)
    private static (byte[] Key, byte[] Iv) DeriveKeyAndIv(string password, byte[] salt)
    {
        var passwordBytes = Encoding.UTF8.GetBytes(password);
        var combined = new byte[passwordBytes.Length + salt.Length];
        Buffer.BlockCopy(passwordBytes, 0, combined, 0, passwordBytes.Length);
        Buffer.BlockCopy(salt, 0, combined, passwordBytes.Length, salt.Length);

        using var md5 = MD5.Create();
        var hash = md5.ComputeHash(combined);
        var key = new byte[32]; // AES-256需要32字节密钥
        var iv = new byte[16];  // CTR模式IV长度等于AES块大小

        // 第一次哈希得到16字节,不够则继续哈希前一次结果+密码+盐
        Buffer.BlockCopy(hash, 0, key, 0, hash.Length);
        if (key.Length > hash.Length)
        {
            var temp = new byte[hash.Length + combined.Length];
            Buffer.BlockCopy(hash, 0, temp, 0, hash.Length);
            Buffer.BlockCopy(combined, 0, temp, hash.Length, combined.Length);
            var hash2 = md5.ComputeHash(temp);
            Buffer.BlockCopy(hash2, 0, key, hash.Length, hash2.Length);
        }

        // 生成IV:哈希密钥+密码+盐的组合
        var tempIv = new byte[key.Length + combined.Length];
        Buffer.BlockCopy(key, 0, tempIv, 0, key.Length);
        Buffer.BlockCopy(combined, 0, tempIv, key.Length, combined.Length);
        var hashIv = md5.ComputeHash(tempIv);
        Buffer.BlockCopy(hashIv, 0, iv, 0, iv.Length);

        return (key, iv);
    }
}

使用示例

比如解密一个加密文件并写入新文件:

using (var encryptedFile = File.OpenRead("encrypted.dat"))
using (var decryptedFile = File.OpenWrite("decrypted.dat"))
{
    NodeAesCtrDecryptor.DecryptStream(encryptedFile, decryptedFile, "my password");
}

注意事项

  • 确保密码编码一致:这里用UTF-8,和Node.js默认编码匹配
  • CTR模式不需要填充,必须设置PaddingMode.None,否则会解密失败
  • EVP_BytesToKey是较旧的算法,建议后续在Node.js端改用crypto.createCipheriv()手动指定密钥和IV,这样更安全也更便于跨语言兼容

内容的提问来源于stack exchange,提问作者Hung Le

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:52:46