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

能否在C#/.NET 6中解密openssl enc -k加密的AES密码保护文件?

OpenSSL AES-256-CBC 命令对应的C#原生解密实现

核心原理

要复现openssl enc的解密逻辑,需要注意三个核心规则:

  • 加密生成的文件前16字节为固定头:前8字节是ASCII字符串Salted__,后8字节是本次加密生成的随机盐
  • 密钥和IV通过OpenSSL自定义的EVP_BytesToKey算法生成,默认使用MD5哈希、迭代次数为1,不使用Rfc2898DeriveBytes对应的PBKDF2标准
  • AES参数为256位密钥、CBC模式、PKCS7填充,和通用AES实现一致

完整可运行代码

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

public static class OpenSslAesDecryptor
{
    /// <summary>
    /// 解密OpenSSL enc -aes-256-cbc -k <密码> 加密的文件
    /// </summary>
    /// <param name="inputFilePath">加密文件路径</param>
    /// <param name="outputFilePath">解密后输出路径</param>
    /// <param name="password">加密密码</param>
    /// <param name="hashAlgorithmName">OpenSSL加密时的哈希算法,1.1.0之前版本默认MD5,1.1.0及之后默认SHA256</param>
    public static void DecryptFile(string inputFilePath, string outputFilePath, string password, HashAlgorithmName? hashAlgorithmName = null)
    {
        hashAlgorithmName ??= HashAlgorithmName.MD5; // 适配旧版本openssl默认值,新版本默认加密可改为HashAlgorithmName.SHA256
        const int saltLength = 8;
        const int keyLength = 32; // AES256对应32字节密钥
        const int ivLength = 16; // CBC模式对应16字节IV

        // 读取加密文件内容
        var encryptedData = File.ReadAllBytes(inputFilePath);

        // 校验文件头是否为Salted__
        var header = Encoding.ASCII.GetString(encryptedData, 0, 8);
        if (header != "Salted__")
        {
            throw new InvalidDataException("文件不是标准OpenSSL enc带盐加密的格式");
        }

        // 提取盐值
        var salt = new byte[saltLength];
        Array.Copy(encryptedData, 8, salt, 0, saltLength);

        // 提取实际密文
        var cipherText = new byte[encryptedData.Length - 16];
        Array.Copy(encryptedData, 16, cipherText, 0, cipherText.Length);

        // 调用EVP_BytesToKey生成密钥和IV
        var (key, iv) = EVP_BytesToKey(password, salt, hashAlgorithmName.Value, 1, keyLength, ivLength);

        // 执行AES解密
        using var aes = Aes.Create();
        aes.Key = key;
        aes.IV = iv;
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7;

        using var decryptor = aes.CreateDecryptor();
        using var ms = new MemoryStream();
        using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write);
        cs.Write(cipherText, 0, cipherText.Length);
        cs.FlushFinalBlock();

        // 写入解密后的文件
        File.WriteAllBytes(outputFilePath, ms.ToArray());
    }

    /// <summary>
    /// 复现OpenSSL的EVP_BytesToKey算法
    /// </summary>
    private static (byte[] key, byte[] iv) EVP_BytesToKey(string password, byte[] salt, HashAlgorithmName hashAlgorithm, int iterations, int keyLength, int ivLength)
    {
        var passwordBytes = Encoding.UTF8.GetBytes(password);
        var totalLength = keyLength + ivLength;
        var result = new byte[totalLength];
        var currentLength = 0;
        var previousHash = Array.Empty<byte>();

        using var hash = HashAlgorithm.Create(hashAlgorithm.Name!) ?? throw new CryptographicException($"不支持的哈希算法:{hashAlgorithm.Name}");

        while (currentLength < totalLength)
        {
            // 哈希输入 = 上一次哈希结果 + 密码 + 盐
            var hashInput = new byte[previousHash.Length + passwordBytes.Length + salt.Length];
            Buffer.BlockCopy(previousHash, 0, hashInput, 0, previousHash.Length);
            Buffer.BlockCopy(passwordBytes, 0, hashInput, previousHash.Length, passwordBytes.Length);
            Buffer.BlockCopy(salt, 0, hashInput, previousHash.Length + passwordBytes.Length, salt.Length);

            // 迭代计算哈希
            var currentHash = hash.ComputeHash(hashInput);
            for (int i = 1; i < iterations; i++)
            {
                currentHash = hash.ComputeHash(currentHash);
            }

            // 复制到结果数组
            var copyLength = Math.Min(currentHash.Length, totalLength - currentLength);
            Buffer.BlockCopy(currentHash, 0, result, currentLength, copyLength);
            currentLength += copyLength;
            previousHash = currentHash;
        }

        // 拆分密钥和IV
        var key = new byte[keyLength];
        var iv = new byte[ivLength];
        Buffer.BlockCopy(result, 0, key, 0, keyLength);
        Buffer.BlockCopy(result, keyLength, iv, 0, ivLength);
        return (key, iv);
    }

    // 测试调用示例
    public static void Main()
    {
        DecryptFile(
            inputFilePath: @"C:\test\encrypted.bin",
            outputFilePath: @"C:\test\decrypted.txt",
            password: "你的加密密码"
            // 如果是openssl 1.1.0及以上版本加密且没指定-md参数,取消注释下面这行
            // hashAlgorithmName: HashAlgorithmName.SHA256
        );
    }
}

常见问题说明

  • 出现padding invalid错误90%以上是两个原因:一是用了PBKDF2生成密钥而不是EVP_BytesToKey,二是哈希算法和加密时的openssl版本默认值不匹配,只要对应修改hashAlgorithmName参数即可
  • 如果加密时添加了-nosalt参数,需要去掉盐读取逻辑,直接用空盐计算密钥和IV即可
  • 本实现仅针对openssl enc命令的默认加密逻辑,如果你加密时指定了-md、-iv、-K等自定义参数,需要对应调整密钥、IV生成逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 09:15:01