使用Rfc2898DeriveBytes在C#中实现PBKDF2

伙计们,我正在尝试在C#中实现一个创建WPA共享密钥的PBKDF2函数。 我在这里找到了一些: http : //msdn.microsoft.com/en-us/magazine/cc163913.aspx似乎产生了一个有效的结果,但是它的一个字节太短了……而且错误的PSK值。

为了测试输出,我将其与此进行比较: http : //www.xs4all.nl/~rjoris/wpapsk.html或http://anandam.name/pbkdf2/

我确实找到了一种方法来使用C#内置库来调用Rfc2898DeriveBytes。 使用这个,我得到一个有效的输出:

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

现在,我使用Rfc2898DeriveBytes的一个限制是“盐”必须是8个八位字节长。 如果它更短,则Rfc2898DeriveBytes会抛出exception。 我想我所要做的就是将盐(如果它更短)填充到8个字节,我会很好。 但不是! 我已经尝试了几乎所有填充与较短盐的组合,但我不能复制我从上面这两个网站得到的结果。

那么底线是,这是否意味着Rfc2898DeriveBytes只是不能使用短于8字节的源盐? 如果是这样,有没有人知道我可以使用哪些C#代码为WPA预共享密钥实现PBKDF2?

这是一个不需要8字节盐的实现。

您可以按如下方式计算WPA密钥:

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

在比较.NET的Rfc2898DeriveBytes和Anandam的PBKDF2 Javascript实现的密钥派生时,我得到了匹配的结果。

我把一个将SlowAES和Anandam的PBKDF2打包成Windows脚本组件的例子 。 使用此实现显示与.NET RijndaelManaged类和Rfc2898DeriveBytes类的良好互操作。

也可以看看:

  • 在JavaScript中的AES
  • 让SlowAES和RijndaelManaged一起玩

所有这些都比你要求的更进一步。 它们都显示AES加密的互操作性。 但是为了获得加密的互操作性,在基于密码的密钥推导上具有互操作(或匹配输出)是必要的先决条件。

查看Microsoft链接,我进行了一些更改,以使PMK与您提供的链接中发现的PMK相同。

对于内部和外部哈希,将SHA算法从SHA256Managed更改为SHA1Managed。

将HASH_SIZE_IN_BYTES更改为等于20而不是34。

这会生成正确的WPA密钥。

我知道这有点晚了,但我刚刚开始寻找这种信息,并认为我可以帮助其他人。 如果有人读过这篇文章,关于PRFfunction的任何想法以及如何在C#中做到这一点?

这扩展了Dodgyrabbit的答案,他的代码帮我修复了我的开发。 此generics类可以在C#中使用任何HMAC派生类。 这是.NET 4,因为参数具有默认值,但如果这些参数已更改,那么这应该适用于.NET 2,但我没有测试过。 自行承担使用风险。

我今天也在我的博客The Albequerque Left Turn上发布了这个post 。

 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: 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.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; } } } }