Implementación de PBKDF2 en C#con Rfc2898DeriveBytes
(4)
Aquí hay una implementación que no requiere la sal de 8 bytes.
Puede calcular una clave WPA de la siguiente manera:
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(passphrase, Encoding.UTF8.GetBytes(name), 4096);
key = rfc2898.GetBytes(32);
public class Rfc2898DeriveBytes : DeriveBytes
{
const int BlockSize = 20;
uint block;
byte[] buffer;
int endIndex;
readonly HMACSHA1 hmacsha1;
uint iterations;
byte[] salt;
int startIndex;
public Rfc2898DeriveBytes(string password, int saltSize)
: this(password, saltSize, 1000)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt)
: this(password, salt, 1000)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
{
if (saltSize < 0)
{
throw new ArgumentOutOfRangeException("saltSize");
}
byte[] data = new byte[saltSize];
new RNGCryptoServiceProvider().GetBytes(data);
Salt = data;
IterationCount = iterations;
hmacsha1 = new HMACSHA1(new UTF8Encoding(false).GetBytes(password));
Initialize();
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations) : this(new UTF8Encoding(false).GetBytes(password), salt, iterations)
{
}
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations)
{
Salt = salt;
IterationCount = iterations;
hmacsha1 = new HMACSHA1(password);
Initialize();
}
static byte[] Int(uint i)
{
byte[] bytes = BitConverter.GetBytes(i);
byte[] buffer2 = new byte[] {bytes[3], bytes[2], bytes[1], bytes[0]};
if (!BitConverter.IsLittleEndian)
{
return bytes;
}
return buffer2;
}
byte[] DeriveKey()
{
byte[] inputBuffer = Int(block);
hmacsha1.TransformBlock(salt, 0, salt.Length, salt, 0);
hmacsha1.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
byte[] hash = hmacsha1.Hash;
hmacsha1.Initialize();
byte[] buffer3 = hash;
for (int i = 2; i <= iterations; i++)
{
hash = hmacsha1.ComputeHash(hash);
for (int j = 0; j < BlockSize; j++)
{
buffer3[j] = (byte) (buffer3[j] ^ hash[j]);
}
}
block++;
return buffer3;
}
public override byte[] GetBytes(int bytesToGet)
{
if (bytesToGet <= 0)
{
throw new ArgumentOutOfRangeException("bytesToGet");
}
byte[] dst = new byte[bytesToGet];
int dstOffset = 0;
int count = endIndex - startIndex;
if (count > 0)
{
if (bytesToGet < count)
{
Buffer.BlockCopy(buffer, startIndex, dst, 0, bytesToGet);
startIndex += bytesToGet;
return dst;
}
Buffer.BlockCopy(buffer, startIndex, dst, 0, count);
startIndex = endIndex = 0;
dstOffset += count;
}
while (dstOffset < bytesToGet)
{
byte[] src = DeriveKey();
int num3 = bytesToGet - dstOffset;
if (num3 > BlockSize)
{
Buffer.BlockCopy(src, 0, dst, dstOffset, BlockSize);
dstOffset += BlockSize;
}
else
{
Buffer.BlockCopy(src, 0, dst, dstOffset, num3);
dstOffset += num3;
Buffer.BlockCopy(src, num3, buffer, startIndex, BlockSize - num3);
endIndex += BlockSize - num3;
return dst;
}
}
return dst;
}
void Initialize()
{
if (buffer != null)
{
Array.Clear(buffer, 0, buffer.Length);
}
buffer = new byte[BlockSize];
block = 1;
startIndex = endIndex = 0;
}
public override void Reset()
{
Initialize();
}
public int IterationCount
{
get
{
return (int) iterations;
}
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException("value");
}
iterations = (uint) value;
Initialize();
}
}
public byte[] Salt
{
get
{
return (byte[]) salt.Clone();
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
salt = (byte[]) value.Clone();
Initialize();
}
}
}
Chicos, estoy tratando de implementar una función PBKDF2 en C # que crea una clave compartida WPA. He encontrado algunos aquí: http://msdn.microsoft.com/en-us/magazine/cc163913.aspx que parece producir un resultado válido, pero es un byte demasiado corto ... y el valor PSK incorrecto.
Para probar el resultado, lo comparo con esto: http://www.xs4all.nl/~rjoris/wpapsk.html o http://anandam.name/pbkdf2/
Encontré una forma de hacer que esto funcionara con una biblioteca incorporada a C # llamada Rfc2898DeriveBytes. Usando esto, obtengo una salida válida usando:
Rfc2898DeriveBytes k3 = new Rfc2898DeriveBytes(pwd1, salt1, 4096);
byte[] answers = k3.GetBytes(32);
Ahora, la única limitación que tengo usando Rfc2898DeriveBytes es que la "sal" debe tener 8 octetos de longitud. Si es más corto, el Rfc2898DeriveBytes lanza una excepción. Estaba pensando que todo lo que tenía que hacer era rellenar la sal (si era más corta) a 8 bytes, y estaría bien. ¡Pero no! He intentado casi todas las combinaciones de relleno con una sal más corta, pero no puedo duplicar los resultados que obtengo de los dos sitios web anteriores.
Entonces, la conclusión es que, ¿esto significa que los Rfc2898DeriveBytes simplemente no funcionarán con una fuente de sal más corta que 8 bytes? Si es así, ¿alguien sabe de algún código C # que pueda usar que implemente PBKDF2 para la clave WPA Preshared?
Esto amplía la respuesta de Dodgyrabbit y su código me ayudó a corregir el mío mientras lo desarrollaba. Esta clase genérica puede usar cualquier clase derivada de HMAC en C #. Esto es .NET 4 debido a los parámetros con valores predeterminados, pero si se modificaron, entonces debería funcionar con .NET 2, pero no lo he probado. ÚSELO BAJO SU PROPIO RIESGO.
También he publicado esto en mi blog, El giro a la izquierda de Albequerque , hoy.
using System;
using System.Text;
using System.Security.Cryptography;
namespace System.Security.Cryptography
{
//Generic PBKDF2 Class that can use any HMAC algorithm derived from the
// System.Security.Cryptography.HMAC abstract class
// PER SPEC RFC2898 with help from user Dodgyrabbit on StackExchange
// http://.com/questions/1046599/pbkdf2-implementation-in-c-sharp-with-rfc2898derivebytes
// the use of default values for parameters in the functions puts this at .NET 4
// if you remove those defaults and create the required constructors, you should be able to drop to .NET 2
// USE AT YOUR OWN RISK! I HAVE TESTED THIS AGAINST PUBLIC TEST VECTORS, BUT YOU SHOULD
// HAVE YOUR CODE PEER-REVIEWED AND SHOULD FOLLOW BEST PRACTICES WHEN USING CRYPTO-ANYTHING!
// NO WARRANTY IMPLIED OR EXPRESSED, YOU ARE ON YOUR OWN!
// PUBLIC DOMAIN! NO COPYRIGHT INTENDED OR RESERVED!
//constrain T to be any class that derives from HMAC, and that exposes a new() constructor
public class PBKDF2<T>: DeriveBytes where T : HMAC, new()
{
//Internal variables and public properties
private int _blockSize = -1; // the byte width of the output of the HMAC algorithm
byte[] _P = null;
int _C = 0;
private T _hmac;
byte[] _S = null;
// if you called the initializer/constructor specifying a salt size,
// you will need this property to GET the salt after it was created from the crypto rng!
// GET THIS BEFORE CALLING GETBYTES()! OBJECT WILL BE RESET AFTER GETBYTES() AND
// SALT WILL BE LOST!!
public byte[] Salt { get { return (byte[])_S.Clone(); } }
// Constructors
public PBKDF2(string Password, byte[] Salt, int IterationCount = 1000)
{ Initialize(Password, Salt, IterationCount); }
public PBKDF2(byte[] Password, byte[] Salt, int IterationCount = 1000)
{ Initialize(Password, Salt, IterationCount); }
public PBKDF2(string Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{ Initialize(Password, SizeOfSaltInBytes, IterationCount);}
public PBKDF2(byte[] Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{ Initialize(Password, SizeOfSaltInBytes, IterationCount);}
//All Construtors call the corresponding Initialize methods
public void Initialize(string Password, byte[] Salt, int IterationCount = 1000)
{
if (string.IsNullOrWhiteSpace(Password))
throw new ArgumentException("Password must contain meaningful characters and not be null.", "Password");
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
Initialize(new UTF8Encoding(false).GetBytes(Password), Salt, IterationCount);
}
public void Initialize(byte[] Password, byte[] Salt, int IterationCount = 1000)
{
//all Constructors/Initializers eventually lead to this one which does all the "important" work
if (Password == null || Password.Length == 0)
throw new ArgumentException("Password cannot be null or empty.", "Password");
if (Salt == null)
Salt = new byte[0];
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
_P = (byte[])Password.Clone();
_S = (byte[])Salt.Clone();
_C = IterationCount;
//determine _blockSize
_hmac = new T();
_hmac.Key = new byte[] { 0 };
byte[] test = _hmac.ComputeHash(new byte[] { 0 });
_blockSize = test.Length;
}
public void Initialize(string Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{
if (string.IsNullOrWhiteSpace(Password))
throw new ArgumentException("Password must contain meaningful characters and not be null.", "Password");
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
Initialize(new UTF8Encoding(false).GetBytes(Password), SizeOfSaltInBytes, IterationCount);
}
public void Initialize(byte[] Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{
if (Password == null || Password.Length == 0)
throw new ArgumentException("Password cannot be null or empty.", "Password");
if (SizeOfSaltInBytes < 0)
throw new ArgumentOutOfRangeException("SizeOfSaltInBytes");
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
// You didn''t specify a salt, so I''m going to create one for you of the specific byte length
byte[] data = new byte[SizeOfSaltInBytes];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(data);
// and then finish initializing...
// Get the salt from the Salt parameter BEFORE calling GetBytes()!!!!!!!!!!!
Initialize(Password, data, IterationCount);
}
~PBKDF2()
{
//*DOOT* clean up in aisle 5! *KEKERKCRACKLE*
this.Reset();
}
// required by the Derive Bytes class/interface
// this is where you request your output bytes after Initialize
// state of class Reset after use!
public override byte[] GetBytes(int ByteCount)
{
if (_S == null || _P == null)
throw new InvalidOperationException("Object not Initialized!");
if (ByteCount < 1)// || ByteCount > uint.MaxValue * blockSize)
throw new ArgumentOutOfRangeException("ByteCount");
int totalBlocks = (int)Math.Ceiling((decimal)ByteCount / _blockSize);
int partialBlock = (int)(ByteCount % _blockSize);
byte[] result = new byte[ByteCount];
byte[] buffer = null;
// I''m using TT here instead of T from the spec because I don''t want to confuse it with
// the generic object T
for (int TT = 1; TT <= totalBlocks; TT++)
{
// run the F function with the _C number of iterations for block number TT
buffer = _F((uint)TT);
//IF we''re not at the last block requested
//OR the last block requested is whole (not partial)
// then take everything from the result of F for this block number TT
//ELSE only take the needed bytes from F
if (TT != totalBlocks || (TT == totalBlocks && partialBlock == 0))
Buffer.BlockCopy(buffer, 0, result, _blockSize * (TT - 1), _blockSize);
else
Buffer.BlockCopy(buffer, 0, result, _blockSize * (TT - 1), partialBlock);
}
this.Reset(); // force cleanup after every use! Cannot be reused!
return result;
}
// required by the Derive Bytes class/interface
public override void Reset()
{
_C = 0;
_P.Initialize(); // the compiler might optimize this line out! :(
_P = null;
_S.Initialize(); // the compiler might optimize this line out! :(
_S = null;
if (_hmac != null)
_hmac.Clear();
_blockSize = -1;
}
// the core function of the PBKDF which does all the iterations
// per the spec section 5.2 step 3
private byte[] _F(uint I)
{
//NOTE: SPEC IS MISLEADING!!!
//THE HMAC FUNCTIONS ARE KEYED BY THE PASSWORD! NEVER THE SALT!
byte[] bufferU = null;
byte[] bufferOut = null;
byte[] _int = PBKDF2<T>.IntToBytes(I);
_hmac = new T();
_hmac.Key = (_P); // KEY BY THE PASSWORD!
_hmac.TransformBlock(_S, 0, _S.Length, _S, 0);
_hmac.TransformFinalBlock(_int, 0, _int.Length);
bufferU = _hmac.Hash;
bufferOut = (byte[])bufferU.Clone();
for (int c = 1; c < _C; c++)
{
_hmac.Initialize();
_hmac.Key = _P; // KEY BY THE PASSWORD!
bufferU = _hmac.ComputeHash(bufferU);
_Xor(ref bufferOut, bufferU);
}
return bufferOut;
}
// XOR one array of bytes into another (which is passed by reference)
// this is the equiv of data ^= newData;
private void _Xor(ref byte[] data, byte[] newData)
{
for (int i = data.GetLowerBound(0); i <= data.GetUpperBound(0); i++)
data[i] ^= newData[i];
}
// convert an unsigned int into an array of bytes BIG ENDIEN
// per the spec section 5.2 step 3
static internal byte[] IntToBytes(uint i)
{
byte[] bytes = BitConverter.GetBytes(i);
if (!BitConverter.IsLittleEndian)
{
return bytes;
}
else
{
Array.Reverse(bytes);
return bytes;
}
}
}
}
Mirando el enlace de Microsoft, hice algunos cambios para hacer que el PMK sea el mismo que se descubrió en los enlaces que usted presentó.
Cambie el algoritmo SHA de SHA256Managed a SHA1Managed para el hash interno y externo.
Cambie HASH_SIZE_IN_BYTES para igualar 20 en lugar de 34.
Esto produce la clave WPA correcta.
Sé que es un poco tarde, pero acabo de empezar a buscar este tipo de información y pensé que podría ayudar a otros. Si alguien lee esta publicación, ¿alguna idea sobre la función PRF y cómo hacerlo dentro de C #?
Obtengo resultados coincidentes al comparar la derivación de clave de Rfc2898DeriveBytes de .NET y la implementación de Javascript PBKDF2 de Anandam.
Reuní un ejemplo de empaquetamiento de SlowAES y PBKDF2 de Anandam en los componentes de Windows Script. El uso de esta implementación muestra una buena interoperabilidad con la clase .NET RijndaelManaged y la clase Rfc2898DeriveBytes.
Ver también:
Todo esto va más allá de lo que estás pidiendo. Todos ellos muestran la interoperabilidad del cifrado AES. Pero para obtener interoperabilidad en el cifrado, es un requisito previo necesario tener interoperabilidad (o salidas coincidentes) en la derivación de clave basada en contraseña.