我需要创建一个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,但确实知道另一种方式,我仍然感谢你的帮助.
解决方法
编辑(为简洁起见删除了以前的答案历史记录)
现在有一个可以使用的Bouncy Castle Crypto API NuGet包.或者,您可以直接从GitHub获取source,这将工作.我从NuGet获得了标准的Bouncy Castle,在撰写本文时尚未更新为1.8.1.
为了搜索者的利益,这里有一个用于散列的C#助手类.已经在多线程上测试过,看起来很好.
注意:此类也适用于库BouncyCastle.NetCore的.NET Core版本
/// <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 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,int hashByteSize) { var pdb = new Pkcs5S2ParametersGenerator(new Org.BouncyCastle.Crypto.Digests.Sha256Digest()); pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.tocharArray()),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 hashByteSize,string hashAsBase64String) { byte[] saltBytes = Convert.FromBase64String(salt); byte[] actualHashBytes = Convert.FromBase64String(hashAsBase64String); return ValidatePassword(password,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,byte[] actualGainedHasAsByteArray) { byte[] testHash = PBKDF2_SHA256_GetHash(password,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,hashByteSize); var isValid = mainHashingLib.ValidatePassword(password,Convert.FromBase64String(pwdHash)); }