如何将C#的Rijndael加密转换为PHP?

3

SO上已经有一些有用的问题:

然而,我仍然对我的特定情况感到困难。

我尝试了各种方法,但最终出现了错误"The IV parameter must be as long as the blocksize"或文本与结果哈希不匹配。

我不理解加密的足够多以弄清楚我做错了什么。

这是php版本:

$pass = 'hello';
$salt = 'application-salt';

echo Encrypt('hello', 'application-salt');

function Encrypt($pass, $salt)
{
    $derived = PBKDF1($pass, $salt, 100, 16);
    $key = bin2hex(substr($derived, 0, 8));
    $iv = bin2hex(substr($derived, 8, 8));
    return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $pass, MCRYPT_MODE_CBC, $iv);
}

function PBKDF1($pass, $salt, $count, $dklen)
{
    $t = $pass.$salt;
    $t = sha1($t, true);
    for($i=2; $i <= $count; $i++)
    {
        $t = sha1($t, true);
    }
    $t = substr($t,0,$dklen-1);
    return $t;
}

以下是 C# 版本:

Console.WriteLine(Encrypt("hello", "application-salt"));
// output: "Hk4he+qKGsO5BcL2HDtbkA=="

public static string Encrypt(string clearText, string Password)
{
    byte[] clearData = System.Text.Encoding.Unicode.GetBytes(clearText);
    PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password,
        new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });

    MemoryStream ms = new MemoryStream();
    Rijndael alg = Rijndael.Create();
    alg.Key = pdb.GetBytes(32);
    alg.IV = pdb.GetBytes(16);
    CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
    cs.Write(clearData, 0, clearData.Length);
    cs.Close();
    byte[] encryptedData = ms.ToArray();

    return Convert.ToBase64String(encryptedData);
}

我希望能够在一个新的基于php的应用程序中验证用户登录,并与现有的C#应用程序通信到同一MySQL数据库。我打算加密密码并将结果哈希与存储在数据库中的哈希进行比较以进行身份验证。
非常感谢任何指导。
编辑:
我意识到在C#函数中,PasswordDeriveBytes被调用并传递了一个字节数组作为参数,而在PHP版本中我没有类似的东西。我发现这来自Codeproject示例,而ASCII中的字节数组拼写为“Ivan Medvedev”,我认为他是示例作者。不幸的是我不能改变这个。

你能把这个 .net 加密转换成 PHP 吗? - zod
3个回答

3
以下片段可能有助于寻找从C#到PHP的精确转换的人们。
<?php
    class Foo {
      protected $mcrypt_cipher = MCRYPT_RIJNDAEL_128;
      protected $mcrypt_mode = MCRYPT_MODE_CBC;

      public function decrypt($key, $iv, $encrypted)
      {
        return mcrypt_decrypt($this->mcrypt_cipher, $key, base64_decode($encrypted), $this->mcrypt_mode, $iv);
      }

      public function encrypt($key, $iv, $password)
      {
        $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
          $padding = $block - (strlen($password) % $block);
          $password .= str_repeat(chr($padding), $padding);
        return mcrypt_encrypt($this->mcrypt_cipher, $key, $password, $this->mcrypt_mode, $iv);
      }
    }
    $foo = new Foo;
    $pass = 'p@ss';
    $salt = 's@1t';

    $key = PBKDF1($pass, $salt, 2, 32);

    $iv = "@1B2c3D4e5F6g7H8";

    $encrypted = $foo->encrypt($key,$iv,'test@123');

    $encrypted = base64_encode($encrypted);

    echo 'Encrypted: '.$encrypted.'</br>';
    echo 'Decrypted: '.$foo->decrypt($key, $iv, $encrypted);



    function PBKDF1($pass, $salt, $count, $cb)
    {
      static $base;
      static $extra;
      static $extracount= 0;
      static $hashno;
      static $state = 0;

      if ($state == 0)
      {
        $hashno = 0;
        $state = 1;

        $key = $pass . $salt;
        $base = sha1($key, true);
        for($i = 2; $i < $count; $i++)
        {
          $base = sha1($base, true);
        }
      }

      $result = "";

      if ($extracount > 0)
      {
        $rlen = strlen($extra) - $extracount;
        if ($rlen >= $cb)
        {
          $result = substr($extra, $extracount, $cb);
          if ($rlen > $cb)
          {
            $extracount += $cb;
          }
          else
          {
            $extra = null;
            $extracount = 0;
          }
          return $result;
        }
        $result = substr($extra, $rlen, $rlen);
      }

      $current = "";
      $clen = 0;
      $remain = $cb - strlen($result);
      while ($remain > $clen)
      {
        if ($hashno == 0)
        {
          $current = sha1($base, true);
        }
        else if ($hashno < 1000)
        {
          $n = sprintf("%d", $hashno);
          $tmp = $n . $base;
          $current .= sha1($tmp, true);
        }
        $hashno++;
        $clen = strlen($current);     
      }

      // $current now holds at least as many bytes as we need
      $result .= substr($current, 0, $remain);

      // Save any left over bytes for any future requests
      if ($clen > $remain)
      {
        $extra = $current;
        $extracount = $remain;
      }

      return $result; 
    }

