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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:54:53