C#加密到PHP解密

18
我正在尝试使用C#加密一些(cookie)数据,然后在PHP中解密。我选择使用Rijndael加密。我已经接近成功,但只有部分文本被解密!我开始参考这个例子:Decrypt PHP encrypted string in C# 这是我要加密的文本(JSON),已删除敏感信息:
{"DisplayName":"xxx", "Username": "yyy", "EmailAddress":"zzz"}

我登录C#应用程序,该程序使用存储的密钥和IV创建/编码Cookie,然后重定向到PHP应用程序,该应用程序应该解密/读取Cookie。当我解密Cookie时,它会像这样显示:

{"DisplayName":"xxx","F�A ;��HP=D�������4��z����ť���k�#E���R�j�5�\�t. t�D��" 

更新:我已经进一步了解,并且现在的结果如下:

string(96) "{"DisplayName":"xxx","Username":"yyy","EmailAddress"�)ق��-�J��k/VV-v� �9�B`7^" 

正如您所看到的,它开始解密,但后来变得混乱...

当解密字符串时,它输出正确(带有填充,我有一个函数用于去除填充),但如果我更改测试字符串中的一个字符,我再次得到垃圾:

B�nHL�Ek    �¿?�UΣlO����OЏ�M��NO/�f.M���Lƾ�CC�Y>F��~�qd�+

以下是我用于生成随机密钥和IV的C#代码:

更新: 目前我只是使用静态的密钥/IV,它们如下:

Key: lkirwf897+22#bbtrm8814z5qq=498j5
IV: 741952hheeyy66#cs!9hjv887mxx7@8y

RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.BlockSize = 256;
symmetricKey.KeySize = 256;
symmetricKey.Padding = PaddingMode.Zeros;
symmetricKey.Mode = CipherMode.CBC;
string key = Convert.ToBase64String(symmetricKey.Key);
string IV = Convert.ToBase64String(symmetricKey.IV);

我随后将密钥和IV保存到数据库中,以便稍后进行编码/解码。

这是完整的加密类:

public static class Encryption
    {
        public static string Encrypt(string prm_text_to_encrypt, string prm_key, string prm_iv)
        {
            var sToEncrypt = prm_text_to_encrypt;

            var rj = new RijndaelManaged()
            {
                Padding = PaddingMode.PKCS7,
                Mode = CipherMode.CBC,
                KeySize = 256,
                BlockSize = 256,
                //FeedbackSize = 256
            };

            var key = Encoding.ASCII.GetBytes(prm_key);
            var IV = Encoding.ASCII.GetBytes(prm_iv);
            //var key = Convert.FromBase64String(prm_key);
            //var IV = Convert.FromBase64String(prm_iv);

            var encryptor = rj.CreateEncryptor(key, IV);

            var msEncrypt = new MemoryStream();
            var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);

            var toEncrypt = Encoding.ASCII.GetBytes(sToEncrypt);

            csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
            csEncrypt.FlushFinalBlock();

            var encrypted = msEncrypt.ToArray();

            return (Convert.ToBase64String(encrypted));
        }

        public static string Decrypt(string prm_text_to_decrypt, string prm_key, string prm_iv)
        {

            var sEncryptedString = prm_text_to_decrypt;

            var rj = new RijndaelManaged()
            {
                Padding = PaddingMode.PKCS7,
                Mode = CipherMode.CBC,
                KeySize = 256,
                BlockSize = 256,
                //FeedbackSize = 256
            };

            var key = Encoding.ASCII.GetBytes(prm_key);
            var IV = Encoding.ASCII.GetBytes(prm_iv);
            //var key = Convert.FromBase64String(prm_key);
            //var IV = Convert.FromBase64String(prm_iv);

            var decryptor = rj.CreateDecryptor(key, IV);

            var sEncrypted = Convert.FromBase64String(sEncryptedString);

            var fromEncrypt = new byte[sEncrypted.Length];

            var msDecrypt = new MemoryStream(sEncrypted);
            var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);

            csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);

            return (Encoding.ASCII.GetString(fromEncrypt));
        }

        public static void GenerateKeyIV(out string key, out string IV)
        {
            var rj = new RijndaelManaged()
            {
                Padding = PaddingMode.PKCS7,
                Mode = CipherMode.CBC,
                KeySize = 256,
                BlockSize = 256,
                //FeedbackSize = 256
            };
            rj.GenerateKey();
            rj.GenerateIV();

            key = Convert.ToBase64String(rj.Key);
            IV = Convert.ToBase64String(rj.IV);
        }
    }

