Rfc2898/PBKDF2 с SHA256 как дайджест в С#
Я хочу использовать Rfc2898 в С# для получения ключа. Мне также нужно использовать SHA256 как дайджест для Rfc2898. Я нашел класс Rfc2898DeriveBytes
, но он использует SHA-1, и я не вижу способа заставить его использовать другой дайджест.
Есть ли способ использовать Rfc2898 в С# с SHA256 в качестве дайджеста (не реализовав его с нуля)?
Ответы
Ответ 1
Смотрите ответ Бруно Гарсиа.
Карстен: Пожалуйста, примите этот ответ, а не этот.
В то время, когда я начинал этот ответ, Rfc2898DeriveBytes не настраивался для использования другой хэш-функции. Тем временем, однако, это было улучшено; см. ответ Бруно Гарсии. Следующая функция может использоваться для генерации хешированной версии предоставленного пользователем пароля для хранения в базе данных в целях аутентификации.
Для пользователей более старых платформ .NET это по-прежнему полезно:
// NOTE: The iteration count should
// be as high as possible without causing
// unreasonable delay. Note also that the password
// and salt are byte arrays, not strings. After use,
// the password and salt should be cleared (with Array.Clear)
public static byte[] PBKDF2Sha256GetBytes(int dklen, byte[] password, byte[] salt, int iterationCount){
using(var hmac=new System.Security.Cryptography.HMACSHA256(password)){
int hashLength=hmac.HashSize/8;
if((hmac.HashSize&7)!=0)
hashLength++;
int keyLength=dklen/hashLength;
if((long)dklen>(0xFFFFFFFFL*hashLength) || dklen<0)
throw new ArgumentOutOfRangeException("dklen");
if(dklen%hashLength!=0)
keyLength++;
byte[] extendedkey=new byte[salt.Length+4];
Buffer.BlockCopy(salt,0,extendedkey,0,salt.Length);
using(var ms=new System.IO.MemoryStream()){
for(int i=0;i<keyLength;i++){
extendedkey[salt.Length]=(byte)(((i+1)>>24)&0xFF);
extendedkey[salt.Length+1]=(byte)(((i+1)>>16)&0xFF);
extendedkey[salt.Length+2]=(byte)(((i+1)>>8)&0xFF);
extendedkey[salt.Length+3]=(byte)(((i+1))&0xFF);
byte[] u=hmac.ComputeHash(extendedkey);
Array.Clear(extendedkey,salt.Length,4);
byte[] f=u;
for(int j=1;j<iterationCount;j++){
u=hmac.ComputeHash(u);
for(int k=0;k<f.Length;k++){
f[k]^=u[k];
}
}
ms.Write(f,0,f.Length);
Array.Clear(u,0,u.Length);
Array.Clear(f,0,f.Length);
}
byte[] dk=new byte[dklen];
ms.Position=0;
ms.Read(dk,0,dklen);
ms.Position=0;
for(long i=0;i<ms.Length;i++){
ms.WriteByte(0);
}
Array.Clear(extendedkey,0,extendedkey.Length);
return dk;
}
}
Ответ 2
.NET Core имеет новую реализацию Rfc2898DeriveBytes
.
версия CoreFX больше не имеет жестко закодированный алгоритм хеширования
Код доступен в Github. Он был объединен для освоения в марте 2017 года и был отправлен с .NET Core 2.0.
Ответ 3
Для тех, кому это нужно,.NET Framework 4.7.2 включает перегрузку Rfc2898DeriveBytes, которая позволяет указать алгоритм хеширования:
byte[] bytes;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
bytes = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
Варианты HashAlgorithmName на данный момент:
- MD5
- SHA1
- SHA256
- SHA384
- SHA512
Ответ 4
BCL Rfc2898DeriveBytes
жестко закодирован для использования sha-1.
KeyDerivation.Pbkdf2 позволяет получить точно такой же результат, но также позволяет использовать HMAC SHA-256 и HMAC SHA-512. Это быстрее; на моей машине примерно в 5 раз - и это хорошо для безопасности, потому что это позволяет больше раундов, что делает жизнь для крекеров сложнее (кстати, sha-512 намного менее дружелюбен к gpu, чем sha-256 или sha1). И api проще, для загрузки:
byte[] salt = ...
string password = ...
var rounds = 50000; // pick something bearable
var num_bytes_requested = 16; // 128 bits is fine
var prf = KeyDerivationPrf.HMACSHA512; // or sha256, or sha1
byte[] hashed = KeyDerivation.Pbkdf2(password, salt, prf, rounds, num_bytes_requested);
Это из пакета nuget Microsoft.AspNetCore.Cryptography.KeyDerivation, который не зависит от ядра asp.net; он работает на .net 4.5.1 или .net стандарте 1.3 или выше.
Ответ 5
Я знаю, что это старый вопрос, но для всех, кто сталкивается с ним, теперь вы можете использовать KeyDerivation.Pbkdf2 из пакета Microsoft.AspNetCore.Cryptography.KeyDerivation nuget. Это то, что используется в ядре asp.net.
К сожалению, это добавит массу ссылок, которые действительно не нужны. Вы можете просто скопировать код и вставить его в свой собственный проект (хотя теперь вам нужно будет поддерживать код cryto, который является PITA)
Ответ 6
Вы можете использовать Bouncy Castle. Спецификация С# содержит алгоритм "PBEwithHmacSHA-256", который может быть только PBKDF2 с SHA-256.
Ответ 7
Для чего это стоит, здесь копия реализации Microsoft, но с SHA-1 заменена на SHA512:
namespace System.Security.Cryptography
{
using System.Globalization;
using System.IO;
using System.Text;
[System.Runtime.InteropServices.ComVisible(true)]
public class Rfc2898DeriveBytes_HMACSHA512 : DeriveBytes
{
private byte[] m_buffer;
private byte[] m_salt;
private HMACSHA512 m_HMACSHA512; // The pseudo-random generator function used in PBKDF2
private uint m_iterations;
private uint m_block;
private int m_startIndex;
private int m_endIndex;
private static RNGCryptoServiceProvider _rng;
private static RNGCryptoServiceProvider StaticRandomNumberGenerator
{
get
{
if (_rng == null)
{
_rng = new RNGCryptoServiceProvider();
}
return _rng;
}
}
private const int BlockSize = 20;
//
// public constructors
//
public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize) : this(password, saltSize, 1000) { }
public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize, int iterations)
{
if (saltSize < 0)
throw new ArgumentOutOfRangeException("saltSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
byte[] salt = new byte[saltSize];
StaticRandomNumberGenerator.GetBytes(salt);
Salt = salt;
IterationCount = iterations;
m_HMACSHA512 = new HMACSHA512(new UTF8Encoding(false).GetBytes(password));
Initialize();
}
public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt) : this(password, salt, 1000) { }
public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt, int iterations) : this(new UTF8Encoding(false).GetBytes(password), salt, iterations) { }
public Rfc2898DeriveBytes_HMACSHA512(byte[] password, byte[] salt, int iterations)
{
Salt = salt;
IterationCount = iterations;
m_HMACSHA512 = new HMACSHA512(password);
Initialize();
}
//
// public properties
//
public int IterationCount
{
get { return (int)m_iterations; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
m_iterations = (uint)value;
Initialize();
}
}
public byte[] Salt
{
get { return (byte[])m_salt.Clone(); }
set
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length < 8)
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Cryptography_PasswordDerivedBytes_FewBytesSalt")));
m_salt = (byte[])value.Clone();
Initialize();
}
}
//
// public methods
//
public override byte[] GetBytes(int cb)
{
if (cb <= 0)
throw new ArgumentOutOfRangeException("cb", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
byte[] password = new byte[cb];
int offset = 0;
int size = m_endIndex - m_startIndex;
if (size > 0)
{
if (cb >= size)
{
Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, size);
m_startIndex = m_endIndex = 0;
offset += size;
}
else
{
Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, cb);
m_startIndex += cb;
return password;
}
}
//BCLDebug.Assert(m_startIndex == 0 && m_endIndex == 0, "Invalid start or end index in the internal buffer.");
while (offset < cb)
{
byte[] T_block = Func();
int remainder = cb - offset;
if (remainder > BlockSize)
{
Buffer.InternalBlockCopy(T_block, 0, password, offset, BlockSize);
offset += BlockSize;
}
else
{
Buffer.InternalBlockCopy(T_block, 0, password, offset, remainder);
offset += remainder;
Buffer.InternalBlockCopy(T_block, remainder, m_buffer, m_startIndex, BlockSize - remainder);
m_endIndex += (BlockSize - remainder);
return password;
}
}
return password;
}
public override void Reset()
{
Initialize();
}
private void Initialize()
{
if (m_buffer != null)
Array.Clear(m_buffer, 0, m_buffer.Length);
m_buffer = new byte[BlockSize];
m_block = 1;
m_startIndex = m_endIndex = 0;
}
internal static byte[] Int(uint i)
{
byte[] b = BitConverter.GetBytes(i);
byte[] littleEndianBytes = { b[3], b[2], b[1], b[0] };
return BitConverter.IsLittleEndian ? littleEndianBytes : b;
}
// This function is defined as follow :
// Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
// where i is the block number.
private byte[] Func()
{
byte[] INT_block = Int(m_block);
m_HMACSHA512.TransformBlock(m_salt, 0, m_salt.Length, m_salt, 0);
m_HMACSHA512.TransformFinalBlock(INT_block, 0, INT_block.Length);
byte[] temp = m_HMACSHA512.Hash;
m_HMACSHA512.Initialize();
byte[] ret = temp;
for (int i = 2; i <= m_iterations; i++)
{
temp = m_HMACSHA512.ComputeHash(temp);
for (int j = 0; j < BlockSize; j++)
{
ret[j] ^= temp[j];
}
}
// increment the block count.
m_block++;
return ret;
}
}
}
В дополнение к замене HMACSHA1
на HMACSHA512
вам нужно добавить свойство StaticRandomNumberGenerator
, потому что Utils.StaticRandomNumberGenerator
- internal
в сборке microsoft, и вам нужно добавить метод static byte[] Int(uint i)
, потому что microsoft Utils.Int
также internal
. Помимо этого, код работает.
Ответ 8
Хотя это старый вопрос, так как я добавил ссылку на этот вопрос в своем настраиваемом Rfc2898DeriveBytes, где я спросил, была ли общая реализация алгоритма Rfc2898DeriveBytes
правильный.
Теперь я тестировал и проверял, что он генерирует точные хэш-значения, если HMACSHA1
предоставляется для TAlgorithm
как реализация .NET Rfc2898DeriveBytes
Чтобы использовать класс, необходимо предоставить конструктор для алгоритма HMAC, требующего байтового массива в качестве первого аргумента.
например:
var rfcGenSha1 = new Rfc2898DeriveBytes<HMACSHA1>(b => new HMACSHA1(b), key, ...)
var rfcGenSha256 = new Rfc2898DeriveBytes<HMACSHA256>(b => new HMACSHA256(b), key, ...)
Это требует, чтобы алгоритм наследовал HMAC в этот момент, я считаю, что можно было бы уменьшить ограничение, требующее наследования от KeyedHashAlgorithm
вместо HMAC
, если конструктор алгоритма принимает массив байтов для конструктора.