2009-06-25 24 views
22

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.Implementación PBKDF2 en C# con Rfc2898DeriveBytes

Para probar la salida, lo estoy comparando a esto: http://www.xs4all.nl/~rjoris/wpapsk.html o http://anandam.name/pbkdf2/

Lo que encontrar una forma de conseguir que esto funcione con un sistema incorporado en la biblioteca para llamadas Rfc2898DeriveBytes C#. El uso de este, me sale una salida válida usando:

Rfc2898DeriveBytes k3 = new Rfc2898DeriveBytes(pwd1, salt1, 4096); 
byte[] answers = k3.GetBytes(32); 

Ahora, la única limitación que he usando Rfc2898DeriveBytes es la "sal" debe ser de 8 octetos de largo. Si es más corto, Rfc2898DeriveBytes lanza una excepción. Estaba pensando que todo lo que tenía que hacer era rellenar la sal (si fuera más corta) con 8 bytes, y estaría bien. ¡Pero no! Intenté casi todas las combinaciones de relleno con una sal más corta, pero no puedo duplicar los resultados que obtuve de esos dos sitios web anteriores.

El resultado final es, ¿significa esto que el Rfc2898DeriveBytes simplemente no funcionará con una sal de origen inferior a 8 bytes? Si es así, ¿alguien sabe de algún código de C# que pueda usar que implemente PBKDF2 para la clave de WPA compartido previamente?

+2

podría utilizar este: [http://msdn.microsoft.com/en-us/library/system.web.helpers.crypto.hashpassword%28v=vs.99%29.aspx] (http://msdn.microsoft.com/en-us/library/system.web.helpers.crypto.hashpassword%28v=vs.99%29.aspx) –

Respuesta

7

Obtengo resultados que coinciden al comparar la derivación de claves de .NET Rfc2898DeriveBytes y la implementación de Anandam's PBKDF2 Javascript.

Reuní an example del embalaje SlowAES y el PBKDF2 de Anandam en Windows Script Components. El uso de esta implementación muestra una buena interoperabilidad con la clase .NET RijndaelManaged y la clase Rfc2898DeriveBytes.

Consulte también:

Todos estos van más allá de lo que está pidiendo. Todos muestran interoperabilidad del cifrado AES. Pero para obtener interoperabilidad en el cifrado, es un prerrequisito necesario tener interoperabilidad (o salidas coincidentes) en la derivación de clave basada en contraseña.

15

Aquí hay una implementación que no requiere la sal de 8 bytes.

Se 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(); 
      } 
     } 
    } 
6

Mirando el enlace de Microsoft, he hecho algunos cambios con el fin de hacer que el PMK los mismos que los descubiertos en los enlaces que pones adelante.

Cambie el algoritmo SHA de SHA256Administrado a SHA1Ganjado para el hash interno y externo.

Cambio HASH_SIZE_IN_BYTES a la igualdad de 20 en lugar de 34.

Esto produce la clave WPA correcta.

Sé que es un poco tarde, pero acabo de comenzar 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#?

+0

No recomendaría usar el algoritmo SHA-1 si tiene la elección. Ha sido probado como [vulnerable] (http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html). Es cierto que el ataque aún requiere una gran cantidad de cálculos, pero será posible descifrar los hashes SHA-1 mucho antes de lo previsto por la NSA. –

+2

@Sam: ese artículo es de 2005 y la seguridad se ha movido mucho desde entonces. SHA1 es vulnerable no porque se pueda revertir o porque el número de colisiones sea demasiado alto, sino porque es demasiado rápido, lo que hace que los ataques de fuerza bruta sean demasiado fáciles con el moderno procesamiento en la nube. SHA256 no es mucho más lento y es tan vulnerable. Los algoritmos Key-stretching como PBKDF2/RFC2898 toman hash como SHA y lo repiten miles de veces para que sea menos intenso, haciendo que cualquier ataque de fuerza bruta sea mucho más difícil. La diferencia entre SHA1 y SHA256 no es para nada significativa en ese contexto. – Keith

+0

@ Keith Ah sí estuvo de acuerdo, pero aún así sería mejor evitar SHA1 si tiene la opción. –

3

Esto amplía la respuesta de Dodgyrabbit y su código ayudó a reparar el mío a medida que desarrollaba esto. 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 cambiaron entonces este debería trabajar hasta .NET 2, pero no lo he probado. ÚSELO BAJO SU PROPIO RIESGO.

También he publicado esto en mi blog, The Albequerque Left Turn, 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://stackoverflow.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; 
      } 
     } 
    } 
} 
+1

+1 para el comentario: ¡LAS FUNCIONES HMAC ESTÁN CLAVES POR LA CONTRASEÑA! NUNCA LA SAL! Me salvó un dolor de cabeza masivo! – absentmindeduk