对于128或256个keySize的少量修改,如果您使用MCRYPT_RIJNDAEL_128则需要使用PBKDF1($pass, $salt, 2, 16),其中2是迭代次数,16128/8。 如果您想使用MCRYPT_RIJNDAEL_256,则将16更改为32,因为256/8=32 - Ahsaan Yousuf

2
我认为PHP版本可能会向密钥和IV添加00h值字节。它们的大小都是无效的:每个8字节。需要将它们扩展到16字节以用于AES-128。在您的C#代码中,密钥使用32字节,因此将使用具有256位密钥长度的AES。

此外,您没有指定PasswordDeriveBytes中的迭代次数,应该指定它,因为该类未指定默认迭代次数。根据您的评论,这应该是100,让我们假设它是100。

哦,而且你使用了错误的加密方法。MCRYPT_RIJNDAEL_256指定使用块大小为256位的Rijndael算法,而不是使用256位的密钥。假设密钥的位数只是密钥字节数乘以8。

请尝试将您的Encrypt函数替换为以下函数:
function Encrypt($pass, $salt)
{
     $derived = PBKDF1($pass, $salt, 100, 48);
     $key = bin2hex(substr($derived, 0, 32));
     $iv = bin2hex(substr($derived, 32, 16));
     return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $pass, MCRYPT_MODE_CBC, $iv);
}

最后,请在执行加密或解密之前检查生成的IV和密钥是否与PHP中的匹配。您确定PHP PBKDF1函数是正确的吗?
更新:这里有关于密码推导字节中M$ PBKDF1例程的更多信息(包括您可以尝试转换的Java代码):
“哈,我明白你的意思了。有趣的是,使用.NET:当调用48或调用32后跟16时,结果是不同的:
.NET GetBytes(32 + 16): 04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922655D8DD89AE1CAAC60A8041FCD7E8DA4
.NET GetBytes(32) 04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922 后跟GetBytes(16)89946D3662B3196860A8041FCD7E8DA4”
这是真正的Microsoft代码,他们无法更改它,因为这可能会破坏现有应用程序。请注意,按设计,当使用16个字节然后8个字节或直接使用24个字节时,它们也会返回不同的结果。最好升级到PBKDF2,并将PBKDF1限制为最大20个字节,如标准中所定义。

尽量避免使用默认值(如果可以更改C#代码)。我认为参数是正确的...但是推理将相反。我会相应地重写我的答案。 - Maarten Bodewes
如果密钥正确而IV不正确,您可能需要尝试调用PBKDF1两次,一次请求32字节的密钥,第二次使用16字节的IV,但我认为上面的代码应该是正确的。如果是正确的话,我会删除这个注释。 - Maarten Bodewes
它无法运行,我尝试运行它,但是PHP中的PBKDF1函数没有执行由Microsoft创建的PBKDF1函数的“扩展”,让那些白痴忘记指定它。我会猜一下。你能打印出从你的C#代码检索到的密钥和iv的十六进制吗? - Maarten Bodewes
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/7076/discussion-between-owlstead-and-jyelton - Maarten Bodewes
尽管我最终没有重新创建微软特定的PBKDF1功能,但你揭示了问题的核心并提供了非常需要的帮助。这个答案完全解释了这个特定情况的头痛,因此得到了我的接受。 - JYelton
显示剩余4条评论

-1

这个页面已经在另一个标签上打开了,而且我在我的函数中使用了mcrypt_encrypt。然而,生成正确的密钥和初始化向量并没有成功。需要额外的帮助! :) - JYelton
抱歉,我没有仔细阅读你的问题。你有查看过 Mcrypt 页面上的任何示例吗?它们有很好的做你想要的事情的示例。 - Chris Laplante
是的,这两个示例都特定于TripleDES和ECB;尽管如此,我认为我可能需要使用mcrypt_create_iv,这在示例2中有所体现。然而,正是在这一点上,我感到自己已经深陷加密领域了。 - JYelton
它们应该是相似的。不幸的是,当涉及到具体的加密算法时,我也不太了解。希望其他人能处理具体细节。抱歉 :( - Chris Laplante

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