以下是我用来解密数据的PHP代码:

这里是我使用的PHP代码:

    function decryptRJ256($key,$iv,$string_to_decrypt)
{
    $string_to_decrypt = base64_decode($string_to_decrypt);
    $rtn = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $string_to_decrypt, MCRYPT_MODE_CBC, $iv);
    //$rtn = rtrim($rtn, "\0\4");
    $rtn = unpad($rtn);
    return($rtn);
}

function unpad($value)
{
    $blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
    //apply pkcs7 padding removal
    $packing = ord($value[strlen($value) - 1]);
    if($packing && $packing < $blockSize){
        for($P = strlen($value) - 1; $P >= strlen($value) - $packing; $P--){
            if(ord($value{$P}) != $packing){
                $packing = 0;
            }//end if
        }//end for
    }//end if 

    return substr($value, 0, strlen($value) - $packing); 
}

$ky = 'lkirwf897+22#bbtrm8814z5qq=498j5'; // 32 * 8 = 256 bit key
$iv = '741952hheeyy66#cs!9hjv887mxx7@8y'; // 32 * 8 = 256 bit iv

$enc = $_COOKIE["MyCookie"];

$dtext = decryptRJ256($ky, $iv, $enc);
var_dump($dtext);

我对这部分有点不确定,因为我看到的所有示例代码都直接将base64编码的字符串传递给解密器,但在我的示例中,我必须先进行base64解码,否则会出现密钥和IV长度不正确的错误。

更新:我正在使用PHP需要的格式的ASCII密钥。如果我从RijndaelManaged类生成密钥,则无法在PHP端使用它们,但我可以使用已知可在PHP端工作的密钥,并在RijndaelManaged C#端使用它们。

请告诉我是否漏掉了任何相关信息。TIA!


2
我记得如果你提供的IV解密不正确,就会出现这种情况。 - pm100
尝试在加密之前将symmetricKey.FeedbackSize设置为256。在RijndaelManaged中,默认情况下为128,但mcrypt可能会使用默认的密码块大小或其他值。 - mistika
更改反馈大小似乎没有帮助。谢谢。 - solidau
你对加密输出进行了base64编码。但是不要对解密输入进行base64解码。 - Cole Tobin
最好不要使用mcrypt,它已经被遗弃了将近十年。因此,它已被弃用,并将在PHP 7.2中从核心中移除并转移到PECL中。它不支持标准的PKCS#7(前身为PKCS#5)填充,只支持非标准的空填充,甚至不能与二进制数据一起使用。mcrypt存在许多未解决的错误,这些错误可以追溯到2003年。相反,考虑使用defuseRNCryptor,它们提供了完整的解决方案,正在得到维护并且是正确的。 - zaph
显示剩余3条评论
2个回答

20

为了后人的参考,我在这里放置了完整的解决方案。

C#加密类

public static class Encryption
{
    public static string Encrypt(string prm_text_to_encrypt, string prm_key, string prm_iv)
    {
        var sToEncrypt = prm_text_to_encrypt;

        var rj = new RijndaelManaged()
        {
            Padding = PaddingMode.PKCS7,
            Mode = CipherMode.CBC,
            KeySize = 256,
            BlockSize = 256,
        };

        var key = Convert.FromBase64String(prm_key);
        var IV = Convert.FromBase64String(prm_iv);

        var encryptor = rj.CreateEncryptor(key, IV);

        var msEncrypt = new MemoryStream();
        var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);

        var toEncrypt = Encoding.ASCII.GetBytes(sToEncrypt);

        csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
        csEncrypt.FlushFinalBlock();

        var encrypted = msEncrypt.ToArray();

