2009-06-25 6 views
22

Les gars, j'essaie d'implémenter une fonction PBKDF2 en C# qui crée une clé partagée WPA. J'en ai trouvé ici: http://msdn.microsoft.com/en-us/magazine/cc163913.aspx qui semble produire un résultat valide, mais c'est un octet trop court ... et la mauvaise valeur PSK.Implémentation de PBKDF2 en C# avec Rfc2898DeriveBytes

Pour tester la sortie, je compare à ceci: http://www.xs4all.nl/~rjoris/wpapsk.html ou http://anandam.name/pbkdf2/

J'ai trouvé un moyen d'obtenir que cela fonctionne avec une bibliothèque intégrée à C# appelé Rfc2898DeriveBytes. Avec cela, je reçois une sortie valide en utilisant:

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

Maintenant, une limitation que je l'ai en utilisant Rfc2898DeriveBytes est le « sel » doit être de 8 octets. Si elle est plus courte, le Rfc2898DeriveBytes lève une exception. Je pensais que tout ce que je devais faire était de tamponner le sel (si c'était plus court) à 8 octets, et je serais bien. Mais non! J'ai essayé à peu près toutes les combinaisons de rembourrage avec un sel plus court, mais je ne peux pas reproduire les résultats que j'ai obtenus sur ces deux sites ci-dessus.

Donc la ligne du bas est, est-ce que cela signifie que Rfc2898DeriveBytes ne fonctionnera tout simplement pas avec un sel source plus court que 8 octets? Si tel est le cas, quelqu'un connaît-il un code C# que je pourrais utiliser et qui implémente PBKDF2 pour la clé WPA Preshared?

+2

vous pouvez utiliser ceci: ([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) –

Répondre

7

J'obtiens des résultats correspondants en comparant la dérivation de clé à partir de Rfc2898DeriveBytes de .NET et l'implémentation Javascript de PBKDF2 d'Anandam.

J'ai mis ensemble an example de l'emballage SlowAES et Anandam PBKDF2 dans Windows Script Components. L'utilisation de cette implémentation montre un bon intérop avec la classe .NET RijndaelManaged et la classe Rfc2898DeriveBytes.

Voir aussi:

Tous ces aller plus loin que ce que vous demandez. Ils montrent tous l'interopérabilité du cryptage AES. Mais pour obtenir l'interopérabilité sur le cryptage, il est nécessaire d'avoir interop (ou des sorties correspondantes) sur la dérivation de la clé basée sur le mot de passe.

15

Voici une implémentation qui ne nécessite pas le sel de 8 octets.

Vous pouvez calculer une clé WPA comme suit:

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

Axé sur le lien Microsoft, j'ai fait quelques changements afin de rendre la PMK les mêmes que ceux découverts dans les liens que vous mettez en avant.

Modifier l'algorithme SHA à partir de SHA256Managed à SHA1Managed pour le hachage interne et externe.

Changement HASH_SIZE_IN_BYTES à l'égalité 20 plutôt que 34.

Cela produit la clé WPA correcte.

Je sais que c'est un peu tard, mais je viens tout juste de commencer à chercher ce genre d'information et j'ai pensé pouvoir aider les autres. Si quelqu'un lit ce post, des idées sur la fonction PRF et comment le faire en C#?

+0

Je ne recommanderais pas d'utiliser l'algorithme SHA-1 si vous avez le choix. Il a été prouvé que [vulnérable] (http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html). Certes, l'attaque nécessite encore beaucoup de calculs, mais il sera possible de cracker les hachages SHA-1 beaucoup plus tôt que prévu par la NSA. –

+2

@ Sam - cet article date de 2005 et la sécurité a beaucoup bougé depuis. SHA1 est vulnérable non pas parce qu'il peut être inversé ou parce que le nombre de collisions est trop élevé mais parce qu'il est _fast_, rendant les attaques par force brute trop faciles avec le traitement en nuage moderne. SHA256 n'est pas beaucoup plus lent et est donc assez vulnérable. Les algorithmes d'étirement de clé comme PBKDF2/RFC2898 prennent le hachage comme SHA et le répètent des milliers de fois pour qu'il soit léger, ce qui rend toute attaque par force brute beaucoup plus difficile. La différence entre SHA1 et SHA256 est loin d'être aussi significative dans ce contexte. – Keith

+0

@Keith Ah oui d'accord, mais il serait toujours préférable d'éviter SHA1 si vous avez le choix. –

3

Cela élargit la réponse de Dodgyrabbit et son code a aidé à résoudre le mien comme je l'ai développé. Cette classe générique peut utiliser n'importe quelle classe dérivée de HMAC en C#. C'est .NET 4 en raison des paramètres avec des valeurs par défaut, mais si ceux-ci ont été modifiés, alors devrait fonctionner vers .NET 2, mais je n'ai pas testé cela. À UTILISER À VOS RISQUES ET PÉRILS.

J'ai également posté ceci sur mon blog, The Albequerque Left Turn, aujourd'hui.

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 pour le commentaire: LES FONCTIONS HMAC SONT CLES PAR LE MOT DE PASSE! JAMAIS LE SEL! M'a sauvé un mal de tête énorme! – absentmindeduk

Questions connexes