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

.NET中AES解密无法实现PHP中openssl_decrypt的效果

解决C# AES解密与PHP逻辑不匹配的问题

我帮你排查了C#和PHP代码之间的差异,找到了导致解密失败的几个核心问题,下面一步步给你解释并提供修正后的代码:

关键问题分析

你的C#代码和PHP代码在三个核心环节上处理不一致:

  • Key的生成逻辑完全不同
    PHP里hash('sha256', $secret_key)返回的是64位小写十六进制字符串,openssl_decrypt会把这个字符串当作明文key,用UTF-8编码成64字节的数组,再通过OpenSSL内置的EVP_BytesToKey算法自动转换成AES-256需要的32字节key。而你的C#代码直接用了SHA256哈希后的原始32字节数组作为key,这和PHP生成的key完全不是一回事,这是最主要的问题。

  • Padding模式不匹配
    PHP的openssl_decrypt默认使用PKCS7填充(当options为0时),但你在C#里设置了PaddingMode.None,这会导致解密时无法正确识别加密数据末尾的填充字节,要么抛出"Input data is not a complete block"错误,要么得到乱码。

  • 编码细节的潜在问题
    你用Encoding.ASCII转换IV字符串,虽然十六进制字符的ASCII和UTF-8编码结果一致,但PHP默认用UTF-8,统一用UTF-8能避免其他场景下的编码问题。

修正后的C#代码实现

首先,生成和PHP一致的SHA256十六进制字符串:

public static string GenerateSha256HexString(string input)
{
    using (var sha256 = SHA256.Create())
    {
        byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
        var builder = new StringBuilder();
        foreach (byte b in hashBytes)
        {
            builder.Append(b.ToString("x2")); // 生成小写十六进制,和PHP的hash()输出完全一致
        }
        return builder.ToString();
    }
}

然后,模拟OpenSSL的EVP_BytesToKey算法生成正确的key(PHP会自动执行这一步):

private static byte[] DeriveKeyUsingEvpBytesToKey(byte[] passwordBytes, int keyLength)
{
    var key = new List<byte>();
    byte[] currentHash = Array.Empty<byte>();
    
    using (var md5 = MD5.Create())
    {
        while (key.Count < keyLength)
        {
            // 拼接当前哈希和密码字节,生成新的哈希
            var input = new byte[currentHash.Length + passwordBytes.Length];
            Buffer.BlockCopy(currentHash, 0, input, 0, currentHash.Length);
            Buffer.BlockCopy(passwordBytes, 0, input, currentHash.Length, passwordBytes.Length);
            
            currentHash = md5.ComputeHash(input);
            key.AddRange(currentHash);
        }
    }
    
    return key.Take(keyLength).ToArray();
}

接下来,修正AES解密方法,匹配PHP的填充和编码逻辑:

public static string AesDecrypt(byte[] encryptedData, string keyString, string ivString)
{
    using (var aes = Aes.Create())
    {
        aes.Mode = CipherMode.CBC;
        aes.Padding = PaddingMode.PKCS7; // 和PHP默认填充一致
        aes.KeySize = 256;

        // 把key字符串转成UTF-8字节,再用EVP_BytesToKey生成32字节的AES key
        byte[] keyBytes = Encoding.UTF8.GetBytes(keyString);
        aes.Key = DeriveKeyUsingEvpBytesToKey(keyBytes, aes.KeySize / 8);

        // IV直接用UTF-8编码转换
        aes.IV = Encoding.UTF8.GetBytes(ivString);

        using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
        using (var memoryStream = new MemoryStream(encryptedData))
        using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
        using (var streamReader = new StreamReader(cryptoStream, Encoding.UTF8))
        {
            return streamReader.ReadToEnd();
        }
    }
}

最后,调整调用代码,完全对齐PHP的逻辑:

var secretKey = "t-3zafRa";
var secretIv = "St@cE4eZ";
var encryptedTxt = "RmVpUjU4VjJlcXp2dkN5UlhUODQzUT09";

// 生成和PHP一致的key字符串
var keyString = GenerateSha256HexString(secretKey);
// 生成IV:SHA256十六进制字符串取前16位
var ivString = GenerateSha256HexString(secretIv).Substring(0, 16);

// 解码Base64加密数据
var encryptedData = Convert.FromBase64String(encryptedTxt);

// 解密并输出结果
var decryptedText = AesDecrypt(encryptedData, keyString, ivString);
Console.WriteLine(decryptedText);

为什么要模拟EVP_BytesToKey?

当你给PHP的openssl_decrypt传入的key长度不符合AES要求时(比如AES-256需要32字节,但我们传入的是64字节的UTF-8数组),OpenSSL会自动用EVP_BytesToKey算法生成符合要求的key。这个算法默认用MD5哈希、无salt、迭代1次,所以我们必须在C#里模拟这个逻辑,才能得到和PHP完全一致的key。

现在运行这段代码,应该就能得到和PHP解密一样的结果了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:17:04