PBKDF2 implementation in C# with Rfc2898DeriveBytes

前端 未结 4 1583
被撕碎了的回忆
被撕碎了的回忆 2020-12-13 05:00

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 see

4条回答
  •  温柔的废话
    2020-12-13 05:36

    This expands on Dodgyrabbit's answer and his code helped to fix mine as I developed this. This generic class can use any HMAC-derived class in C#. This is .NET 4 because of the parameters with default values, but if those were changed then this should work down to .NET 2, but I haven't tested that. USE AT YOUR OWN RISK.

    I have also posted this on my blog, The Albequerque Left Turn, today.

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

提交回复
热议问题