File: System\Security\Cryptography\Rfc2898DeriveBytes.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Versioning;
using System.Text;
using Internal.Cryptography;
 
namespace System.Security.Cryptography
{
    public partial class Rfc2898DeriveBytes : DeriveBytes
    {
        private byte[] _salt;
        private uint _iterations;
        private IncrementalHash _hmac;
        private readonly int _blockSize;
 
        private byte[] _buffer;
        private uint _block;
        private int _startIndex;
        private int _endIndex;
 
        /// <summary>
        /// Gets the hash algorithm used for byte derivation.
        /// </summary>
        public HashAlgorithmName HashAlgorithm { get; }
 
        [Obsolete(Obsoletions.Rfc2898OutdatedCtorMessage, DiagnosticId = Obsoletions.Rfc2898OutdatedCtorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations)
            : this(password, salt, iterations, HashAlgorithmName.SHA1)
        {
        }
 
        public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm)
            : this(password, salt, iterations, hashAlgorithm, clearPassword: false)
        {
        }
 
        [Obsolete(Obsoletions.Rfc2898OutdatedCtorMessage, DiagnosticId = Obsoletions.Rfc2898OutdatedCtorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public Rfc2898DeriveBytes(string password, byte[] salt)
             : this(password, salt, 1000)
        {
        }
 
        [Obsolete(Obsoletions.Rfc2898OutdatedCtorMessage, DiagnosticId = Obsoletions.Rfc2898OutdatedCtorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public Rfc2898DeriveBytes(string password, byte[] salt, int iterations)
            : this(password, salt, iterations, HashAlgorithmName.SHA1)
        {
        }
 
        public Rfc2898DeriveBytes(string password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm)
            : this(
                Encoding.UTF8.GetBytes(password ?? throw new ArgumentNullException(nameof(password))),
                salt,
                iterations,
                hashAlgorithm,
                clearPassword: true)
        {
        }
 
        [Obsolete(Obsoletions.Rfc2898OutdatedCtorMessage, DiagnosticId = Obsoletions.Rfc2898OutdatedCtorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public Rfc2898DeriveBytes(string password, int saltSize)
            : this(password, saltSize, 1000)
        {
        }
 
        [Obsolete(Obsoletions.Rfc2898OutdatedCtorMessage, DiagnosticId = Obsoletions.Rfc2898OutdatedCtorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
            : this(password, saltSize, iterations, HashAlgorithmName.SHA1)
        {
        }
 
        public Rfc2898DeriveBytes(string password, int saltSize, int iterations, HashAlgorithmName hashAlgorithm)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(saltSize);
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(iterations);
 
            _salt = new byte[saltSize + sizeof(uint)];
            RandomNumberGenerator.Fill(_salt.AsSpan(0, saltSize));
 
            _iterations = (uint)iterations;
            byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
            HashAlgorithm = hashAlgorithm;
            _hmac = OpenHmac(passwordBytes);
            CryptographicOperations.ZeroMemory(passwordBytes);
            _blockSize = _hmac.HashLengthInBytes;
 
            Initialize();
        }
 
        internal Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm, bool clearPassword) :
            this(
                new ReadOnlySpan<byte>(password ?? throw new ArgumentNullException(nameof(password))),
                new ReadOnlySpan<byte>(salt ?? throw new ArgumentNullException(nameof(salt))),
                iterations,
                hashAlgorithm)
        {
            if (clearPassword)
            {
                CryptographicOperations.ZeroMemory(password);
            }
        }
 
        internal Rfc2898DeriveBytes(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, int iterations, HashAlgorithmName hashAlgorithm)
        {
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(iterations);
 
            _salt = new byte[salt.Length + sizeof(uint)];
            salt.CopyTo(_salt);
            _iterations = (uint)iterations;
            HashAlgorithm = hashAlgorithm;
            _hmac = OpenHmac(password);
 
            _blockSize = _hmac.HashLengthInBytes;
            Initialize();
        }
 
        public int IterationCount
        {
            get
            {
                return (int)_iterations;
            }
 
            set
            {
                ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
                _iterations = (uint)value;
                Initialize();
            }
        }
 
        public byte[] Salt
        {
            get
            {
                return _salt.AsSpan(0, _salt.Length - sizeof(uint)).ToArray();
            }
 
            set
            {
                ArgumentNullException.ThrowIfNull(value);
                _salt = new byte[value.Length + sizeof(uint)];
                value.AsSpan().CopyTo(_salt);
                Initialize();
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_hmac != null)
                {
                    _hmac.Dispose();
                    _hmac = null!;
                }
 
                if (_buffer != null)
                    Array.Clear(_buffer);
                if (_salt != null)
                    Array.Clear(_salt);
            }
            base.Dispose(disposing);
        }
 
        public override byte[] GetBytes(int cb)
        {
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(cb);
 
            byte[] ret = new byte[cb];
            GetBytes(ret);
            return ret;
        }
 
        internal void GetBytes(Span<byte> destination)
        {
            Debug.Assert(_blockSize > 0);
            int cb = destination.Length;
            int offset = 0;
            int size = _endIndex - _startIndex;
            ReadOnlySpan<byte> bufferSpan = _buffer;
 
            if (size > 0)
            {
                if (cb >= size)
                {
                    bufferSpan.Slice(_startIndex, size).CopyTo(destination);
                    _startIndex = _endIndex = 0;
                    offset += size;
                }
                else
                {
                    bufferSpan.Slice(_startIndex, cb).CopyTo(destination);
                    _startIndex += cb;
                    return;
                }
            }
 
            Debug.Assert(_startIndex == 0 && _endIndex == 0, "Invalid start or end index in the internal buffer.");
 
            while (offset < cb)
            {
                Func();
                int remainder = cb - offset;
                if (remainder >= _blockSize)
                {
                    bufferSpan.Slice(0, _blockSize).CopyTo(destination.Slice(offset));
                    offset += _blockSize;
                }
                else
                {
                    bufferSpan.Slice(0, remainder).CopyTo(destination.Slice(offset));
                    _startIndex = remainder;
                    _endIndex = _buffer.Length;
                    return;
                }
            }
        }
 
        [Obsolete(Obsoletions.Rfc2898CryptDeriveKeyMessage, DiagnosticId = Obsoletions.Rfc2898CryptDeriveKeyDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        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();
        }
 
        private IncrementalHash OpenHmac(ReadOnlySpan<byte> password)
        {
            HashAlgorithmName hashAlgorithm = HashAlgorithm;
 
            if (string.IsNullOrEmpty(hashAlgorithm.Name))
            {
                throw new CryptographicException(SR.Cryptography_HashAlgorithmNameNullOrEmpty);
            }
 
            // Restrict the HashAlgorithmName to known hashes, particularly excluding MD5.
            if (hashAlgorithm != HashAlgorithmName.SHA1 &&
                hashAlgorithm != HashAlgorithmName.SHA256 &&
                hashAlgorithm != HashAlgorithmName.SHA384 &&
                hashAlgorithm != HashAlgorithmName.SHA512 &&
                hashAlgorithm != HashAlgorithmName.SHA3_256 &&
                hashAlgorithm != HashAlgorithmName.SHA3_384 &&
                hashAlgorithm != HashAlgorithmName.SHA3_512)
            {
                throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name));
            }
 
            return IncrementalHash.CreateHMAC(hashAlgorithm, password);
        }
 
        [MemberNotNull(nameof(_buffer))]
        private void Initialize()
        {
            if (_buffer != null)
                Array.Clear(_buffer);
            _buffer = new byte[_blockSize];
            _block = 0;
            _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 void Func()
        {
            // Block number is going to overflow, exceeding the maximum total possible bytes
            // that can be extracted.
            if (_block == uint.MaxValue)
                throw new CryptographicException(SR.Cryptography_ExceedKdfExtractLimit);
 
            BinaryPrimitives.WriteUInt32BigEndian(_salt.AsSpan(_salt.Length - sizeof(uint)), _block + 1);
            Debug.Assert(_blockSize == _buffer.Length);
 
            // The biggest _blockSize we have is from SHA512, which is 64 bytes.
            // Since we have a closed set of supported hash algorithms (OpenHmac())
            // we can know this always fits.
            //
            Span<byte> uiSpan = stackalloc byte[64];
            uiSpan = uiSpan.Slice(0, _blockSize);
            _hmac.AppendData(_salt);
            int bytesWritten = _hmac.GetHashAndReset(uiSpan);
            Debug.Assert(bytesWritten == _blockSize);
 
            uiSpan.CopyTo(_buffer);
 
            for (int i = 2; i <= _iterations; i++)
            {
                _hmac.AppendData(uiSpan);
                bytesWritten = _hmac.GetHashAndReset(uiSpan);
                Debug.Assert(bytesWritten == _blockSize);
 
                for (int j = _buffer.Length - 1; j >= 0; j--)
                {
                    _buffer[j] ^= uiSpan[j];
                }
            }
 
            // increment the block count.
            _block++;
        }
    }
}