// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // https://g...content-available-to-author-only...b.com/dotnet/corefx/blob/29cb063b95661470340b6ba7e1381495c05bfff2/src/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rfc2898DeriveBytes.cs namespace My.System.Security.Cryptography { using global::System; using global::System.Text; using global::System.Diagnostics; using global::System.Security.Cryptography; using global::My.Internal.Cryptography; public class Rfc2898DeriveBytes : DeriveBytes { private const int MinimumSaltSize = 8; 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 HashAlgorithmName HashAlgorithm { get; } public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations) : this(password, salt, iterations, HashAlgorithmName.SHA1) { } public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, HashAlgorithmName hashAlgorithm) { if (salt == null) throw new ArgumentNullException(nameof(salt)); if (salt.Length < MinimumSaltSize) throw new ArgumentException("SR.Cryptography_PasswordDerivedBytes_FewBytesSalt", nameof(salt)); if (iterations <= 0) throw new ArgumentOutOfRangeException(nameof(iterations), "SR.ArgumentOutOfRange_NeedPosNum"); if (password == null) throw new NullReferenceException(); // This "should" be ArgumentNullException but for compat, we throw NullReferenceException. _salt = salt.CloneByteArray(); _iterations = (uint)iterations; _password = password.CloneByteArray(); 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, HashAlgorithmName.SHA1) { } public Rfc2898DeriveBytes(string password, byte[] salt, int iterations, HashAlgorithmName 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, HashAlgorithmName.SHA1) { } public Rfc2898DeriveBytes(string password, int saltSize, int iterations, HashAlgorithmName hashAlgorithm) { if (saltSize < 0) throw new ArgumentOutOfRangeException(nameof(saltSize), "SR.ArgumentOutOfRange_NeedNonNegNum"); if (saltSize < MinimumSaltSize) throw new ArgumentException("SR.Cryptography_PasswordDerivedBytes_FewBytesSalt", nameof(saltSize)); if (iterations <= 0) throw new ArgumentOutOfRangeException(nameof(iterations), "SR.ArgumentOutOfRange_NeedPosNum"); _salt = Helpers.GenerateRandom(saltSize); _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), "SR.ArgumentOutOfRange_NeedPosNum"); _iterations = (uint)value; Initialize(); } } public byte[] Salt { get { return _salt.CloneByteArray(); } set { if (value == null) throw new ArgumentNullException(nameof(value)); if (value.Length < MinimumSaltSize) throw new ArgumentException("SR.Cryptography_PasswordDerivedBytes_FewBytesSalt"); _salt = value.CloneByteArray(); 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) { Debug.Assert(_blockSize > 0); if (cb <= 0) throw new ArgumentOutOfRangeException(nameof(cb), "SR.ArgumentOutOfRange_NeedPosNum"); 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; } } Debug.Assert(_startIndex == 0 && _endIndex == 0, "Invalid start or end index in the internal buffer."); 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(); } [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "HMACSHA1 is needed for compat. (https://g...content-available-to-author-only...b.com/dotnet/corefx/issues/9438)")] private HMAC OpenHmac() { Debug.Assert(_password != null); HashAlgorithmName hashAlgorithm = HashAlgorithm; if (string.IsNullOrEmpty(hashAlgorithm.Name)) throw new CryptographicException("SR.Cryptography_HashAlgorithmNameNullOrEmpty"); if (hashAlgorithm == HashAlgorithmName.SHA1) return new HMACSHA1(_password); if (hashAlgorithm == HashAlgorithmName.SHA256) return new HMACSHA256(_password); if (hashAlgorithm == HashAlgorithmName.SHA384) return new HMACSHA384(_password); if (hashAlgorithm == HashAlgorithmName.SHA512) return new HMACSHA512(_password); throw new CryptographicException(string.Format("SR.Cryptography_UnknownHashAlgorithm", hashAlgorithm.Name)); } 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); Helpers.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; } } } // https://g...content-available-to-author-only...b.com/dotnet/corefx/blob/45b724f6b6391910edea8a70f3f22a4a7996696d/src/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/HashAlgorithmName.cs namespace My.System.Security.Cryptography { using global::System; // Strongly typed string representing the name of a hash algorithm. // Open ended to allow extensibility while giving the discoverable feel of an enum for common values. /// /// Specifies the name of a cryptographic hash algorithm. /// /// Asymmetric Algorithms implemented using Microsoft's CNG (Cryptography Next Generation) API /// will interpret the underlying string value as a CNG algorithm identifier: /// * https://msdn.microsoft.com/en-us/library/windows/desktop/aa375534(v=vs.85).aspx /// /// As with CNG, the names are case-sensitive. /// /// Asymmetric Algorithms implemented using other technologies: /// * Must recognize at least "MD5", "SHA1", "SHA256", "SHA384", and "SHA512". /// * Should recognize additional CNG IDs for any other hash algorithms that they also support. /// public struct HashAlgorithmName : IEquatable { // Returning a new instance every time is free here since HashAlgorithmName is a struct with // a single string field. The optimized codegen should be equivalent to return "MD5". /// /// Gets a representing "MD5" /// public static HashAlgorithmName MD5 { get { return new HashAlgorithmName("MD5"); } } /// /// Gets a representing "SHA1" /// public static HashAlgorithmName SHA1 { get { return new HashAlgorithmName("SHA1"); } } /// /// Gets a representing "SHA256" /// public static HashAlgorithmName SHA256 { get { return new HashAlgorithmName("SHA256"); } } /// /// Gets a representing "SHA384" /// public static HashAlgorithmName SHA384 { get { return new HashAlgorithmName("SHA384"); } } /// /// Gets a representing "SHA512" /// public static HashAlgorithmName SHA512 { get { return new HashAlgorithmName("SHA512"); } } private readonly string _name; /// /// Gets a representing a custom name. /// /// The custom hash algorithm name. public HashAlgorithmName(string name) { // Note: No validation because we have to deal with default(HashAlgorithmName) regardless. _name = name; } /// /// Gets the underlying string representation of the algorithm name. /// /// /// May be null or empty to indicate that no hash algorithm is applicable. /// public string Name { get { return _name; } } public override string ToString() { return _name ?? String.Empty; } public override bool Equals(object obj) { return obj is HashAlgorithmName && Equals((HashAlgorithmName)obj); } public bool Equals(HashAlgorithmName other) { // NOTE: intentionally ordinal and case sensitive, matches CNG. return _name == other._name; } public override int GetHashCode() { return _name == null ? 0 : _name.GetHashCode(); } public static bool operator ==(HashAlgorithmName left, HashAlgorithmName right) { return left.Equals(right); } public static bool operator !=(HashAlgorithmName left, HashAlgorithmName right) { return !(left == right); } } } // https://g...content-available-to-author-only...b.com/dotnet/corefx/blob/bffef76f6af208e2042a2f27bc081ee908bb390b/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/Helpers.cs // https://g...content-available-to-author-only...b.com/dotnet/corefx/blob/827f47f48df00923b802427486b062d62dd243b5/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/Helpers.cs namespace My.Internal.Cryptography { using global::System; using global::System.Diagnostics; using global::System.Security.Cryptography; internal static class Helpers { public static byte[] CloneByteArray(this byte[] src) { if (src == null) { return null; } return (byte[])(src.Clone()); } public static byte[] GenerateRandom(int count) { byte[] buffer = new byte[count]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) { rng.GetBytes(buffer); } return buffer; } // encodes the integer i into a 4-byte array, in big endian. public static void WriteInt(uint i, byte[] arr, int offset) { unchecked { Debug.Assert(arr != null); Debug.Assert(arr.Length >= offset + sizeof(uint)); arr[offset] = (byte)(i >> 24); arr[offset + 1] = (byte)(i >> 16); arr[offset + 2] = (byte)(i >> 8); arr[offset + 3] = (byte)i; } } } }