2009-06-25 35 views
22

Các bạn, tôi đang cố triển khai hàm PBKDF2 trong C# để tạo khóa chia sẻ WPA. Tôi đã tìm thấy một số ở đây: http://msdn.microsoft.com/en-us/magazine/cc163913.aspx mà dường như để tạo ra một kết quả hợp lệ, nhưng đó là một byte quá ngắn ... và giá trị PSK sai.Thực hiện PBKDF2 trong C# với Rfc2898DeriveBytes

Để kiểm tra đầu ra, tôi đang so sánh nó như thế này: http://www.xs4all.nl/~rjoris/wpapsk.html hoặc http://anandam.name/pbkdf2/

Tôi đã tìm thấy một cách để nhận này để làm việc với một xây dựng trong thư viện C# gọi Rfc2898DeriveBytes. Sử dụng điều này, tôi nhận được kết quả hợp lệ bằng cách sử dụng:

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

Bây giờ, một giới hạn tôi đã sử dụng Rfc2898DeriveBytes là "muối" phải dài 8 octet. Nếu nó ngắn hơn, Rfc2898DeriveBytes ném một ngoại lệ. Tôi đã nghĩ tất cả những gì tôi phải làm là đổ muối (nếu nó ngắn hơn) đến 8 byte, và tôi sẽ tốt. Nhưng không! Tôi đã cố gắng khá nhiều mọi sự kết hợp của padding với một muối ngắn hơn, nhưng tôi không thể lặp lại kết quả tôi nhận được từ hai trang web trên.

Vì vậy, dòng dưới cùng là, điều này có nghĩa là Rfc2898DeriveBytes chỉ đơn giản là sẽ không hoạt động với một nguồn muối ngắn hơn 8 byte? Nếu vậy, không ai biết về bất kỳ mã C# tôi có thể sử dụng mà thực hiện PBKDF2 cho WPA Preshared chính?

+2

bạn có thể sử dụng này: [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) –

Trả lời

7

Tôi nhận được kết quả phù hợp khi so sánh khóa phát sinh từ Rfc2898DeriveBytes của .NET và triển khai Javascript PBKDF2 của Anandam.

Tôi đặt cùng nhau an example bao bì SlowAES và PBKDF2 của Anandam vào Windows Script Components. Sử dụng triển khai này cho thấy sự tương tác tốt với lớp .NET RijndaelManaged và lớp Rfc2898DeriveBytes.

Xem thêm:

Tất cả những đi xa hơn so với những gì bạn đang yêu cầu. Tất cả đều hiển thị interop của mã hóa AES. Nhưng để có được interop về mã hóa, nó là một điều kiện tiên quyết cần thiết để có interop (hoặc kết quả đầu ra phù hợp) trên dẫn xuất khóa dựa trên mật khẩu.

15

Đây là triển khai không yêu cầu muối 8 byte.

Bạn có thể tính toán một chìa khóa WPA như sau:

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

Nhìn vào liên kết Microsoft, tôi thực hiện một số thay đổi để làm cho PMK giống như những phát hiện trong các liên kết bạn đưa ra.

Thay đổi thuật toán SHA từ SHA256Quản lý thành SHA1Quản lý cho băm bên trong và bên ngoài.

Thay đổi HASH_SIZE_IN_BYTES thành 20 thay vì 34.

Điều này tạo khóa WPA chính xác.

Tôi biết đã đến trễ một chút, nhưng tôi chỉ mới bắt đầu tìm kiếm loại thông tin này và nghĩ rằng tôi có thể giúp đỡ người khác. Nếu có ai đọc bài này, bất kỳ ý tưởng nào về chức năng PRF và cách thực hiện nó trong C#?

+0

Tôi sẽ không khuyên bạn sử dụng thuật toán SHA-1 nếu bạn có sự lựa chọn. Nó đã được chứng minh là [dễ bị tổn thương] (http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html). Phải thừa nhận rằng cuộc tấn công vẫn đòi hỏi rất nhiều tính toán nhưng nó sẽ khả thi để crack các băm SHA-1 sớm hơn nhiều mà NSA dự định. –

+2

@Sam - bài viết đó là từ năm 2005 và tính bảo mật đã di chuyển rất nhiều kể từ đó. SHA1 dễ bị tấn công không phải vì nó có thể đảo ngược hoặc vì số va chạm quá cao nhưng vì nó là _fast_, khiến cho các cuộc tấn công bạo lực quá dễ dàng với xử lý đám mây hiện đại. SHA256 không chậm hơn nhiều và rất dễ bị tổn thương. Các thuật toán kéo dài khóa như PBKDF2/RFC2898 lấy hàm băm như SHA và lặp lại hàng nghìn lần sao cho nó là _slow_, khiến cho bất kỳ tấn công bạo lực nào mạnh hơn. Sự khác biệt giữa SHA1 và SHA256 là hư không gần như là quan trọng trong bối cảnh đó. – Keith

+0

@Keith Ah yes đồng ý, nhưng tốt nhất nên tránh SHA1 nếu bạn có lựa chọn. –

3

Điều này mở rộng câu trả lời của Dodgyrabbit và mã của anh ấy đã giúp sửa lỗi của tôi khi tôi phát triển điều này. Lớp chung này có thể sử dụng bất kỳ lớp dẫn xuất HMAC nào trong C#. Đây là .NET 4 vì các tham số có giá trị mặc định, nhưng nếu những thay đổi đó được thay đổi thì sẽ hoạt động xuống .NET 2, nhưng tôi chưa thử nghiệm điều đó. SỬ DỤNG CÓ NGUY CƠ CỦA RIÊNG BẠN.

Tôi cũng đã đăng nội dung này trên blog của mình, The Albequerque Left Turn, hôm nay.

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 cho nhận xét: CÁC CHỨC NĂNG HMAC ĐƯỢC TỪ KHÓA THEO MẬT KHẨU! KHÔNG BAO GIỜ SALT! Đã cứu tôi một cơn đau đầu lớn! – absentmindeduk