【问题标题】:Convert C# PBKDF2 using Rfc2898DeriveBytes to PHP使用 Rfc2898DeriveBytes 将 C# PBKDF2 转换为 PHP
【发布时间】:2017-01-17 01:40:29
【问题描述】:

长话短说,我们有一个内置于 .NET 的会员系统,我们将其移植到 WordPress,并且需要复制 PBKDF2 加密,因此用户无需重置密码。

使用已知的散列密码,我可以轻松地在 .NET 中复制此密码,代码如下:

static void Main(string[] args)
{
  var isValid = CheckPassword("#0zEZcD7uNmv", "5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ");
}

public static int PBKDF2IterCount = 10000;
public static int PBKDF2SubkeyLength = 256 / 8; // 256 bits
public static int SaltSize = 128 / 8; // 128 bits

private static bool CheckPassword(string Password, string ExistingHashedPassword)
{
  byte[] saltAndPassword = Convert.FromBase64String(ExistingHashedPassword);
  byte[] salt = new byte[SaltSize];

  Array.Copy(saltAndPassword, 0, salt, 0, SaltSize);

  Console.WriteLine("--Salt--");
  Console.WriteLine(Convert.ToBase64String(salt));

  string hashedPassword = HashPassword(Password, salt);

  Console.WriteLine("--HashedPassword--");
  Console.WriteLine(hashedPassword);

  return hashedPassword == ExistingHashedPassword;
}

private static string HashPassword(string Password, byte[] salt)
{
  byte[] hash = new byte[PBKDF2SubkeyLength];
  using (var pbkdf2 = new Rfc2898DeriveBytes(Password, salt, PBKDF2IterCount))
  {
    hash = pbkdf2.GetBytes(PBKDF2SubkeyLength);
  }

  byte[] hashBytes = new byte[PBKDF2SubkeyLength + SaltSize];
  Array.Copy(salt, 0, hashBytes, 0, SaltSize);
  Array.Copy(hash, 0, hashBytes, SaltSize, PBKDF2SubkeyLength);

  string hashedPassword = Convert.ToBase64String(hashBytes);
  return hashedPassword;
}

控制台应用程序将生成以下内容:

--Salt--
5SyOX+Rbclzvvit3MEM2nA==
--HashedPassword--
5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ
--IsValid--
True

但是在 PHP 方面我无法得到相同的结果。到目前为止,我的代码如下。

$mySalt = base64_decode('5SyOX+Rbclzvvit3MEM2nA==');
$dev = pbkdf2('sha1', '#0zEZcD7uNmv', $mySalt, 10000, 48, true);
$key = substr($dev, 0, 32); //Keylength: 32
$iv = substr($dev, 32, 16); // IV-length: 16

echo 'PHP<br/>';
echo 'PASS: '.base64_encode($dev).'<br/>';
echo 'SALT: '.base64_encode($iv).'<br/><br/>'; 

echo '.NET<br/>';
echo 'PASS: 5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ<br/>';
echo 'SALT: 5SyOX+Rbclzvvit3MEM2nA==<br/><br/>'; 

function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
        die('PBKDF2 ERROR: Invalid parameters.');

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }
    return substr($output, 0, $key_length);
}

结果是:

PHP
PASS: FFo9WjYztlOzuffOddN/Jbg6HBOUku+lxSUKvJuWCRCsYe+1Tgbb8Ob4FtxumMal
SALT: rGHvtU4G2/Dm+BbcbpjGpQ==

.NET
PASS: 5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ
SALT: 5SyOX+Rbclzvvit3MEM2nA==

任何帮助将不胜感激。

【问题讨论】:

  • @ArtisticPhoenix 谢谢,将其切换为自定义方法得到了完全相同的结果,这很好,它简化了代码,但不幸的是仍然存在同样的问题。
  • 嗯,一个问题是您正在打印$iv 并将其标记为 SALT。接下来我会问 strlen 是否合适。但更难看到的是 .NET 通过 UTF-8 将字符串密码转换为字节。如果 PHP 使用 UCS-2 或 UTF-16,那么你的二进制 HMAC 密钥就不一样了。
  • 正如@bartonjs - 提到的,我会为字符集添加你可能会发现这很有用php.net/manual/en/function.mb-internal-encoding.php
  • @ArtisticPhoenix 内部编码没有帮助。

标签: c# php wordpress pbkdf2


【解决方案1】:

最终使用 https://github.com/defuse/password-hashing 库使其工作,一些小的更改与我正在使用的正在导入的数据库的哈希格式相匹配。

但我的主要问题是我试图从哈希中获取密钥的这些行。

$dev = pbkdf2('sha1', '#0zEZcD7uNmv', $mySalt, 10000, 48, true);
$key = substr($dev, 0, 32); //Keylength: 32
$iv = substr($dev, 32, 16); // IV-length: 16

将其更改为以下内容,以便它创建一个 32 位长的散列散列并将返回的散列连接到盐解决了该问题。

$dev = pbkdf2('sha1', '#0zEZcD7uNmv', $mySalt, 10000, 32, true);
echo 'PASS: '.base64_encode($mySalt.$dev).'<br />';

下面的输出现在与 .NET 匹配:

PASS: 5SyOX+Rbclzvvit3MEM2nBRaPVo2M7ZTs7n3znXTfyW4OhwTlJLvpcUlCryblgkQ

【讨论】:

    【解决方案2】:

    我在寻找将密码从旧版 Asp.Net MVC 应用程序迁移到 Laravel 的方法时遇到了这篇文章。

    对于那些只想比较生成的哈希(即用于身份验证)感兴趣的人,请考虑以下几点:

    function legacyHashCheck($hash, $password)
    {
        $raw     = base64_decode($hash);
        $salt    = substr($raw, 1, 16);
        $payload = substr($raw, 17, 32);
    
        //new Rfc2898DeriveBytes(password, salt, 1000).GetBytes(32)
        $check   = hash_pbkdf2('sha1', $password, $salt, 1000, 32, true);
    
        return $payload === $check;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-11-06
      • 2015-01-30
      • 2015-11-09
      • 2017-12-04
      • 2011-09-15
      • 2020-08-04
      • 1970-01-01
      • 1970-01-01
      • 2011-12-25
      相关资源
      最近更新 更多