Подтвердить что ты не робот

Реализация PBKDF2 в С# с Rfc2898DeriveBytes

Ребята, я пытаюсь реализовать функцию PBKDF2 в С#, которая создает WPA Shared key. Я нашел здесь: http://msdn.microsoft.com/en-us/magazine/cc163913.aspx, который, похоже, приводит к допустимому результату, но это один байт слишком короткий... и неправильный PSK значение.

Чтобы проверить результат, я сравниваю его с этим: http://www.xs4all.nl/~rjoris/wpapsk.html или http://anandam.name/pbkdf2/

Я нашел один способ заставить это работать со встроенной библиотекой на С# под названием Rfc2898DeriveBytes. Используя это, я получаю действительный вывод, используя:

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

Теперь одно ограничение, которое я использую Rfc2898DeriveBytes, - это "соль", длина которого должна быть 8 октетов. Если он короче, Rfc2898DeriveBytes генерирует исключение. Я думал, что все, что мне нужно было сделать, это проложить соль (если бы она была короче) до 8 байт, и я был бы хорош. Но нет! Я пробовал почти все комбинации дополнений с более короткой солью, но я не могу дублировать результаты, полученные с этих двух сайтов выше.

Итак, нижняя строка означает, означает ли это, что Rfc2898DeriveBytes просто не будет работать с источником соли короче 8 байтов? Если да, то кто-нибудь знает какой-либо код С#, который я мог бы использовать, который реализует PBKDF2 для WPA Preshared key?

4b9b3361

Ответ 1

Вот реализация, которая не требует 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();
            }
        }
    }

Ответ 2

Я получаю результаты сопоставления при сравнении вывода ключей из .NET Rfc2898DeriveBytes и реализации Jandcript для Anandam PBKDF2.

Я собрал пример упаковки SlowAES и Anandam PBKDF2 в Windows Script Components. Использование этой реализации показывает хорошее взаимодействие с классом .NET RijndaelManaged и классом Rfc2898DeriveBytes.

См. также:

Все это идет дальше того, о чем вы просите. Все они показывают взаимодействие шифрования AES. Но чтобы получить interop на шифровании, это является необходимым предварительным условием для взаимодействия (или соответствия выходов) при генерации ключа на основе пароля.

Ответ 3

Посмотрев на ссылку Microsoft, я внесла некоторые изменения, чтобы сделать PMK таким же, как те, которые были обнаружены в ссылках, которые вы указали.

Измените алгоритм SHA с SHA256Managed на SHA1Managed для внутреннего и внешнего хэшей.

Измените HASH_SIZE_IN_BYTES равным 20, а не 34.

Это создает правильный ключ WPA.

Я знаю, что это немного поздно, но я только начал искать такого рода информатину и думал, что смогу помочь другим. Если кто-нибудь прочтет это сообщение, какие-либо идеи о функции PRF и как это сделать в С#?

Ответ 4

Это расширилось на ответ Dodgyrabbit, и его код помог исправить мой, как я это разработал. Этот общий класс может использовать любой HMAC-производный класс в С#. Это .NET 4 из-за параметров со значениями по умолчанию, но если они были изменены, это должно сработать до .NET 2, но я не тестировал это. ИСПОЛЬЗУЙТЕ НА СВОЙ СОБСТВЕННЫЙ РИСК.

Я также разместил это в своем блоге, "Альбекерский левый поворот" сегодня.

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