将C#AES 256解密移植到PHP

时间:2017-09-22 10:12:25

标签: c# php encryption aes

我正在尝试将此C#代码移植到PHP:

private static string DecryptString(string content, string password)
{
    Rijndael aes;
    byte[] retVal = null;
    byte[] contentBytes;
    byte[] passwordBytes;
    byte[] ivBytes;

    try {
        //Get the content as byte[]
        contentBytes = Convert.FromBase64String(content);

        //Create the password and initial vector bytes
        passwordBytes = new byte[32];
        ivBytes = new byte[16];

        Array.Copy(Encoding.Unicode.GetBytes(password), passwordBytes, Encoding.Unicode.GetBytes(password).Length);
        Array.Copy(passwordBytes, ivBytes, 16);

        //Create the cryptograpy object
        aes = Rijndael.Create();
        aes.Key = passwordBytes;
        aes.IV = ivBytes;
        aes.Padding = PaddingMode.PKCS7;

        string mode = aes.Mode.ToString();

        //Decrypt
        retVal = aes.CreateDecryptor().TransformFinalBlock(contentBytes, 0, contentBytes.Length);
    }
    catch {}

    return Encoding.Unicode.GetString(retVal);
}

content参数是一个44个字符长的字符串,基数为64,password参数是一个6个字符长的字符串。

这是我放在一起的PHP代码:

$content = "[44 char base 64 encoded string]";
$password = "[6 char password]";

$padding = 32 - (strlen($password) % 32);
$password .= str_repeat(chr($padding), $padding);

$iv = substr($password, 0, 16);

$data = base64_decode($content);

$decrypted  = openssl_decrypt($data, 'AES-256-CBC', $password, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);

C#代码的结果是一个10个字符的长数字。 PHP的结果是大约32个字符长的乱码 - 我猜二进制数据。

任何人都可以帮我修复该代码,或者知道我可以尝试什么?

1 个答案:

答案 0 :(得分:0)

正如zaph在评论中所提到的,这段代码不被认为是安全的,我建议不要在生产环境中使用它。引用zaph的comment

  

基本上代码不安全。应使用密钥派生函数(如PBKDF2)从密码创建密钥。对于每次加密,IV应该是随机的,而不是密码/密钥。 IV可以并且通常预先加密数据,它们不需要保密。

话虽这么说,这是一个与你的C#代码相当的PHP:

function DecryptString($content, $password){
    $password = mb_convert_encoding($password, "utf-16le");
    $padding = 32 - (strlen($password) % 32);
    $password .= str_repeat("\0", $padding);
    $iv = substr($password, 0, 16);
    $decrypted = openssl_decrypt($content, "AES-256-CBC", $password, false, $iv);
    $decoded = mb_convert_encoding($decrypted, "utf-8", "utf-16le");
    return $decoded;
}

C#Unicode字符串是Little Endian UTF-16编码。为了在PHP中正确解码它们,我们必须使用mb_convert_encoding

PHP测试:

$password = "012345";
$content = "EJWgZ/26wp+Cb5utbM1aMk8XfqPQide4dzjQzzzYfj8=";
echo DecryptString($content, $password);

//0123456789

C#test:

string password = "012345";
string content = "EJWgZ/26wp+Cb5utbM1aMk8XfqPQide4dzjQzzzYfj8=";
Console.WriteLine(so.DecryptString(content, password));

//0123456789

一些提示:

PHP的openssl_decrypt默认使用PKCS填充,并且可以处理base64编码的数据。我们可以通过将options参数设置为false来充分利用这些功能。

IVs应该是随机字节。这很重要,因为使用静态IV会使您的加密容易受到攻击。您可以为C#创建RNGCryptoServiceProvider的安全随机IV,为PHP创建openssl_random_pseudo_bytes

密码应尽可能长且不可预测 - 123456不是一个好密码!虽然您可以将密码用作密钥(如果密码大小合适),但最好使用使用KDF创建的密钥。您可以将Rfc2898DeriveBytes用于C#,将hash_pbkdf2用于PHP。

如果您不检查邮件的真实性,那么您的数据可能会被更改,或者您的服务可能容易受到填充oracle攻击。您可以使用MAC来验证消息(对于C#为HMACSHA256,对于PHP为hash_hmac)或使用经过身份验证的模式,如GCM。