C#实现与OpenSSL兼容的AES-256-CBC+PBKDF2加解密问题排查
OpenSSL AES-256-CBC+PBKDF2 与 C# 加密结果不一致的问题排查
我通过OpenSSL命令行实现了字符串的AES-256-CBC+PBKDF2加解密:
加密命令
echo "test" | openssl enc -aes-256-cbc -a -pbkdf2 -md sha256 -pass pass:"12345"
加密输出:U2FsdGVkX18s7uM/ydI4ER7zb939KShMxpvJ9aqeL2g=
解密命令
echo "U2FsdGVkX18s7uM/ydI4ER7zb939KShMxpvJ9aqeL2g=" | openssl enc -d -aes-256-cbc -a -pbkdf2 -md sha256 -pass pass:"12345"
解密输出:test
随后编写的C#加密函数无法和OpenSSL结果匹配,用OpenSSL解密时提示"bad magic number",尝试修改哈希算法无效,代码如下:
public string Encrypt(string plainText, string password) { var salt = new byte[8]; password = Hash(password); byte[] pwd = Encoding.UTF8.GetBytes(password); derivedBytes = new Rfc2898DeriveBytes(pwd, salt, 10000, HashAlgorithmName.SHA256); byte[] k = derivedBytes.GetBytes(32); byte[] iv = derivedBytes.GetBytes(16); using Aes aes = Aes.Create(); aes.KeySize = 256; aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.CBC; aes.Key = k; aes.IV = iv; // Encrypt ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); byte[] text = Encoding.ASCII.GetBytes(plainText); byte[] cipherText = encryptor.TransformFinalBlock(text, 0, text.Length); return Convert.ToBase64String(cipherText); } public static string Hash(string input) { // Use input string to calculate hash using (System.Security.Cryptography.SHA256 md = System.Security.Cryptography.SHA256.Create()) { byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input); byte[] hashBytes = md.ComputeHash(inputBytes); // Convert the byte array to hexadecimal string StringBuilder sb = new StringBuilder(); for (int i = 0; i < hashBytes.Length; i++) { sb.Append(hashBytes[i].ToString("X2")); } return sb.ToString().ToLower(); } }
代码存在的问题
- 盐值与密文格式错误:OpenSSL会生成随机8字节盐,且密文输出包含固定前缀
Salted__(8字节ASCII)+ 盐值 + 加密后的内容;你的代码使用全0固定盐,且仅输出加密后的密文,缺失头部信息,导致OpenSSL解密时无法识别格式,抛出"bad magic number"错误。 - 密码预处理逻辑错误:OpenSSL直接使用原始密码作为PBKDF2的输入,你的代码却先对密码做SHA256哈希并转成小写十六进制字符串,完全改变了PBKDF2的输入源,导致派生的Key/IV与OpenSSL完全不一致。
- 编码不一致:代码中明文使用ASCII编码,而OpenSSL默认使用UTF-8编码处理输入文本,会导致明文字节序列差异。
修正后的C#加密函数
using System; using System.Security.Cryptography; using System.Text; public class OpenSslAesHelper { public string Encrypt(string plainText, string password) { // 生成随机8字节盐,与OpenSSL逻辑对齐 byte[] salt = new byte[8]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(salt); } // 使用原始密码作为PBKDF2输入,不做额外哈希处理 byte[] passwordBytes = Encoding.UTF8.GetBytes(password); // PBKDF2参数匹配OpenSSL:迭代次数10000,哈希算法SHA256 using var pbkdf2 = new Rfc2898DeriveBytes(passwordBytes, salt, 10000, HashAlgorithmName.SHA256); byte[] key = pbkdf2.GetBytes(32); // AES-256密钥长度32字节 byte[] iv = pbkdf2.GetBytes(16); // CBC模式IV长度16字节 // AES配置匹配OpenSSL:CBC模式、PKCS7填充 using Aes aes = Aes.Create(); aes.KeySize = 256; aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.CBC; aes.Key = key; aes.IV = iv; // 加密明文,使用UTF-8编码 byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); byte[] cipherTextBytes = encryptor.TransformFinalBlock(plainTextBytes, 0, plainTextBytes.Length); // 拼接OpenSSL标准格式:魔法头 + 盐 + 密文 byte[] magicHeader = Encoding.ASCII.GetBytes("Salted__"); byte[] combinedBytes = new byte[magicHeader.Length + salt.Length + cipherTextBytes.Length]; Buffer.BlockCopy(magicHeader, 0, combinedBytes, 0, magicHeader.Length); Buffer.BlockCopy(salt, 0, combinedBytes, magicHeader.Length, salt.Length); Buffer.BlockCopy(cipherTextBytes, 0, combinedBytes, magicHeader.Length + salt.Length, cipherTextBytes.Length); // 转Base64输出,与OpenSSL一致 return Convert.ToBase64String(combinedBytes); } // 配套解密函数,用于验证 public string Decrypt(string encryptedBase64, string password) { byte[] combinedBytes = Convert.FromBase64String(encryptedBase64); byte[] magicHeader = Encoding.ASCII.GetBytes("Salted__"); // 验证密文格式 for (int i = 0; i < magicHeader.Length; i++) { if (combinedBytes[i] != magicHeader[i]) throw new ArgumentException("无效的OpenSSL密文格式"); } // 提取盐和密文内容 byte[] salt = new byte[8]; Buffer.BlockCopy(combinedBytes, magicHeader.Length, salt, 0, salt.Length); byte[] cipherTextBytes = new byte[combinedBytes.Length - magicHeader.Length - salt.Length]; Buffer.BlockCopy(combinedBytes, magicHeader.Length + salt.Length, cipherTextBytes, 0, cipherTextBytes.Length); // 派生Key和IV byte[] passwordBytes = Encoding.UTF8.GetBytes(password); using var pbkdf2 = new Rfc2898DeriveBytes(passwordBytes, salt, 10000, HashAlgorithmName.SHA256); byte[] key = pbkdf2.GetBytes(32); byte[] iv = pbkdf2.GetBytes(16); // AES解密 using Aes aes = Aes.Create(); aes.KeySize = 256; aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.CBC; aes.Key = key; aes.IV = iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); byte[] plainTextBytes = decryptor.TransformFinalBlock(cipherTextBytes, 0, cipherTextBytes.Length); return Encoding.UTF8.GetString(plainTextBytes); } }
验证说明
修正后的函数生成的Base64密文可直接用你提供的OpenSSL解密命令解密,也可通过自带的Decrypt函数还原原始明文。
内容的提问来源于stack exchange,提问作者veigatronic
相关产品推荐
相关产品推荐

