如何在 C#/Bouncy Castle 中创建 PBKDF2-SHA256 密码哈希
How to create a PBKDF2-SHA256 password hash in C# / Bouncy Castle
我需要创建一个 PBKDF2-SHA256 密码哈希,但遇到了一些问题。
我下载了 Bouncy Castle 存储库,但在寻找我在单元测试中寻找的内容时遇到了一点困难。
找到一些示例代码 here,但这仅适用于 SHA1。代码的关键位是:
/// <summary>
/// Computes the PBKDF2-SHA1 hash of a password.
/// </summary>
/// <param name="password">The password to hash.</param>
/// <param name="salt">The salt.</param>
/// <param name="iterations">The PBKDF2 iteration count.</param>
/// <param name="outputBytes">The length of the hash to generate, in bytes.</param>
/// <returns>A hash of the password.</returns>
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
{
var pdb = new Pkcs5S2ParametersGenerator();
pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
iterations);
var key = (KeyParameter)pdb.GenerateDerivedMacParameters(outputBytes * 8);
return key.GetKey();
}
我需要将其从 SHA1 更改为 SHA256。
从 Java 文档和 this post 看来,以下内容似乎是可行的,但 C# 库中的构造函数没有重载。
var pdb = new Pkcs5S2ParametersGenerator(new Sha256Derived());
在堆栈溢出时发现 another article,我认为以下可能是可能的,但 SHA 哈希算法不在查找列表中,因此以下将不起作用。
var bcparam = (KeyParameter)pdb.GenerateDerivedParameters("sha256", outputBytes * 8);
请问我需要做什么才能让它正常工作?
注意:如果您阅读本文但不知道如何使用 Bouncy Castle,但知道另一种方法,我仍然会感谢您的帮助。
编辑(为简洁起见删除了之前的回答历史记录)
现在有一个直接来自 GitHub 的 Bouncy Castle Crypto API NuGet package that can be used. Alternatively, you can get the source,它将起作用。我从 NuGet 获得了标准的 Bouncy Castle,在撰写本文时尚未更新到 1.8.1。
为了搜索者的利益,这里有一个用于散列的 C# 助手 class。已经在多线程上测试过,看起来不错。
注意:此 class 也适用于库的 .NET Core 版本 BouncyCastle.NetCore
/// <summary>
/// Contains the relevant Bouncy Castle Methods required to encrypt a password.
/// References NuGet Package BouncyCastle.Crypto.dll
/// </summary>
public class BouncyCastleHashing
{
private SecureRandom _cryptoRandom;
public BouncyCastleHashing()
{
_cryptoRandom = new SecureRandom();
}
/// <summary>
/// Random Salt Creation
/// </summary>
/// <param name="size">The size of the salt in bytes</param>
/// <returns>A random salt of the required size.</returns>
public byte[] CreateSalt(int size)
{
byte[] salt = new byte[size];
_cryptoRandom.NextBytes(salt);
return salt;
}
/// <summary>
/// Gets a PBKDF2_SHA256 Hash (Overload)
/// </summary>
/// <param name="password">The password as a plain text string</param>
/// <param name="saltAsBase64String">The salt for the password</param>
/// <param name="iterations">The number of times to encrypt the password</param>
/// <param name="hashByteSize">The byte size of the final hash</param>
/// <returns>A base64 string of the hash.</returns>
public string PBKDF2_SHA256_GetHash(string password, string saltAsBase64String, int iterations, int hashByteSize)
{
var saltBytes = Convert.FromBase64String(saltAsBase64String);
var hash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);
return Convert.ToBase64String(hash);
}
/// <summary>
/// Gets a PBKDF2_SHA256 Hash (CORE METHOD)
/// </summary>
/// <param name="password">The password as a plain text string</param>
/// <param name="salt">The salt as a byte array</param>
/// <param name="iterations">The number of times to encrypt the password</param>
/// <param name="hashByteSize">The byte size of the final hash</param>
/// <returns>A the hash as a byte array.</returns>
public byte[] PBKDF2_SHA256_GetHash(string password, byte[] salt, int iterations, int hashByteSize)
{
var pdb = new Pkcs5S2ParametersGenerator(new Org.BouncyCastle.Crypto.Digests.Sha256Digest());
pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
iterations);
var key = (KeyParameter)pdb.GenerateDerivedMacParameters(hashByteSize * 8);
return key.GetKey();
}
/// <summary>
/// Validates a password given a hash of the correct one. (OVERLOAD)
/// </summary>
/// <param name="password">The original password to hash</param>
/// <param name="salt">The salt that was used when hashing the password</param>
/// <param name="iterations">The number of times it was encrypted</param>
/// <param name="hashByteSize">The byte size of the final hash</param>
/// <param name="hashAsBase64String">The hash the password previously provided as a base64 string</param>
/// <returns>True if the hashes match</returns>
public bool ValidatePassword(string password, string salt, int iterations, int hashByteSize, string hashAsBase64String)
{
byte[] saltBytes = Convert.FromBase64String(salt);
byte[] actualHashBytes = Convert.FromBase64String(hashAsBase64String);
return ValidatePassword(password, saltBytes, iterations, hashByteSize, actualHashBytes);
}
/// <summary>
/// Validates a password given a hash of the correct one (MAIN METHOD).
/// </summary>
/// <param name="password">The password to check.</param>
/// <param name="correctHash">A hash of the correct password.</param>
/// <returns>True if the password is correct. False otherwise.</returns>
public bool ValidatePassword(string password, byte[] saltBytes, int iterations, int hashByteSize, byte[] actualGainedHasAsByteArray)
{
byte[] testHash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);
return SlowEquals(actualGainedHasAsByteArray, testHash);
}
/// <summary>
/// Compares two byte arrays in length-constant time. This comparison
/// method is used so that password hashes cannot be extracted from
/// on-line systems using a timing attack and then attacked off-line.
/// </summary>
/// <param name="a">The first byte array.</param>
/// <param name="b">The second byte array.</param>
/// <returns>True if both byte arrays are equal. False otherwise.</returns>
private 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;
}
}
使用示例
public void CreatePasswordHash_Single()
{
int iterations = 100000; // The number of times to encrypt the password - change this
int saltByteSize = 64; // the salt size - change this
int hashByteSize = 128; // the final hash - change this
BouncyCastleHashing mainHashingLib = new BouncyCastleHashing();
var password = "password"; // That's really secure! :)
byte[] saltBytes = mainHashingLib.CreateSalt(saltByteSize);
string saltString = Convert.ToBase64String(saltBytes);
string pwdHash = mainHashingLib.PBKDF2_SHA256_GetHash(password, saltString, iterations, hashByteSize);
var isValid = mainHashingLib.ValidatePassword(password, saltBytes, iterations, hashByteSize, Convert.FromBase64String(pwdHash));
}
我需要创建一个 PBKDF2-SHA256 密码哈希,但遇到了一些问题。
我下载了 Bouncy Castle 存储库,但在寻找我在单元测试中寻找的内容时遇到了一点困难。
找到一些示例代码 here,但这仅适用于 SHA1。代码的关键位是:
/// <summary>
/// Computes the PBKDF2-SHA1 hash of a password.
/// </summary>
/// <param name="password">The password to hash.</param>
/// <param name="salt">The salt.</param>
/// <param name="iterations">The PBKDF2 iteration count.</param>
/// <param name="outputBytes">The length of the hash to generate, in bytes.</param>
/// <returns>A hash of the password.</returns>
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
{
var pdb = new Pkcs5S2ParametersGenerator();
pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
iterations);
var key = (KeyParameter)pdb.GenerateDerivedMacParameters(outputBytes * 8);
return key.GetKey();
}
我需要将其从 SHA1 更改为 SHA256。
从 Java 文档和 this post 看来,以下内容似乎是可行的,但 C# 库中的构造函数没有重载。
var pdb = new Pkcs5S2ParametersGenerator(new Sha256Derived());
在堆栈溢出时发现 another article,我认为以下可能是可能的,但 SHA 哈希算法不在查找列表中,因此以下将不起作用。
var bcparam = (KeyParameter)pdb.GenerateDerivedParameters("sha256", outputBytes * 8);
请问我需要做什么才能让它正常工作?
注意:如果您阅读本文但不知道如何使用 Bouncy Castle,但知道另一种方法,我仍然会感谢您的帮助。
编辑(为简洁起见删除了之前的回答历史记录)
现在有一个直接来自 GitHub 的 Bouncy Castle Crypto API NuGet package that can be used. Alternatively, you can get the source,它将起作用。我从 NuGet 获得了标准的 Bouncy Castle,在撰写本文时尚未更新到 1.8.1。
为了搜索者的利益,这里有一个用于散列的 C# 助手 class。已经在多线程上测试过,看起来不错。
注意:此 class 也适用于库的 .NET Core 版本 BouncyCastle.NetCore
/// <summary>
/// Contains the relevant Bouncy Castle Methods required to encrypt a password.
/// References NuGet Package BouncyCastle.Crypto.dll
/// </summary>
public class BouncyCastleHashing
{
private SecureRandom _cryptoRandom;
public BouncyCastleHashing()
{
_cryptoRandom = new SecureRandom();
}
/// <summary>
/// Random Salt Creation
/// </summary>
/// <param name="size">The size of the salt in bytes</param>
/// <returns>A random salt of the required size.</returns>
public byte[] CreateSalt(int size)
{
byte[] salt = new byte[size];
_cryptoRandom.NextBytes(salt);
return salt;
}
/// <summary>
/// Gets a PBKDF2_SHA256 Hash (Overload)
/// </summary>
/// <param name="password">The password as a plain text string</param>
/// <param name="saltAsBase64String">The salt for the password</param>
/// <param name="iterations">The number of times to encrypt the password</param>
/// <param name="hashByteSize">The byte size of the final hash</param>
/// <returns>A base64 string of the hash.</returns>
public string PBKDF2_SHA256_GetHash(string password, string saltAsBase64String, int iterations, int hashByteSize)
{
var saltBytes = Convert.FromBase64String(saltAsBase64String);
var hash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);
return Convert.ToBase64String(hash);
}
/// <summary>
/// Gets a PBKDF2_SHA256 Hash (CORE METHOD)
/// </summary>
/// <param name="password">The password as a plain text string</param>
/// <param name="salt">The salt as a byte array</param>
/// <param name="iterations">The number of times to encrypt the password</param>
/// <param name="hashByteSize">The byte size of the final hash</param>
/// <returns>A the hash as a byte array.</returns>
public byte[] PBKDF2_SHA256_GetHash(string password, byte[] salt, int iterations, int hashByteSize)
{
var pdb = new Pkcs5S2ParametersGenerator(new Org.BouncyCastle.Crypto.Digests.Sha256Digest());
pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
iterations);
var key = (KeyParameter)pdb.GenerateDerivedMacParameters(hashByteSize * 8);
return key.GetKey();
}
/// <summary>
/// Validates a password given a hash of the correct one. (OVERLOAD)
/// </summary>
/// <param name="password">The original password to hash</param>
/// <param name="salt">The salt that was used when hashing the password</param>
/// <param name="iterations">The number of times it was encrypted</param>
/// <param name="hashByteSize">The byte size of the final hash</param>
/// <param name="hashAsBase64String">The hash the password previously provided as a base64 string</param>
/// <returns>True if the hashes match</returns>
public bool ValidatePassword(string password, string salt, int iterations, int hashByteSize, string hashAsBase64String)
{
byte[] saltBytes = Convert.FromBase64String(salt);
byte[] actualHashBytes = Convert.FromBase64String(hashAsBase64String);
return ValidatePassword(password, saltBytes, iterations, hashByteSize, actualHashBytes);
}
/// <summary>
/// Validates a password given a hash of the correct one (MAIN METHOD).
/// </summary>
/// <param name="password">The password to check.</param>
/// <param name="correctHash">A hash of the correct password.</param>
/// <returns>True if the password is correct. False otherwise.</returns>
public bool ValidatePassword(string password, byte[] saltBytes, int iterations, int hashByteSize, byte[] actualGainedHasAsByteArray)
{
byte[] testHash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);
return SlowEquals(actualGainedHasAsByteArray, testHash);
}
/// <summary>
/// Compares two byte arrays in length-constant time. This comparison
/// method is used so that password hashes cannot be extracted from
/// on-line systems using a timing attack and then attacked off-line.
/// </summary>
/// <param name="a">The first byte array.</param>
/// <param name="b">The second byte array.</param>
/// <returns>True if both byte arrays are equal. False otherwise.</returns>
private 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;
}
}
使用示例
public void CreatePasswordHash_Single()
{
int iterations = 100000; // The number of times to encrypt the password - change this
int saltByteSize = 64; // the salt size - change this
int hashByteSize = 128; // the final hash - change this
BouncyCastleHashing mainHashingLib = new BouncyCastleHashing();
var password = "password"; // That's really secure! :)
byte[] saltBytes = mainHashingLib.CreateSalt(saltByteSize);
string saltString = Convert.ToBase64String(saltBytes);
string pwdHash = mainHashingLib.PBKDF2_SHA256_GetHash(password, saltString, iterations, hashByteSize);
var isValid = mainHashingLib.ValidatePassword(password, saltBytes, iterations, hashByteSize, Convert.FromBase64String(pwdHash));
}