1

I am trying to work with an api in .Net framework 4.5 that supposed to provide me cryptocurrencies wallet. in a part of it's documentations it says:

Pass Pin Code through the PBKDF2 function with 128 Bit Key Length and 1,024 iterations of SHA256

i could not find the Specify method in C# to do that. in documentations they have input "be9d3a4f1220495a96c38d36d8558365" as pin code and the out put is "4369cb0560d54f55d0c03564fbd983c4". it seems that i should use Rfc2898DeriveBytes Method, and i used it like code below but i didnot get the same result.

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

output ==> 'x10zclBJY2eeZqjMyPfQm4ljyMFPvWbxF72Om2DCzHE='

8
  • 1
    Unless you give it the same salt each time, every run will be different. Secondarily, you are converting the result to a Base64 string, but from the looks of the expected response, that is a hex encoded string and it is only 16 bytes. Commented Nov 9, 2018 at 15:31
  • 1
    I think the default hashing algo for RFC2898Derive bytes is SHA1. I might be wrong, but I'm sure I read that somewhere when looking into this in the past. Depending on your version of .Net you can specifiy SHA256 Commented Nov 9, 2018 at 15:32
  • 3
    github.com/BlockIo/block_io-php/blob/… says they use the empty salt (which Rfc2898DeriveBytes won't accept). (And to use SHA256 with Rfc2898DeriveBytes you need to upgrade to .NET 4.7.2). Commented Nov 9, 2018 at 15:40
  • 1
    so there is no a method to do SHA256 with 128 key lenght in .Net Framework 4.5? Commented Nov 10, 2018 at 0:10
  • 1
    hi @user2729871 I am also working on block.io wallet api and resolved this problem using this help but I am not able to complete the next steps of block.io api , can you please share your code snippets so I can also complete my wallet process? Commented Aug 14, 2019 at 20:45

1 Answer 1

4

It's probably best to implement your own version of PBKDF2. PBKDF2 is the actual algorithm implemented by the badly named Rfc2898DeriveBytes class.

As .NET 4.5 doesn't include the functionality to use PBKDF2 with a different hash. .NET version 4.7.2 does include the functionality but it doesn't allow the salt to be zero bytes.

So therefore it is best to implement your own version. The .NET version of Microsoft has specific copyright notices that do not seem compatible. One way to go around this is to implement PBKDF2 from Mono, but the later versions of Mono do not implement this class (it seems) and they do not implement the version where the hash can be chosen.

Fortunately bartonjs has indicated a version that has the permissive MIT license, which can be used, leading to the following solution:

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

this is a class where more specific exceptions have been rewritten, some specialized cloning is replaced, and the random salt generation is generalized. The minimum salt size has also been set to 0. Otherwise it is the same code in a different name space.

It is possible to use it like this:

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

Note that the PIN is hexadecimals encoded as UTF-8, the default encoding for PBKDF2 (not the default encoding for .NET!). The result is a key that, when represented as hexadecimals equals 4369cb0560d54f55d0c03564fbd983c4.

I've converted to a 4.5 compatible class using a string to indicate the hash function, for the one with an enum HashAlgorithm (4.6 or something similar) take a look at the revision history.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.