Guys, I'm trying to implement a PBKDF2 function in C# that creates a WPA Shared key. I've found some here: http://msdn.microsoft.com/en-us/magazine/cc163913.aspx that seems to produce a valid result, but it's one byte too short... and the wrong PSK value.
To test the output, I am comparing it to this: http://www.xs4all.nl/~rjoris/wpapsk.html or http://anandam.name/pbkdf2/
I did find one way of getting this to work with a built in library to C# called Rfc2898DeriveBytes. Using this, I get a valid output using:
Rfc2898DeriveBytes k3 = new Rfc2898DeriveBytes(pwd1, salt1, 4096); byte[] answers = k3.GetBytes(32);
Now, the one limitation I have using Rfc2898DeriveBytes is the "salt" must be 8 octets long. If it is shorter, the Rfc2898DeriveBytes throws an exception. I was thinking all I had to do was pad the salt (if it was shorter) to 8 bytes, and I'd be good. But NO! I've tried pretty much every combination of padding with a shorter salt, but I cannot duplicate the results I get from those two websites above.
So bottom line is, does this mean the Rfc2898DeriveBytes just simply won't work with a source salt shorter than 8 bytes? If so, does anyone know of any C# code I could use that implements PBKDF2 for WPA Preshared key?
Here is an implementation that does not require the 8 byte salt.
You can calculate a WPA key as follows:
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(); } } }
I get matching results when comparing key-derivation from .NET's Rfc2898DeriveBytes and Anandam's PBKDF2 Javascript implementation.
I put together an example of packaging SlowAES and Anandam's PBKDF2 into Windows Script Components. Using this implementation shows good interop with the .NET RijndaelManaged class and the Rfc2898DeriveBytes class.
See also:
All of these go further than what you are asking for. They all show interop of the AES encryption. But to get interop on encryption, it is a necessary pre-requisite to have interop (or matching outputs) on the password-based key derivation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With