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

发布于 2024-12-29 04:19:11 字数 2480 浏览 2 评论 0原文

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

但是我仍然遇到困难就我的具体情况而言。

我尝试了各种方法,但最终收到错误“IV 参数必须与块大小一样长”或与生成的哈希值不匹配的文本。

我对加密的了解还不足以弄清楚我做错了什么。

这是 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”,我认为他是示例作者。不幸的是我无法改变这一点。

There are already some helpful questions on SO:

However I am still having difficulties with my particular case.

I've tried various methods but end up getting the error "The IV parameter must be as long as the blocksize" or text that doesn't match the resulting hash.

I don't understand encryption enough to work out what I'm doing wrong.

Here is the php version:

$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;
}

And the C# version:

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);
}

I want to be able to validate user logins in a new php-based application which will communicate to the same MySQL database as an existing C# application. I intend to encrypt the password and compare the resulting hash to the one stored in the database to authenticate.

Any pointers would be most appreciated.

Edit:

I realize that in the C# function, the PasswordDeriveBytes is being called and passed a byte array as an argument for which I don't have an analog in the PHP version. I discovered that this originates from a Codeproject example and that the byte array in ASCII spells "Ivan Medvedev" whom I assume to be the example author. Unfortunately I cannot change this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

风流物 2025-01-05 04:19:11

对于正在寻找从 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; 
    }

Below snippet may be helpful who are looking for exact conversion from C# to 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; 
    }
无敌元气妹 2025-01-05 04:19:11

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

此外,您没有在 PasswordDeriveBytes 中指定迭代次数,您应该指定它,因为该类没有指定默认的迭代次数 - 根据您的注释,这将是 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中的IV和密钥是否匹配。您确定 PHP PBKDF1 函数正确吗?

更新:
这里是有关 M$ PBKDF1 例程的更多信息在PasswordDeriveBytes中(包括您可以尝试并转换的Java代码):

哈哈,我明白你的意思了。

有趣的是,使用.NET:调用48时结果不同
或先调用 32,后调用 16:

.NET GetBytes( 32 +16 ):
04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922655D8DD89AE1CAAC60A8041FCD7E8DA4

.NET GetBytes( 32 )
04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922
接下来是 GetBytes( 16 ) 89946D3662B3196860A8041FCD7E8DA4

真正的 Microsoft 代码,他们无法更改它,因为它可能会破坏现场应用程序。请注意,当使用 16 个字节然后使用 8 个字节或按设计直接使用 24 个字节调用时,它们也会返回不同的结果。您最好升级到 PBKDF2,并将 PBKDF1 按照标准中的定义限制为最大 20 个字节。

I think that the PHP version may actually add 00h valued bytes to the key and IV. They both have an invalid size : 8 bytes for each. They need to be extended to 16 bytes for AES-128. In your C# code you use 32 bytes for the key, which will therefore use AES with a key size of 256 bits.

Futhermore, you don't specify the number of iterations in PasswordDeriveBytes, you should specify it as the class does not specify the default number of iterations - according to your comments, this would be 100, lets assume it is.

Oh, and you use the incorrect encryption method. MCRYPT_RIJNDAEL_256 specifies the Rijndael algorithm using a blocksize of 256 bits, not keys of 256 bits. Presumably, the bitsize of the keys is simply the number of bytes of the key times 8.

Could you replace your Encrypt function with this and try again?

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);
}

Finally, please check if the generated IV and key match with the ones in PHP before performing encryption or decryption. Are you sure that that PHP PBKDF1 function is correct?

UPDATE:
Here is some more information on the M$ PBKDF1 routines in PasswordDeriveBytes (including Java code which you may try and convert):

ha, I see your point.

Interestingly, using .NET: the results are different when calling 48
or calling 32 followed by 16:

.NET GetBytes( 32 +16 ):
04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922655D8DD89AE1CAAC60A8041FCD7E8DA4

.NET GetBytes( 32 )
04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922
Followed by GetBytes( 16 ) 89946D3662B3196860A8041FCD7E8DA4

True Microsoft code, and they cannot change it because it could break applications in the field. Note that they also would return different results when calling it with 16 and then 8 bytes or directly by 24 bytes by design. You'd better upgrade to PBKDF2, and keep PBKDF1 limited to 20 bytes max, as defined in the standards.

策马西风 2025-01-05 04:19:11

PHP 已将此功能内置到其 Mcrypt 模块中。

试试这个:http://www.php.net/manual/en/book。 mcrypt.php

PHP already has this capability built into its Mcrypt module.

Try this: http://www.php.net/manual/en/book.mcrypt.php

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文