// 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.
/// <summary>
/// Specifies the name of a cryptographic hash algorithm.
/// </summary>
/// 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.
/// </remarks>
public struct HashAlgorithmName : IEquatable<HashAlgorithmName>
{
// 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".
/// <summary>
/// Gets a <see cref="HashAlgorithmName" /> representing "MD5"
/// </summary>
public static HashAlgorithmName MD5 { get { return new HashAlgorithmName("MD5"); } }
/// <summary>
/// Gets a <see cref="HashAlgorithmName" /> representing "SHA1"
/// </summary>
public static HashAlgorithmName SHA1 { get { return new HashAlgorithmName("SHA1"); } }
/// <summary>
/// Gets a <see cref="HashAlgorithmName" /> representing "SHA256"
/// </summary>
public static HashAlgorithmName SHA256 { get { return new HashAlgorithmName("SHA256"); } }
/// <summary>
/// Gets a <see cref="HashAlgorithmName" /> representing "SHA384"
/// </summary>
public static HashAlgorithmName SHA384 { get { return new HashAlgorithmName("SHA384"); } }
/// <summary>
/// Gets a <see cref="HashAlgorithmName" /> representing "SHA512"
/// </summary>
public static HashAlgorithmName SHA512 { get { return new HashAlgorithmName("SHA512"); } }
private readonly string _name;
/// <summary>
/// Gets a <see cref="HashAlgorithmName" /> representing a custom name.
/// </summary>
/// <param name="name">The custom hash algorithm name.</param>
public HashAlgorithmName(string name)
{
// Note: No validation because we have to deal with default(HashAlgorithmName) regardless.
_name = name;
}
/// <summary>
/// Gets the underlying string representation of the algorithm name.
/// </summary>
/// <remarks>
/// May be null or empty to indicate that no hash algorithm is applicable.
/// </remarks>
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;
}
}
}
}