我正在尝试使用.Net Framework 4.5中的api,该API应该可以为我提供加密货币钱包。在documentations的一部分中说:


  通过PBKDF2功能以128位密钥长度和
  SHA256的1,024次迭代


我在C#中找不到指定方法来做到这一点。在文档中,他们输入了“ be9d3a4f1220495a96c38d36d8558365”作为密码,输出为“ 4369cb0560d54f55d0c03564fbd983c4”。
看来我应该使用Rfc2898DeriveBytes方法,我像下面的代码一样使用它,但是我没有得到相同的结果。

string output = Convert.ToBase64String((new Rfc2898DeriveBytes("e24546d6643137a310968566cf1cd42b",16, 1024)).GetBytes(32));

输出==>'x10zclBJY2eeZqjMyPfQm4ljyMFPvWbxF72Om2DCzHE ='

最佳答案

最好实现自己的PBKDF2版本。 PBKDF2是由名称错误的Rfc2898DeriveBytes类实现的实际算法。

由于.NET 4.5不包括将PBKDF2与其他哈希一起使用的功能。 .NET版本4.7.2确实包含该功能,但不允许salt为零字节。

因此,最好实现自己的版本。 Microsoft的.NET版本具有似乎不兼容的特定版权声明。解决此问题的一种方法是从Mono实现PBKDF2,但是Mono的更高版本没有实现该类(看来),并且它们没有实现可以选择哈希的版本。

幸运的是,bartonjs has indicated a version具有允许使用的MIT许可证,可以使用以下解决方案:

using System;
using System.Security.Cryptography;
using System.Text;

namespace StackOverflow
{
    public class Rfc2898DeriveBytes : DeriveBytes
    {
        private const string DEFAULT_HASH_ALGORITHM = "SHA-1";

        private const int MinimumSaltSize = 0;
        private readonly byte[] _password;
        private byte[] _salt;
        private uint _iterations;
        private HMAC _hmac;
        private int _blockSize;

        private byte[] _buffer;
        private uint _block;
        private int _startIndex;
        private int _endIndex;

        public string HashAlgorithm { get; }

        public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations)
            : this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
        {
        }

