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

PHP与.NET/C# AES-256/CBC加密解密不兼容及解密后出现额外空白字符的问题求助

解决PHP openssl_encrypt与.NET AES-256/CBC解密的兼容性问题(末尾空白方块)

你遇到的末尾方块空白字符,本质是加密解密两端的填充方式不匹配导致的——你的.NET代码用的是PaddingMode.Zeros(零填充),但PHP的openssl_encrypt默认使用PKCS7标准填充,这就导致解密后残留了填充的零字节,显示为你看到的空白方块。

下面给你两种解决方案,优先推荐第一种,因为它更符合加密标准,安全性和兼容性更好:

方案一:修改.NET端使用PKCS7填充(推荐)

PKCS7是AES加密的标准填充方式,PHP的openssl_encrypt默认就用这个,所以只需要把.NET代码里的填充模式改成PaddingMode.PKCS7,两边对齐后,解密时会自动识别并去除填充字节,不会残留空白。

修改后的GetRijndaelManaged方法代码:

public static RijndaelManaged GetRijndaelManaged(String VendorKey, String Token) { 
    var keyBytes = new byte[32]; 
    var ivBytes = new byte[16]; 
    var secretKeyBytes = Encoding.UTF8.GetBytes(VendorKey + Token); 
    Array.Copy(secretKeyBytes, keyBytes, Math.Min(keyBytes.Length, secretKeyBytes.Length)); 
    var ivKeyBytes = Encoding.UTF8.GetBytes(VendorKey); 
    Array.Copy(ivKeyBytes, ivBytes, Math.Min(ivBytes.Length, ivKeyBytes.Length)); 
    return new RijndaelManaged { 
        Mode = CipherMode.CBC, 
        Padding = PaddingMode.PKCS7, // 把这里的Zeros改成PKCS7
        KeySize = 256, 
        BlockSize = 128, 
        Key = keyBytes, 
        IV = ivBytes 
    }; 
}

对应的PHP加密代码(保持默认PKCS7填充即可,无需额外处理):

function aes_encrypt($plaintext, $vendorKey, $token) {
    // 生成密钥:VendorKey+Token取前32字节(AES-256需要32字节密钥)
    $key = substr(utf8_encode($vendorKey . $token), 0, 32);
    // 生成IV:VendorKey取前16字节(CBC模式需要16字节IV)
    $iv = substr(utf8_encode($vendorKey), 0, 16);
    // openssl_encrypt默认用PKCS7填充,和修改后的.NET端匹配
    $encryptedRaw = openssl_encrypt($plaintext, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
    return base64_encode($encryptedRaw);
}

方案二:修改PHP端使用零填充(仅当无法修改.NET代码时使用)

如果因为某些原因不能改动.NET代码,那需要让PHP端也使用零填充,并且.NET端解密后手动去除末尾的零字节(因为PaddingMode.Zeros不会自动清理填充的0)。

PHP端加密代码(手动零填充):

function aes_encrypt_zero_pad($plaintext, $vendorKey, $token) {
    $key = substr(utf8_encode($vendorKey . $token), 0, 32);
    $iv = substr(utf8_encode($vendorKey), 0, 16);
    $blockSize = 16; // AES的块大小是128位=16字节
    $plaintextBytes = utf8_encode($plaintext);
    
    // 计算需要填充的字节数,凑成块大小的整数倍
    $paddingLength = $blockSize - (strlen($plaintextBytes) % $blockSize);
    if ($paddingLength !== $blockSize) {
        // 补0填充
        $plaintextBytes .= str_repeat("\0", $paddingLength);
    }
    
    // 开启OPENSSL_ZERO_PADDING选项,此时需要手动完成填充
    $encryptedRaw = openssl_encrypt($plaintextBytes, 'AES-256-CBC', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
    return base64_encode($encryptedRaw);
}

.NET端解密代码(新增去除末尾零字节的逻辑):

public static string Decrypt(String encryptedText, String VendorKey, String Token) { 
    var encryptedBytes = Convert.FromBase64String(encryptedText); 
    var decryptedBytes = Decrypt(encryptedBytes, GetRijndaelManaged(VendorKey,Token));
    // 手动去除末尾的零字节,避免残留空白方块
    decryptedBytes = TrimTrailingZeros(decryptedBytes);
    return Encoding.UTF8.GetString(decryptedBytes); 
}

// 新增工具方法:去除字节数组末尾的零字节
private static byte[] TrimTrailingZeros(byte[] bytes) {
    int lastValidIndex = bytes.Length - 1;
    // 从后往前找第一个非零字节
    while (lastValidIndex >= 0 && bytes[lastValidIndex] == 0) {
        lastValidIndex--;
    }
    // 如果全是零,返回空数组
    if (lastValidIndex < 0) {
        return Array.Empty<byte>();
    }
    // 截取有效部分
    byte[] result = new byte[lastValidIndex + 1];
    Array.Copy(bytes, result, lastValidIndex + 1);
    return result;
}

为什么优先推荐方案一?

零填充有个明显的缺陷:如果你的明文本身末尾就包含零字节,解密时会误把这些真实数据当成填充字节去除,导致数据丢失。而PKCS7填充会填充和缺失字节数相等的数值(比如缺3个字节就填充三个0x03),解密时能精准识别并去除填充,不会破坏原始数据,是更安全可靠的标准方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 20:19:09