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
相关产品推荐
相关产品推荐

