【问题标题】:Porting C# AES 256 decryption to PHP将 C# AES 256 解密移植到 PHP
【发布时间】:2017-09-22 10:12:24
【问题描述】:

我正在尝试将此 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 个字符长的字符串,base 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 个字符长的乱码——我猜是二进制数据。

谁能帮我修复该代码,或者知道我可以尝试什么?

【问题讨论】:

  • 基本上代码是不安全的。应使用具有密钥派生功能(例如 PBKDF2)的密码创建密钥。每次加密的 IV 应该是随机的,而不是密码/密钥。 IV 可以并且通常预先添加到加密数据中,它们不需要保密。
  • @zaph 我知道 - 但我无法更改 C# 代码,只需将其移植到 PHP :-)
  • 啊,所以你不关心安全,我错误地认为安全是目标。
  • @zaph 我是。但在这种情况下,我无能为力。这是我正在使用的服务 - 由其他人创建。
  • 你选择消费它。您选择帮助使事情更安全还是更不安全,这就是问题所在。我们确实需要朝着更安全的方向发展。事后每个人都有借口,他们都归结为“不够在意”。看来你选择了“不在乎”。

标签: c# php encryption aes


【解决方案1】:

正如 zaph 在 cmets 中提到的,此代码不被认为是安全的,我建议不要在生产环境中使用它。引用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# 测试:

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

//0123456789

一些提示:

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

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

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

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

【讨论】:

  • 谢谢,谢谢,谢谢!您不知道我搜索了多长时间的解决方案。关于您的安全问题 - 我只是现有服务的消费者。因此,我无法更改或做任何事情 - 但会将其传递给服务的创建者。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-15
  • 1970-01-01
  • 1970-01-01
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多