为什么hash_pbkdf2(PHP)的输出与.NET / C#实现不同

时间:2014-05-11 14:13:40

标签: c# php .net pbkdf2 rfc2898

在我的一个小项目中,我需要计算一个函数的哈希值。

我有一个PHP哈希的工作示例

$pass = "123456";
$mysalt = strrev($pass);
echo hash_pbkdf2('sha1', $pass, $mysalt, 1000, 32); //using the PHP inbuilt function

echo "</br>";
include_once('PasswordHash.php');
echo pbkdf2('sha1', $pass, $mysalt, 1000, 16);  //using external code

它们都有相同的输出:523d904c8f2df96634d9eed3b444838e

现在我需要我的代码向后兼容生成的C#,因为密码将由PHP服务器验证。并且请求将由C#应用程序发送。

以下是我尝试的内容:output = 8e59ead5f90c6af11cf80641d51c241c

public static class Program
{
    public static string ReverseString(this string s)
    {
        char[] arr = s.ToCharArray();
        Array.Reverse(arr);
        return new string(arr);
    }

    static void Main(string[] args)
    {
        var pass = "123456";
        byte[] salt = Encoding.ASCII.GetBytes(pass.ReverseString());


        //https://github.com/defuse/password-hashing/blob/master/PasswordHash.cs
        //was getting error salt not 8 byte,
        //http://stackoverflow.com/questions/1647481/what-is-the-c-sharp-equivalent-of-the-php-pack-function
        salt = Pack(pass.ReverseString());
        var hash = PasswordHash.PBKDF2(pass, salt, 1000, 16);
        Console.WriteLine(BitConverter.ToString(hash).Replace("-", string.Empty).ToLower());

        Console.ReadKey();

    }

    public static byte[] Pack(string salt)
    {
        using (var ms = new MemoryStream())
        {
            using (var bw = new BinaryWriter(ms))
            {
                var data = Encoding.ASCII.GetBytes(salt);
                bw.Write(data.Length + 4); // Size of ASCII string + length (4 byte int)
                bw.Write(data);
            }

            return ms.ToArray();
        }
    }
}

2 个答案:

答案 0 :(得分:4)

这里的问题是你的盐。它只有6个字节,PHP处理的不同于c#代码。如果您将代码更新为以下内容:

<?php
$pass = "1234567890";
$mysalt = strrev($pass);
echo hash_pbkdf2('sha1', $pass, $mysalt, 1000, 32);
?>

你的输出是:42e8bfc7fc5fd4686915d49d5a29bc1e

然后将c#代码调整为:

var pass = "1234567890";
byte[] salt = Encoding.ASCII.GetBytes(pass.ReverseString());

//DISABLE YOUR PACK METHOD
//salt = Pack(pass.ReverseString());

var hash = PasswordHash.PBKDF2(pass, salt, 1000, 16);
Console.WriteLine(BitConverter.ToString(hash).Replace("-", string.Empty).ToLower());

Console.ReadKey();

输出为:42e8bfc7fc5fd4686915d49d5a29bc1e

差异来自您的Pack方法,它随机地向盐添加4个字节。您可以在VS中的检查器中轻松查看。

Salt inspector

因此,最简单的解决方法是使用至少包含8个字符的盐(Rfc2898DeriveBytes代码使用的最小值C#)并且不要使用Pack方法

如果查看php docs,则会有“请求注释”,提到盐必须至少为8字节(64位)。因此,使用较少会导致冲突,就像您已经遇到的那样。

<强>更新

现在,如果您真的想要使用安全性较低的盐并且使用&lt; 8字节,那么您可以查看以下stackoverflow问题PBKDF2 implementation in C# with Rfc2898DeriveBytes,了解不需要最小长度的c#版本。

答案 1 :(得分:2)

看起来Pack方法不是必需的,但是你的盐必须至少有8个字节。

$pass = "12345678";
$mysalt = strrev($pass);
echo hash_pbkdf2('sha1', $pass, $mysalt, 1000, 32); //using the PHP inbuilt function

这会输出381dae25b08b6f141671c74715961b1b

此C#代码提供相同的输出。

public static class Program
{
    public static string ReverseString(this string s)
    {
        char[] arr = s.ToCharArray();
        Array.Reverse(arr);
        return new string(arr);
    }

    static void Main(string[] args)
    {
        var pass = "12345678";
        byte[] salt = Encoding.ASCII.GetBytes(pass.ReverseString());


        //https://github.com/defuse/password-hashing/blob/master/PasswordHash.cs
        //was getting error salt not 8 byte,
        //https://stackoverflow.com/questions/1647481/what-is-the-c-sharp-equivalent-of-the-php-pack-function
        var hash = PasswordHash.PBKDF2(pass, salt, 1000, 16);
        Console.WriteLine(BitConverter.ToString(hash).Replace("-", string.Empty).ToLower());

        Console.ReadKey();

    }
}

根据您的评论,您似乎可能会在需求限制下进行开发。如果您无法控制盐的要求,可以查看this answer