向 C# 发出匹配 PHP 加密

Issue matching PHP encryption to C#

我有一个实现加密的 C# 应用程序。我正在努力在 PHP 中添加一些功能以使用此应用程序。我在应用程序中使用 Rijndael 托管加密。我能够解密 PHP 中的字符串没问题,但是当我尝试加密字符串时,它与在 C# 应用程序中创建的字符串不匹配。我搜索了 Whosebug 并尝试了一堆组合,但似乎无法正常工作。

我加密字符串的C#代码如下:

    public static string Encrypt(string plainText, byte[] key, byte[] iv)
    {
        RijndaelManaged crypto = new RijndaelManaged();
        crypto.Key = key;
        crypto.IV = iv;
        byte[] textBytes = System.Text.Encoding.Unicode.GetBytes(plainText);        
        ICryptoTransform Encryptor = crypto.CreateEncryptor(crypto.Key, crypto.IV);
        MemoryStream mem_stream = new MemoryStream();
        CryptoStream cryptoStream = new CryptoStream(mem_stream, Encryptor, CryptoStreamMode.Write);
        cryptoStream.Write(textBytes, 0, textBytes.Length);
        cryptoStream.FlushFinalBlock();
        byte[] CipherBytes = mem_stream.ToArray();
        mem_stream.Close();
        cryptoStream.Close();
        string Encrypt_Data = Convert.ToBase64String(CipherBytes);
        return Encrypt_Data;
    }

在PHP中我使用了下面的代码:

class Encryption {
    protected $cipher = MCRYPT_RIJNDAEL_128;
    protected $mode = MCRYPT_MODE_CBC;

    public function getKey()
    {
        return implode(array_map('chr', .....);
    }

    function addPadding($string)
    {
        $blocksize = mcrypt_get_block_size($this->cipher, $this->mode);
        $padding = $blocksize - (strlen($string) % $blocksize);
        $string .= str_repeat(chr($padding), $padding);
        return $string;
    }

    function Encrypt($string, $iv)
    {
        $value = rtrim(base64_encode(mcrypt_encrypt($this->cipher, $this->getKey(), $this->addPadding($string), $this->mode, $iv)));
        return $value;
    }

    function Decrypt($string, $iv)
    {
        return rtrim(mcrypt_decrypt($this->cipher, $this->getKey(), base64_decode($string), $this->mode, $iv));
    }

}

两者使用相同的私钥(一个 32 字节长的字节数组)。我使用现有示例对其进行测试,如下所示:

$iv = hex2bin('B773705230CFADC864401FF2EB1FCF14');
$first = 'Jx7Khz4v+AQE3lUkIQF/SA==';

$enc = new Encryption;
$decrypted = $enc->Decrypt($first, $iv);
print_r(unpack('C*', $decrypted));
print_r(unpack('C*', 'Jackson'));
$encrypted = $enc->Encrypt('Jackson', $iv);

echo 'C# Encrypted: '.$first."\n";
echo 'Decrypted: '.$decrypted."\n";
echo 'PHP Encrypted: '.$encrypted;

这就是有趣的地方。我很好地得到了解密的字符串(它打印出 Jackson),但是它的字节数组非常奇怪。似乎初始字符串的填充方式可能是导致问题的原因。

Array
(
    [1] => 74
    [2] => 0
    [3] => 97
    [4] => 0
    [5] => 99
    [6] => 0
    [7] => 107
    [8] => 0
    [9] => 115
    [10] => 0
    [11] => 111
    [12] => 0
    [13] => 110
    [14] => 0
    [15] => 2
    [16] => 2
)
Array
(
    [1] => 74
    [2] => 97
    [3] => 99
    [4] => 107
    [5] => 115
    [6] => 111
    [7] => 110
)
C# Encrypted: Jx7Khz4v+AQE3lUkIQF/SA==
Decrypted: Jackson
PHP Encrypted: /qIq5gSWFPbLpGJMkGg+Zw==

这显然与填充有关。我只是不确定为什么每隔一个字节似乎都有 0。我显然在某处遗漏了一些东西。我已经在 c# 中设置了加密,数据已经加密,所以更改 C# 代码几乎是不可能的,除非我在应用程序中有一个辅助函数在存储到数据库之前调整值....

非常感谢您的帮助。

编辑:更新解决方案

正如 Kevin 所指出的,区别在于 C# 应用程序中的 UTF-16 和 PHP 中的 UTF-8。为了解决这个问题,我只需要对 Encrypt 函数做一个小改动,如下所示,将字符串转换为正确的编码:

    function Encrypt($string, $iv)
    {
        $string = iconv('UTF-8', 'UTF-16LE', $string);
        $value = rtrim(base64_encode(mcrypt_encrypt($this->cipher, $this->getKey(), $this->addPadding($string), $this->mode, $iv)));
        return $value;
    }

数组中出现 0 的原因是您使用 Unicode 编码(每个字符 16 位)将源字符串转换为数组。如果您使用 UTF8 或 ASCII(每个字符 8 位),0 将不再出现在数组中。您只需要确保双方都使用相同的编码(在发送方创建数组并在接收方对字符串进行编码)。然后,只要您的密钥和 IV 在两侧都匹配,您就可以开始了。