我正在尝试使用.Net Framework 4.5中的api,该API应该可以为我提供加密货币钱包。在documentations的一部分中说:
通过PBKDF2功能以128位密钥长度和
SHA256的1,024次迭代
我在C#中找不到指定方法来做到这一点。在文档中,他们输入了“ be9d3a4f1220495a96c38d36d8558365”作为密码,输出为“ 4369cb0560d54f55d0c03564fbd983c4”。
看来我应该使用Rfc2898DeriveBytes方法,我像下面的代码一样使用它,但是我没有得到相同的结果。string output = Convert.ToBase64String((new Rfc2898DeriveBytes("e24546d6643137a310968566cf1cd42b",16, 1024)).GetBytes(32));
输出==>'x10zclBJY2eeZqjMyPfQm4ljyMFPvWbxF72Om2DCzHE ='
最佳答案
最好实现自己的PBKDF2版本。 PBKDF2是由名称错误的Rfc2898DeriveBytes类实现的实际算法。
由于.NET 4.5不包括将PBKDF2与其他哈希一起使用的功能。 .NET版本4.7.2确实包含该功能,但不允许salt为零字节。
因此,最好实现自己的版本。 Microsoft的.NET版本具有似乎不兼容的特定版权声明。解决此问题的一种方法是从Mono实现PBKDF2,但是Mono的更高版本没有实现该类(看来),并且它们没有实现可以选择哈希的版本。
幸运的是,bartonjs has indicated a version具有允许使用的MIT许可证,可以使用以下解决方案:
using System;
using System.Security.Cryptography;
using System.Text;
namespace StackOverflow
{
public class Rfc2898DeriveBytes : DeriveBytes
{
private const string DEFAULT_HASH_ALGORITHM = "SHA-1";
private const int MinimumSaltSize = 0;
private readonly byte[] _password;
private byte[] _salt;
private uint _iterations;
private HMAC _hmac;
private int _blockSize;
private byte[] _buffer;
private uint _block;
private int _startIndex;
private int _endIndex;
public string HashAlgorithm { get; }
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations)
: this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, string hashAlgorithm)
{
if (salt == null)
throw new ArgumentNullException(nameof(salt));
if (salt.Length < MinimumSaltSize)
throw new ArgumentException(nameof(salt));
if (iterations <= 0)
throw new ArgumentOutOfRangeException(nameof(iterations));
if (password == null)
throw new NullReferenceException(); // This "should" be ArgumentNullException but for compat, we throw NullReferenceException.
_salt = (byte[])salt.Clone();
_iterations = (uint)iterations;
_password = (byte[])password.Clone();
HashAlgorithm = hashAlgorithm;
_hmac = OpenHmac();
// _blockSize is in bytes, HashSize is in bits.
_blockSize = _hmac.HashSize >> 3;
Initialize();
}
public Rfc2898DeriveBytes(string password, byte[] salt)
: this(password, salt, 1000)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations)
: this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations, string hashAlgorithm)
: this(Encoding.UTF8.GetBytes(password), salt, iterations, hashAlgorithm)
{
}
public Rfc2898DeriveBytes(string password, int saltSize)
: this(password, saltSize, 1000)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
: this(password, saltSize, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations, string hashAlgorithm)
{
if (saltSize < 0)
throw new ArgumentOutOfRangeException(nameof(saltSize));
if (saltSize < MinimumSaltSize)
throw new ArgumentException(nameof(saltSize));
if (iterations <= 0)
throw new ArgumentOutOfRangeException(nameof(iterations));
_salt = new byte[saltSize];
RandomNumberGenerator.Create().GetBytes(_salt);
_iterations = (uint)iterations;
_password = Encoding.UTF8.GetBytes(password);
HashAlgorithm = hashAlgorithm;
_hmac = OpenHmac();
// _blockSize is in bytes, HashSize is in bits.
_blockSize = _hmac.HashSize >> 3;
Initialize();
}
public int IterationCount
{
get
{
return (int)_iterations;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
_iterations = (uint)value;
Initialize();
}
}
public byte[] Salt
{
get
{
return (byte[])_salt.Clone();
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.Length < MinimumSaltSize)
throw new ArgumentException("Too few bytes for salt");
_salt = (byte[])value.Clone();
Initialize();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_hmac != null)
{
_hmac.Dispose();
_hmac = null;
}
if (_buffer != null)
Array.Clear(_buffer, 0, _buffer.Length);
if (_password != null)
Array.Clear(_password, 0, _password.Length);
if (_salt != null)
Array.Clear(_salt, 0, _salt.Length);
}
base.Dispose(disposing);
}
public override byte[] GetBytes(int cb)
{
if (cb <= 0)
throw new ArgumentOutOfRangeException(nameof(cb));
byte[] password = new byte[cb];
int offset = 0;
int size = _endIndex - _startIndex;
if (size > 0)
{
if (cb >= size)
{
Buffer.BlockCopy(_buffer, _startIndex, password, 0, size);
_startIndex = _endIndex = 0;
offset += size;
}
else
{
Buffer.BlockCopy(_buffer, _startIndex, password, 0, cb);
_startIndex += cb;
return password;
}
}
while (offset < cb)
{
byte[] T_block = Func();
int remainder = cb - offset;
if (remainder > _blockSize)
{
Buffer.BlockCopy(T_block, 0, password, offset, _blockSize);
offset += _blockSize;
}
else
{
Buffer.BlockCopy(T_block, 0, password, offset, remainder);
offset += remainder;
Buffer.BlockCopy(T_block, remainder, _buffer, _startIndex, _blockSize - remainder);
_endIndex += (_blockSize - remainder);
return password;
}
}
return password;
}
public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV)
{
// If this were to be implemented here, CAPI would need to be used (not CNG) because of
// unfortunate differences between the two. Using CNG would break compatibility. Since this
// assembly currently doesn't use CAPI it would require non-trivial additions.
// In addition, if implemented here, only Windows would be supported as it is intended as
// a thin wrapper over the corresponding native API.
// Note that this method is implemented in PasswordDeriveBytes (in the Csp assembly) using CAPI.
throw new PlatformNotSupportedException();
}
public override void Reset()
{
Initialize();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "HMACSHA1 is needed for compat. (https://github.com/dotnet/corefx/issues/9438)")]
private HMAC OpenHmac()
{
String hashAlgorithm = HashAlgorithm;
if (string.IsNullOrEmpty(hashAlgorithm))
throw new CryptographicException("HashAlgorithm name not present");
if (hashAlgorithm.Equals("SHA-1", StringComparison.OrdinalIgnoreCase))
return new HMACSHA1(_password);
if (hashAlgorithm.Equals("SHA-256", StringComparison.OrdinalIgnoreCase))
return new HMACSHA256(_password);
if (hashAlgorithm.Equals("SHA-384", StringComparison.OrdinalIgnoreCase))
return new HMACSHA384(_password);
if (hashAlgorithm.Equals("SHA-512", StringComparison.OrdinalIgnoreCase))
return new HMACSHA512(_password);
throw new CryptographicException("MAC algorithm " + hashAlgorithm + " not available");
}
private void Initialize()
{
if (_buffer != null)
Array.Clear(_buffer, 0, _buffer.Length);
_buffer = new byte[_blockSize];
_block = 1;
_startIndex = _endIndex = 0;
}
// This function is defined as follows:
// Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
// where i is the block number.
private byte[] Func()
{
byte[] temp = new byte[_salt.Length + sizeof(uint)];
Buffer.BlockCopy(_salt, 0, temp, 0, _salt.Length);
WriteInt(_block, temp, _salt.Length);
temp = _hmac.ComputeHash(temp);
byte[] ret = temp;
for (int i = 2; i <= _iterations; i++)
{
temp = _hmac.ComputeHash(temp);
for (int j = 0; j < _blockSize; j++)
{
ret[j] ^= temp[j];
}
}
// increment the block count.
_block++;
return ret;
}
private void WriteInt(uint i, byte[] buf, int bufOff)
{
buf[bufOff++] = (byte)(i >> 24);
buf[bufOff++] = (byte)(i >> 16);
buf[bufOff++] = (byte)(i >> 8);
buf[bufOff] = (byte)i;
}
}
}
这是一类,其中重写了更具体的异常,替换了一些特殊的克隆,并且对随机盐生成进行了概括。最小盐大小也已设置为0。否则,它是在不同名称空间中的相同代码。
可以这样使用它:
string pw = "be9d3a4f1220495a96c38d36d8558365";
byte[] salt = new byte[0];
int iterations = 1024;
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(pw, salt, iterations, "SHA-256");
byte[] key = pbkdf2.GetBytes(16);
请注意,PIN是十六进制编码为UTF-8,这是PBKDF2的默认编码(不是.NET!的默认编码)。结果是一个密钥,当以十六进制表示时,该密钥等于
4369cb0560d54f55d0c03564fbd983c4
。我已经使用字符串将其表示为哈希函数转换为4.5兼容类,对于带有枚举
HashAlgorithm
(4.6或类似名称)的类,请查看the revision history。关于c# - .NET Framework 4.5中的PBKDF2函数具有128位 key 长度和SHA256的1,024次迭代,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53228398/