C#中的PBKDF2实现与Rfc2898DeriveBytes

23

大家好,我正在尝试在C#中实现一个创建WPA共享密钥的PBKDF2函数。 我在这里找到了一些:http://msdn.microsoft.com/en-us/magazine/cc163913.aspx 看起来可以产生有效的结果,但它少了一个字节...而且错误的PSK值。

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

我发现通过使用内置于C#中的Rfc2898DeriveBytes库,有一种方法可以使之工作。 使用此方法,我可以得到有效的输出:

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

使用Rfc2898DeriveBytes时,我的一个限制是“盐”必须为8个八位字节长。如果它更短,Rfc2898DeriveBytes会抛出异常。我认为我所要做的就是将盐(如果它更短)填充到8个字节,然后就好了。但不!我已经尝试了几乎每种填充较短盐的组合,但我无法复制从上述两个网站获得的结果。

所以问题的关键是,这是否意味着Rfc2898DeriveBytes只能使用长度大于8个字节的源盐?如果是这样,是否有人知道任何可以用于WPA预共享密钥的PBKDF2的C#代码?


2
您可以使用以下链接:http://msdn.microsoft.com/zh-cn/library/system.web.helpers.crypto.hashpassword%28v=vs.99%29.aspx - user1518771
4个回答

15

这里有一个实现不需要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();
            }
        }
    }

7
当比较来自.NET的Rfc2898DeriveBytes和Anandam的PBKDF2 Javascript实现的密钥派生时,我得到了匹配的结果。
我组合了SlowAES和Anandam的PBKDF2打包成Windows脚本组件的示例。使用这个实现可以很好地与.NET RijndaelManaged类和Rfc2898DeriveBytes类进行交互。
另请参阅: 所有这些都超出了您所要求的范围。他们都展示了AES加密的互操作性。但是要在加密上实现互操作性,必须先在基于密码的密钥派生上实现互操作性(或匹配输出)。

7
在查看Microsoft链接的基础上,我进行了一些更改,以使PMK与您提供的链接中发现的PMK相同。
将内部和外部哈希的SHA算法从SHA256Managed更改为SHA1Managed。
将HASH_SIZE_IN_BYTES更改为20而不是34。
这将生成正确的WPA密钥。
我知道有点晚了,但我刚开始寻找这种信息,并认为我可以帮助其他人。如果有人阅读此帖子,请分享关于PRF函数的想法以及如何在C#中执行它的方法。

如果可以选择,我不建议使用SHA-1算法。它已被证明存在漏洞(http://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html)。尽管攻击仍需要大量计算,但破解SHA-1哈希的可行性将比NSA预期的要早得多。 - Samuel Parkinson
2
@Sam - 那篇文章是2005年的,安全性已经有了很大的提升。SHA1不是因为可以被反转或碰撞数太高而容易受到攻击,而是因为它速度过快,使得现代云处理下的暴力攻击变得太容易。SHA256并没有慢多少,因此也几乎同样容易受到攻击。像PBKDF2/RFC2898这样的密钥拉伸算法会像SHA一样重复数千次,使其变得缓慢,从而使任何暴力攻击变得更加困难。在这种情况下,SHA1和SHA256之间的差异远远不如你想象的那么显著。 - Keith
@Keith 是的,同意,但如果有选择的话,最好还是避免使用SHA1。 - Samuel Parkinson

3
这扩展了Dodgyrabbit的答案,并且他的代码帮助我解决问题。这个通用类可以在C#中使用任何HMAC衍生类。因为有默认值参数,所以这是.NET 4,但是如果更改这些参数,那么它应该适用于.NET 2,但我没有测试过。请自行决定是否使用。

我还在我的博客The Albequerque Left Turn上发布了这篇文章。

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
    // https://dev59.com/MnNA5IYBdhLWcg3wNrEy

    // 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 对于这个评论:HMAC 函数是由密码进行密钥化的!永远不要使用盐值进行密钥化!这让我避免了一个巨大的头痛! - absentmindeduk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接