使用Rfc2898DeriveBytes在C#中实现PBKDF2
伙计们,我正在尝试在C#中实现一个PBKDF2函数,它创建一个WPA共享密钥。我在这里发现了一些:这似乎产生了一个有效的结果,但它太短了一个字节。。。和错误的PSK值 为了测试输出,我将其与以下内容进行比较:或 我确实找到了一种方法,让它与C#的内置库一起工作,名为Rfc2898DeriveBytes。使用此选项,我可以通过以下方式获得有效输出:使用Rfc2898DeriveBytes在C#中实现PBKDF2,c#,pbkdf2,rfc2898,C#,Pbkdf2,Rfc2898,伙计们,我正在尝试在C#中实现一个PBKDF2函数,它创建一个WPA共享密钥。我在这里发现了一些:这似乎产生了一个有效的结果,但它太短了一个字节。。。和错误的PSK值 为了测试输出,我将其与以下内容进行比较:或 我确实找到了一种方法,让它与C#的内置库一起工作,名为Rfc2898DeriveBytes。使用此选项,我可以通过以下方式获得有效输出: Rfc2898DeriveBytes k3 = new Rfc2898DeriveBytes(pwd1, salt1, 4096); byte[] a
Rfc2898DeriveBytes k3 = new Rfc2898DeriveBytes(pwd1, salt1, 4096);
byte[] answers = k3.GetBytes(32);
现在,我使用Rfc2898DeriveBytes的一个限制是“salt”必须是8个八位字节长。如果较短,Rfc2898DeriveBytes将引发异常。我在想,我所要做的就是把salt(如果它更短的话)填充到8个字节,这样我就很好了。但是没有!我几乎尝试过每一种填充物与短盐的组合,但我无法复制我从上述两个网站获得的结果
因此,底线是,这是否意味着Rfc2898DeriveBytes无法处理短于8字节的源代码?如果是这样,是否有人知道我可以使用任何C代码来实现WPA预共享密钥的PBKDF2?当比较.NET的Rfc2898DeriveBytes和Anandam的PBKDF2 Javascript实现的密钥派生时,我会得到匹配的结果 我将SlowAES和Anandam的PBKDF2打包到Windows脚本组件中。使用此实现可以显示与.NET RijndaelManaged类和Rfc2898DeriveBytes类的良好互操作性 另见:
所有这些都超出了你的要求。它们都显示了AES加密的互操作性。但要在加密上实现互操作,必须先对基于密码的密钥派生进行互操作(或匹配输出) 这里是一个不需要8字节salt的实现 您可以按如下方式计算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();
}
}
}
Rfc2898DeriveBytes rfc2898=新的Rfc2898DeriveBytes(密码短语,Encoding.UTF8.GetBytes(name),4096);
key=rfc2898.GetBytes(32);
公共类Rfc2898DeriveBytes:DeriveBytes
{
const int BlockSize=20;
块体;
字节[]缓冲区;
内部索引;
只读HMACSHA1 HMACSHA1;
单元迭代;
字节[]盐;
国际标准指数;
公共Rfc2898DeriveBytes(字符串密码,int-saltSize)
:此(密码,saltSize,1000)
{
}
公共Rfc2898DeriveBytes(字符串密码,字节[]salt)
:此(密码,salt,1000)
{
}
公共Rfc2898DeriveBytes(字符串密码、int saltSize、int迭代次数)
{
如果(盐度<0)
{
抛出新ArgumentOutOfRangeException(“saltSize”);
}
字节[]数据=新字节[saltSize];
新的RNGCryptoServiceProvider().GetBytes(数据);
Salt=数据;
迭代次数=迭代次数;
hmacsha1=新的hmacsha1(新的UTF8编码(false).GetBytes(密码));
初始化();
}
公共Rfc2898DeriveBytes(字符串密码,字节[]salt,int迭代):这(新的UTF8Encoding(false)。GetBytes(密码),salt,迭代)
{
}
公共Rfc2898DeriveBytes(字节[]密码,字节[]salt,整数迭代)
{
盐=盐;
迭代次数=迭代次数;
hmacsha1=新的hmacsha1(密码);
初始化();
}
静态字节[]Int(uint i)
{
字节[]字节=位转换器.GetBytes(i);
字节[]缓冲区2=新字节[]{字节[3],字节[2],字节[1],字节[0]};
if(!BitConverter.IsLittleEndian)
{
返回字节;
}
返回缓冲区2;
}
字节[]派生键()
{
字节[]inputBuffer=Int(块);
hmacsha1.转换块(盐,0,盐。长度,盐,0);
hmacsha1.TransformFinalBlock(inputBuffer,0,inputBuffer.Length);
字节[]散列=hmacsha1.hash;
hmacsha1.Initialize();
字节[]缓冲区3=散列;
用于(int i=2;i块大小)
{
Buffer.BlockCopy(src,0,dst,dstOffset,BlockSize);
dstOffset+=块大小;
}
其他的
{
块拷贝(src,0,dst,dstOffset,num3);
dstOffset+=num3;
BlockCopy(src,num3,Buffer,startIndex,BlockSize-num3);
endIndex+=块大小-num3;
返回dst;
}
}
返回dst;
}
void Initialize()
{
if(缓冲区!=null)
{
Array.Clear(buffer,0,buffer.Length);
}
缓冲区=新字节[块大小];
块=1;
startIndex=endIndex=0;
}
公共覆盖无效重置()
{
初始化();
}
公共整数迭代计数
{
得到
{
返回(int)迭代;
}
设置
{
如果(value查看Microsoft链接,我做了一些更改,以使PMK与您提出的链接中发现的PMK相同
将内部和外部散列的SHA算法从SHA256Managed更改为SHA1 Managed
将哈希大小(以字节为单位)更改为等于20而不是34
这将生成正确的WPA密钥
我知道这有点晚了,但我只是刚刚开始寻找这类信息,并认为我可以帮助其他人。如果有人读过这篇文章,对PRF函数有什么想法,以及如何在C#中实现它?这扩展了Dodgyrabbit的答案,他的代码在我开发这篇文章时帮助修复了我的问题。这个通用类可以使用C#中的ny HMAC派生类。这是.NET 4,因为参数具有默认值,但
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;
}
}
}
}