2009-06-25 9 views
22

Ragazzi, sto cercando di implementare una funzione PBKDF2 in C# che crei una chiave condivisa WPA. Ho trovato alcuni qui: http://msdn.microsoft.com/en-us/magazine/cc163913.aspx che sembra produrre un risultato valido, ma è un byte troppo corto ... e il valore PSK sbagliato.Implementazione PBKDF2 in C# con Rfc2898DeriveBytes

per provare l'uscita, mi sto paragonando a questo: http://www.xs4all.nl/~rjoris/wpapsk.html o http://anandam.name/pbkdf2/

Ho trovato un modo di ottenere questo al lavoro con un costruito in libreria per C# chiamato Rfc2898DeriveBytes. Usando questo, ho un'uscita valida utilizzando:

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

Ora, quella limitazione che ho utilizzando Rfc2898DeriveBytes è il "sale" deve essere di 8 ottetti lungo. Se è più breve, Rfc2898DeriveBytes genera un'eccezione. Stavo pensando che tutto quello che dovevo fare era tamponare il sale (se era più corto) a 8 byte, e sarei stato buono. Ma no! Ho provato praticamente tutte le combinazioni di padding con un sale più breve, ma non riesco a duplicare i risultati che ottengo da quei due siti sopra.

Quindi la linea di fondo è, vuol dire che Rfc2898DeriveBytes semplicemente non funzionerà con una sorgente salata inferiore a 8 byte? In tal caso, qualcuno sa di un codice C# che potrei usare che implementa PBKDF2 per la chiave condivisa WPA?

+2

è possibile utilizzare questo: [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) –

risposta

7

Ottengo risultati corrispondenti confrontando la derivazione della chiave da Rfc2898DeriveBytes di .NET e l'implementazione Javascript PBKDF2 di Anandam.

Ho assemblato an example della confezione SlowAES e PBKDF2 di Anandam in Componenti script di Windows. L'utilizzo di questa implementazione mostra una buona interoperabilità con la classe .NET RijndaelManaged e la classe Rfc2898DeriveBytes.

Consulta anche:

Tutti questi vanno oltre ciò che si sta chiedendo. Tutti mostrano l'interoperabilità della crittografia AES. Ma per ottenere l'interoperabilità con la crittografia, è necessario pre-requisito avere interop (o corrispondenze di output) sulla derivazione della chiave basata su password.

15

Ecco un'implementazione che non richiede il sale da 8 byte.

Si può calcolare una chiave WPA come segue:

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

Guardando il collegamento di Microsoft, ho fatto alcune modifiche al fine di rendere il PMK gli stessi di quelli scoperti nel link che hai messo in avanti.

Modificare l'algoritmo SHA da SHA256Gestito a SHA1Gestito per l'hash interno ed esterno.

Change HASH_SIZE_IN_BYTES a parità 20 invece che 34.

Ciò produce la chiave WPA corretta.

So che è un po 'tardi, ma ho appena iniziato a cercare questo tipo di informazioni e ho pensato di poter aiutare gli altri. Se qualcuno legge questo post, qualche idea sulla funzione PRF e come farlo all'interno di C#?

+0

Non consiglierei di usare l'algoritmo SHA-1 se hai la scelta. È stato dimostrato come [vulnerabile] (http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html). Ammettiamo che l'attacco richieda ancora molti calcoli ma sarà fattibile per decifrare gli hash SHA-1 molto prima che l'NSA intendesse. –

+2

@ Sam - quell'articolo è del 2005 e da allora la sicurezza si è spostata molto. SHA1 è vulnerabile non perché può essere invertito o perché il numero di collisioni è troppo alto, ma perché è _fast_, rendendo gli attacchi di forza bruta troppo facili con l'elaborazione moderna del cloud. SHA256 non è molto più lento e quindi è più vulnerabile. Gli algoritmi per l'allungamento della chiave come PBKDF2/RFC2898 prendono l'hash come SHA e lo ripetono migliaia di volte in modo che sia _slow_, rendendo qualsiasi attacco di forza bruta molto più difficile. La differenza tra SHA1 e SHA256 non è neanche lontanamente significativa in questo contesto. – Keith

+0

@Keith Ah sì, d'accordo, ma sarebbe comunque meglio evitare SHA1 se si ha la scelta. –

3

Questo amplia la risposta di Dodgyrabbit e il suo codice ha aiutato a sistemare il mio mentre lo sviluppavo. Questa classe generica può utilizzare qualsiasi classe derivata da HMAC in C#. Si tratta di .NET 4 a causa dei parametri con valori predefiniti, ma se questi sono stati modificati, questo dovrebbe essere in .NET 2, ma non l'ho verificato. UTILIZZARE A PROPRIO RISCHIO.

Ho anche postato questo sul mio blog, The Albequerque Left Turn, oggi.

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 per il commento: LE FUNZIONI HMAC SONO CHIAVE DALLA PASSWORD! MAI IL SALE! Mi ha salvato un forte mal di testa! – absentmindeduk