        public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, string hashAlgorithm)
        {
            if (salt == null)
                throw new ArgumentNullException(nameof(salt));
            if (salt.Length < MinimumSaltSize)
                throw new ArgumentException(nameof(salt));
            if (iterations <= 0)
                throw new ArgumentOutOfRangeException(nameof(iterations));
            if (password == null)
                throw new NullReferenceException();  // This "should" be ArgumentNullException but for compat, we throw NullReferenceException.

            _salt = (byte[])salt.Clone();
            _iterations = (uint)iterations;
            _password = (byte[])password.Clone();
            HashAlgorithm = hashAlgorithm;
            _hmac = OpenHmac();
            // _blockSize is in bytes, HashSize is in bits.
            _blockSize = _hmac.HashSize >> 3;

            Initialize();
        }

        public Rfc2898DeriveBytes(string password, byte[] salt)
             : this(password, salt, 1000)
        {
        }

        public Rfc2898DeriveBytes(string password, byte[] salt, int iterations)
            : this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
        {
        }

        public Rfc2898DeriveBytes(string password, byte[] salt, int iterations, string hashAlgorithm)
            : this(Encoding.UTF8.GetBytes(password), salt, iterations, hashAlgorithm)
        {
        }

        public Rfc2898DeriveBytes(string password, int saltSize)
            : this(password, saltSize, 1000)
        {
        }

        public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
            : this(password, saltSize, iterations, DEFAULT_HASH_ALGORITHM)
        {
        }

        public Rfc2898DeriveBytes(string password, int saltSize, int iterations, string hashAlgorithm)
        {
            if (saltSize < 0)
                throw new ArgumentOutOfRangeException(nameof(saltSize));
            if (saltSize < MinimumSaltSize)
                throw new ArgumentException(nameof(saltSize));
            if (iterations <= 0)
                throw new ArgumentOutOfRangeException(nameof(iterations));

            _salt = new byte[saltSize];
            RandomNumberGenerator.Create().GetBytes(_salt);
            _iterations = (uint)iterations;
            _password = Encoding.UTF8.GetBytes(password);
            HashAlgorithm = hashAlgorithm;
            _hmac = OpenHmac();
            // _blockSize is in bytes, HashSize is in bits.
            _blockSize = _hmac.HashSize >> 3;

            Initialize();
        }

        public int IterationCount
        {
            get
            {
                return (int)_iterations;
            }

            set
            {
                if (value <= 0)
                    throw new ArgumentOutOfRangeException(nameof(value));
                _iterations = (uint)value;
                Initialize();
            }
        }

        public byte[] Salt
        {
            get
            {
                return (byte[])_salt.Clone();
            }

            set
            {
                if (value == null)
                    throw new ArgumentNullException(nameof(value));
                if (value.Length < MinimumSaltSize)
                    throw new ArgumentException("Too few bytes for salt");
                _salt = (byte[])value.Clone();
                Initialize();
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_hmac != null)
                {
                    _hmac.Dispose();
                    _hmac = null;
                }

                if (_buffer != null)
                    Array.Clear(_buffer, 0, _buffer.Length);
                if (_password != null)
                    Array.Clear(_password, 0, _password.Length);
                if (_salt != null)
                    Array.Clear(_salt, 0, _salt.Length);
            }
            base.Dispose(disposing);
        }

        public override byte[] GetBytes(int cb)
        {
            if (cb <= 0)
                throw new ArgumentOutOfRangeException(nameof(cb));
            byte[] password = new byte[cb];

            int offset = 0;
            int size = _endIndex - _startIndex;
            if (size > 0)
            {
                if (cb >= size)
                {
                    Buffer.BlockCopy(_buffer, _startIndex, password, 0, size);
                    _startIndex = _endIndex = 0;
                    offset += size;
                }
                else
                {
                    Buffer.BlockCopy(_buffer, _startIndex, password, 0, cb);
                    _startIndex += cb;
                    return password;
                }
            }

            while (offset < cb)
            {
                byte[] T_block = Func();
                int remainder = cb - offset;
                if (remainder > _blockSize)
                {
                    Buffer.BlockCopy(T_block, 0, password, offset, _blockSize);
                    offset += _blockSize;
                }
                else
                {
                    Buffer.BlockCopy(T_block, 0, password, offset, remainder);
                    offset += remainder;
                    Buffer.BlockCopy(T_block, remainder, _buffer, _startIndex, _blockSize - remainder);
                    _endIndex += (_blockSize - remainder);
                    return password;
                }
            }
            return password;
        }

        public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV)
        {
            // If this were to be implemented here, CAPI would need to be used (not CNG) because of
            // unfortunate differences between the two. Using CNG would break compatibility. Since this
            // assembly currently doesn't use CAPI it would require non-trivial additions.
            // In addition, if implemented here, only Windows would be supported as it is intended as
            // a thin wrapper over the corresponding native API.
            // Note that this method is implemented in PasswordDeriveBytes (in the Csp assembly) using CAPI.
            throw new PlatformNotSupportedException();
        }

        public override void Reset()
        {
            Initialize();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "HMACSHA1 is needed for compat. (https://github.com/dotnet/corefx/issues/9438)")]
        private HMAC OpenHmac()
        {
            String hashAlgorithm = HashAlgorithm;

            if (string.IsNullOrEmpty(hashAlgorithm))
                throw new CryptographicException("HashAlgorithm name not present");

            if (hashAlgorithm.Equals("SHA-1", StringComparison.OrdinalIgnoreCase))
                return new HMACSHA1(_password);
            if (hashAlgorithm.Equals("SHA-256", StringComparison.OrdinalIgnoreCase))
                return new HMACSHA256(_password);
            if (hashAlgorithm.Equals("SHA-384", StringComparison.OrdinalIgnoreCase))
                return new HMACSHA384(_password);
            if (hashAlgorithm.Equals("SHA-512", StringComparison.OrdinalIgnoreCase))
                return new HMACSHA512(_password);

            throw new CryptographicException("MAC algorithm " + hashAlgorithm + " not available");
        }

        private void Initialize()
        {
            if (_buffer != null)
                Array.Clear(_buffer, 0, _buffer.Length);
            _buffer = new byte[_blockSize];
            _block = 1;
            _startIndex = _endIndex = 0;
        }

        // This function is defined as follows:
        // Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
        // where i is the block number.
        private byte[] Func()
        {
            byte[] temp = new byte[_salt.Length + sizeof(uint)];
            Buffer.BlockCopy(_salt, 0, temp, 0, _salt.Length);



            WriteInt(_block, temp, _salt.Length);

            temp = _hmac.ComputeHash(temp);

            byte[] ret = temp;
            for (int i = 2; i <= _iterations; i++)
            {
                temp = _hmac.ComputeHash(temp);

                for (int j = 0; j < _blockSize; j++)
                {
                    ret[j] ^= temp[j];
                }
            }

            // increment the block count.
            _block++;
            return ret;
        }

        private void WriteInt(uint i, byte[] buf, int bufOff)
        {
            buf[bufOff++] = (byte)(i >> 24);
            buf[bufOff++] = (byte)(i >> 16);
            buf[bufOff++] = (byte)(i >> 8);
            buf[bufOff] = (byte)i;
        }
    }
}


这是一类,其中重写了更具体的异常,替换了一些特殊的克隆,并且对随机盐生成进行了概括。最小盐大小也已设置为0。否则,它是在不同名称空间中的相同代码。

可以这样使用它:

string pw = "be9d3a4f1220495a96c38d36d8558365";
byte[] salt = new byte[0];
int iterations = 1024;

Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(pw, salt, iterations, "SHA-256");
byte[] key = pbkdf2.GetBytes(16);


请注意,PIN是十六进制编码为UTF-8,这是PBKDF2的默认编码(不是.NET!的默认编码)。结果是一个密钥,当以十六进制表示时,该密钥等于4369cb0560d54f55d0c03564fbd983c4

我已经使用字符串将其表示为哈希函数转换为4.5兼容类,对于带有枚举HashAlgorithm(4.6或类似名称)的类,请查看the revision history

关于c# - .NET Framework 4.5中的PBKDF2函数具有128位 key 长度和SHA256的1,024次迭代,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53228398/

10-11 20:24