PBKDF2 For Php 的 C# 实现

问题描述 投票:0回答:1

我正在将 PHP 代码转换为 C#.net,其中一项任务是对密码进行哈希处理。

PHP 正在使用 PBKDF2 For PHP (https://defuse.ca/php-pbkdf2.htm),经过一番搜索后,我设法编写了一个进行哈希处理的代码,但我无法使用我的密码验证现有密码方法。我需要帮助来理解我所缺少的东西。

PHP 代码

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

    if (function_exists("hash_pbkdf2")) {
        // The output length is in NIBBLES (4-bits) if $raw_output is false!
        if (!$raw_output) {
            $key_length = $key_length * 2;
        }
        return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
    }

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

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}

C# 实现是

    public static string ComputePBKDF2(string algorithm, string password, byte[] salt, int count, int keyLength, bool rawOutput = false)
{
    algorithm = algorithm.ToLower();

    if (count <= 0 || keyLength <= 0)
        throw new ArgumentException("Invalid parameters.");

    if (CryptoConfig.AllowOnlyFipsAlgorithms && !algorithm.StartsWith("hmac", StringComparison.OrdinalIgnoreCase))
        algorithm = "hmacsha256"; // Default to HMAC-SHA-256 for FIPS compliance

    using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt, count))
    {
        //pbkdf2. = keyLength; // Set the key length
        byte[] derivedKey = pbkdf2.GetBytes(keyLength);

        if (rawOutput)
        {
            return Convert.ToBase64String(derivedKey);
        }
        else
        {
            return BitConverter.ToString(derivedKey).Replace("-", "").ToLower();
        }
    }
}

我也尝试过以下方法,但都无法验证密码

    public static byte[] ComputePBKDF2(string algorithm, string password, byte[] salt, int count, int keyLength)
{
    using (SHA256 hmac = SHA256.Create(algorithm))
    {
        int hashLength = (hmac.HashSize + 7) / 8; // Length of the hash in bytes
        int blockCount = (int)Math.Ceiling((double)keyLength / hashLength);

        byte[] output = new byte[keyLength];
        byte[] last = salt;

        for (int i = 0; i < blockCount; i++)
        {
            byte[] iter = BitConverter.GetBytes(i + 1); // $i encoded as 4 bytes, big endian
            Array.Reverse(iter); // Convert to big-endian

            byte[] xorSum = hmac.ComputeHash(last);
            last = xorSum;

            for (int j = 1; j < count; j++)
            {
                byte[] iterHmac = hmac.ComputeHash(last);
                last = iterHmac;

                for (int k = 0; k < xorSum.Length; k++)
                {
                    xorSum[k] ^= iterHmac[k];
                }
            }

            Array.Copy(xorSum, 0, output, i * hashLength, Math.Min(hashLength, keyLength - i * hashLength));
        }

        return output;
    }
}

为了验证它,我使用密码和哈希值调用函数

VerifyPassword("Nouman@2023!", "sha256:1000:DlrSWdE5PFiP7MiMPUD98rwYobnDp56+:dDh1pxHduY9iw5uGFowNWncNfhPXWGDB")

验证方法是

    public static bool VerifyPassword(string password, string correctHash)
{
    int HASH_SECTIONS = 4;
    int HASH_ALGORITHM_INDEX = 0;
    int HASH_ITERATION_INDEX = 1;
    int HASH_SALT_INDEX = 2;
    int HASH_PBKDF2_INDEX = 3;
    string[] hassSplit = correctHash.Split(':');

    if (hassSplit.Length < HASH_SECTIONS)
        return false;

    byte[] pbkdf2 = Convert.FromBase64String(hassSplit[HASH_PBKDF2_INDEX]);
    byte[] salt = new byte[24];
    salt = Convert.FromBase64String(hassSplit[HASH_SALT_INDEX]);

    string result = ComputePBKDF2(hassSplit[HASH_ALGORITHM_INDEX], password, salt, int.Parse(hassSplit[HASH_ITERATION_INDEX]), pbkdf2.Length,true);
    Console.WriteLine(result);
    byte[] computedHash = Convert.FromBase64String(result.);
    
    return slowEquals(pbkdf2, computedHash);
    
}

private static bool slowEquals(byte[] a, byte[] b)
{
    uint diff = (uint)a.Length ^ (uint)b.Length;
    for (int i = 0; i < a.Length && i < b.Length; i++)
    {
        diff |= (uint)(a[i] ^ b[i]);
    }
    return diff == 0;
}
c# php hash
1个回答
0
投票

我将这段代码分成更小的部分来解决这个问题,它们是;

  • 生成盐
  • 创建哈希
  • 验证现有哈希
  • 创建新的哈希值,也可以通过相同的方法进行验证

所以为了生成Salth,在C#中,最初我使用

RNGCryptoServiceProvider
类来生成加密安全的随机字节,这相当于PHP中的
mcrypt_create_iv
MCRYPT_DEV_URANDOM
。以下是我最初在 C# 中使用它的方式:

int PBKDF2_SALT_BYTE_SIZE = 24; // You can adjust the size to your requirements
byte[] salt = new byte[PBKDF2_SALT_BYTE_SIZE];
using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
{
    rngCsp.GetBytes(salt);
}

最新的方法是使用

RandomNumberGenerator

// Generate a salt
byte[] salt = RandomNumberGenerator.GetBytes(PBKDF2_SALT_BYTE_SIZE);

    

在 PHP 代码中,用于生成哈希值的主要方法是

hash_pbkdf2
。在 C# 中,我使用
Rfc2898DeriveBytes
类来执行与 PHP 中的
hash_pbkdf2
等效的操作。我的使用方法如下:

// Generate the hash
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes("PasswordToHash", Convert.ToBase64String(salt), PBKDF2_ITERATIONS,HashAlgorithmName.SHA256);
byte[] hash = pbkdf2.GetBytes(PBKDF2_HASH_BYTE_SIZE);

这里最需要注意的是构造函数中传递的哈希算法名称。默认情况下,此类使用 SHA1。另一件要注意的事情是我必须在

Encoding.ASCII.GetBytes
上使用
Convert.ToBase64String
作为盐。如果我不使用它,那么用于验证现有哈希的验证函数将不适用于新密码哈希。

现在是验证部分。我不需要为它编写任何 Slowequal 方法。内置 C# 函数

CryptographicOperations.FixedTimeEquals
可以解决问题

public static bool Validate(string passwordHash, string password)
{
    Console.WriteLine(HashAlgorithmName.SHA256.ToString().ToLower());
    char[] delimiter = { ':' };
    var pwdElements = passwordHash.Split(delimiter);
    //var salt = Convert.FromBase64String(pwdElements[2]);
    var hash = Convert.FromBase64String(pwdElements[3]);
    byte[] saltBytes = Encoding.ASCII.GetBytes(pwdElements[2]);
    var hashInput = Rfc2898DeriveBytes.Pbkdf2(password, saltBytes, 1000, _hashAlgorithmName, 24);

    Console.WriteLine("saved hash: " + Convert.ToHexString(hash));
    Console.WriteLine("generated hash: " + Convert.ToHexString(hashInput));

    return CryptographicOperations.FixedTimeEquals( hashInput, hash);
}

此代码还验证新的哈希值和现有的哈希值。

© www.soinside.com 2019 - 2024. All rights reserved.