        return (Convert.ToBase64String(encrypted));
        }

    public static string Decrypt(string prm_text_to_decrypt, string prm_key, string prm_iv)
    {

        var sEncryptedString = prm_text_to_decrypt;

        var rj = new RijndaelManaged()
        {
            Padding = PaddingMode.PKCS7,
            Mode = CipherMode.CBC,
            KeySize = 256,
            BlockSize = 256,
        };

        var key = Convert.FromBase64String(prm_key);
        var IV = Convert.FromBase64String(prm_iv);

        var decryptor = rj.CreateDecryptor(key, IV);

        var sEncrypted = Convert.FromBase64String(sEncryptedString);

        var fromEncrypt = new byte[sEncrypted.Length];

        var msDecrypt = new MemoryStream(sEncrypted);
        var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);

        csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);

        return (Encoding.ASCII.GetString(fromEncrypt));
        }

    public static void GenerateKeyIV(out string key, out string IV)
    {
        var rj = new RijndaelManaged()
        {
            Padding = PaddingMode.PKCS7,
            Mode = CipherMode.CBC,
            KeySize = 256,
            BlockSize = 256,
        };
        rj.GenerateKey();
        rj.GenerateIV();

        key = Convert.ToBase64String(rj.Key);
        IV = Convert.ToBase64String(rj.IV);
    }
}

PHP 解密片段

<?php
function decryptRJ256($key,$iv,$encrypted)
{
    //PHP strips "+" and replaces with " ", but we need "+" so add it back in...
    $encrypted = str_replace(' ', '+', $encrypted);

    //get all the bits
    $key = base64_decode($key);
    $iv = base64_decode($iv);
    $encrypted = base64_decode($encrypted);

    $rtn = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
    $rtn = unpad($rtn);
    return($rtn);
}

//removes PKCS7 padding
function unpad($value)
{
    $blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
    $packing = ord($value[strlen($value) - 1]);
    if($packing && $packing < $blockSize)
    {
        for($P = strlen($value) - 1; $P >= strlen($value) - $packing; $P--)
        {
            if(ord($value{$P}) != $packing)
            {
                $packing = 0;
            }
        }
    }

    return substr($value, 0, strlen($value) - $packing); 
}
?>
<pre>
<?php

$enc = $_COOKIE["MyCookie"];

$ky = ""; //INSERT THE KEY GENERATED BY THE C# CLASS HERE
$iv = ""; //INSERT THE IV GENERATED BY THE C# CLASS HERE

$json_user = json_decode(decryptRJ256($ky, $iv, $enc), true);

var_dump($json_user);

?>

我必须将C#代码更改为: var encoding = new UTF8Encoding(); var key = encoding.GetBytes(prm_key); var IV = encoding.GetBytes(prm_iv); 无法找到使用Convert.FromBase64String(prm_iv)生成适当的“prm_iv”的方法,然后我尝试了Encoding.ASCII.GetBytes,我可以生成密钥,但无法从PHP获得正确的结果。所以我遇到了另一个在相同上下文下的问题/答案,我尝试了UTF8Encoding的GetBytes,最终成功了。感谢大家。 - Eduardo Xavier
运行得很好,C# 4.0和PHP 5.4x,谢谢先生 :) - Mohammed Sufian
在这个例子中,GenerateKeyIV()函数创建并输出它们。如果我没记错的话,你几乎可以使用任何值... - solidau

5
由于字符串部分正确,但末尾有乱码,这表明加密中存在填充问题,需要精确的256字节块。我建议将填充设置为PKCS7(PaddingMode.PKCS7),而不是C#侧的Zeros,PHP可以无问题理解(因为它是默认模式)。 编辑: 哎呀,我没有注意到你在PHP中有以下内容:
$enc = $_COOKIE["MyCookie"];

这是需要注意的地方。PHP可能没有直接获取到加密数据,而是运行了一些urldecode清理。你应该打印这个变量,看看它是否与从C#代码发送的内容匹配。
编辑2:
通过添加以下内容将cookie中的空格转换为缺失的+字符:
str_replace(' ', '+', $enc);

我已经做了更改,但仍然存在问题... 我只能编码/解码特定的字符串!我有一个用于测试的JSON字符串,最终成功解密了。然后我开始测试其他几个字符串,如果我在测试字符串中改变了一个字符,它就会出错... 请稍等几分钟,检查我的代码更新... - solidau
1
对于后来阅读此内容的人,我不得不删除其中一些注释...基本上,当字符串从cookie发送到PHP时,PHP会获取该字符串并将“+”字符替换为空格“ ”。 - solidau
太好了!谢谢!现在MS生成的密钥和IV也可以使用了! - solidau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接