我正在将 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;
}
我将这段代码分成更小的部分来解决这个问题,它们是;
所以为了生成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);
}
此代码还验证新的哈希值和现有的哈希值。