Skip to content

Instantly share code, notes, and snippets.

@OpBug
Last active October 29, 2017 20:17
Show Gist options
  • Save OpBug/4460d322178887a4fcb6590d4a059fff to your computer and use it in GitHub Desktop.
Save OpBug/4460d322178887a4fcb6590d4a059fff to your computer and use it in GitHub Desktop.
C# implementation of HMAC-based key derivation functionality, HKDF.
using System;
/// <summary>
/// Specifies the name of a cryptographic HMAC algorithm.
/// </summary>
public struct HMACAlgorithmName : IEquatable<HMACAlgorithmName>
{
#region " Members "
private readonly string _name;
#endregion
#region " Properties "
/// <summary>
/// Gets the underlying string representation of the algorithm name.
/// </summary>
/// <value>The string representation of the algorithm name, or null or <see cref="String.Empty"/> if no hash algorithm is available.</value>
public string Name
{
get { return _name; }
}
/// <summary>
/// Gets a HMAC algorithm name that represents "MD5".
/// </summary>
/// <value>A HMAC algorithm name that represents "MD5".</value>
public static HMACAlgorithmName HMACMD5
{
get { return new HMACAlgorithmName("HMACMD5"); }
}
/// <summary>
/// Gets a HMAC algorithm name that represents "HMACRIPEMD160".
/// </summary>
/// <value>A HMAC algorithm name that represents "HMACRIPEMD160".</value>
public static HMACAlgorithmName HMACRIPEMD160
{
get { return new HMACAlgorithmName("HMACRIPEMD160"); }
}
/// <summary>
/// Gets a HMAC algorithm name that represents "HMACSHA1".
/// </summary>
/// <value>A HMAC algorithm name that represents "HMACSHA1".</value>
public static HMACAlgorithmName HMACSHA1
{
get { return new HMACAlgorithmName("HMACSHA1"); }
}
/// <summary>
/// Gets a HMAC algorithm name that represents "HMACSHA256".
/// </summary>
/// <value>A HMAC algorithm name that represents "HMACSHA256".</value>
public static HMACAlgorithmName HMACSHA256
{
get { return new HMACAlgorithmName("HMACSHA256"); }
}
/// <summary>
/// Gets a HMAC algorithm name that represents "HMACSHA384".
/// </summary>
/// <value>A HMAC algorithm name that represents "HMACSHA384".</value>
public static HMACAlgorithmName HMACSHA384
{
get { return new HMACAlgorithmName("HMACSHA384"); }
}
/// <summary>
/// Gets a HMAC algorithm name that represents "HMACSHA512".
/// </summary>
/// <value>A HMAC algorithm name that represents "HMACSHA512".</value>
public static HMACAlgorithmName HMACSHA512
{
get { return new HMACAlgorithmName("HMACSHA512"); }
}
/// <summary>
/// Gets a HMAC algorithm name that represents "MACTripleDES".
/// </summary>
/// <value>A HMAC algorithm name that represents "MACTripleDES".</value>
public static HMACAlgorithmName MACTripleDES
{
get { return new HMACAlgorithmName("MACTripleDES"); }
}
#endregion
#region " Constructor "
/// <summary>
/// Initializes a new instance of the <see cref="HMACAlgorithmName"/> structure with a custom name.
/// </summary>
/// <param name="name">The custom HMAC algorithm name.</param>
public HMACAlgorithmName(string name)
{
_name = name;
}
#endregion
#region " Overrides "
/// <summary>
/// Returns a value that indicates whether the current instance and a specified object are equal.
/// </summary>
/// <param name="obj">The object to compare with the current instance.</param>
/// <returns>true if obj is a <see cref="HMACAlgorithmName"/> object and its <see cref="Name"/> property is equal to that of the current instance. The comparison is ordinal and case-sensitive.</returns>
public override bool Equals(object obj)
{
if (!(obj is HMACAlgorithmName))
{
return false;
}
return Equals((HMACAlgorithmName)obj);
}
/// <summary>
/// Returns the hash code for the current instance.
/// </summary>
/// <returns>The hash code for the current instance, or 0 if no name value was supplied to the <see cref="HMACAlgorithmName"/> constructor.</returns>
public override int GetHashCode()
{
if (_name == null)
{
return 0;
}
return _name.GetHashCode();
}
/// <summary>
/// Returns the string representation of the current <see cref="HMACAlgorithmName"/> instance.
/// </summary>
/// <returns>The string representation of the current <see cref="HMACAlgorithmName"/> instance.</returns>
public override string ToString()
{
return _name ?? string.Empty;
}
#endregion
#region " IEquatable "
/// <summary>
/// Determines whether two specified <see cref="HMACAlgorithmName"/> objects are equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>true if both left and right have the same <see cref="Name"/> value; otherwise, false.</returns>
public static bool operator ==(HMACAlgorithmName left, HMACAlgorithmName right)
{
return left.Equals(right);
}
/// <summary>
/// Determines whether two specified <see cref="HMACAlgorithmName"/> objects are not equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>true if both left and right do not have the same <see cref="Name"/> value; otherwise, false.</returns>
public static bool operator !=(HMACAlgorithmName left, HMACAlgorithmName right)
{
return !(left == right);
}
/// <summary>
/// Returns a value that indicates whether two <see cref="HMACAlgorithmName"/> instances are equal.
/// </summary>
/// <param name="other">The object to compare with the current instance.</param>
/// <returns>true if the <see cref="Name"/> property of other is equal to that of the current instance. The comparison is ordinal and case-sensitive.</returns>
public bool Equals(HMACAlgorithmName other)
{
return _name == other.Name;
}
#endregion
}
using System;
using System.Security.Cryptography;
/// <summary>
/// Implements HMAC-based key derivation functionality, HKDF, using the provided HMAC algorithm.
/// </summary>
public sealed class Rfc5869DeriveBytes
{
#region " Properties "
/// <summary>
/// Gets the max length of bytes that can be derived by the operation.
/// </summary>
/// <value>The max length that can be derived by the operation.</value>
public int MaxLength
{
get { return _hashLength * 255; }
}
#endregion
#region " Members "
private HMAC _hmac;
private int _hashLength;
#endregion
#region " Constructor "
/// <summary>
/// Initializes a new instance of the <see cref="Rfc5869DeriveBytes"/> class using a hash algorithim, a key, and salt to derive the internal key.
/// </summary>
/// <param name="hashAlgorithm">The HMAC hash implementation to use.</param>
/// <param name="key">The key used to derive the internal key.</param>
/// <param name="salt">The salt used to derive the internal key.</param>
/// <exception cref="ArgumentNullException">The hashAlgorithim or key is null.</exception>
public Rfc5869DeriveBytes(HMACAlgorithmName hashAlgorithm, byte[] key, byte[] salt = null)
{
if (hashAlgorithm == null)
{
throw new ArgumentNullException(nameof(hashAlgorithm));
}
Initialize(HMAC.Create(hashAlgorithm.Name), key, salt);
}
/// <summary>
/// Initializes a new instance of the <see cref="Rfc5869DeriveBytes"/> class using an HMAC, a key, and salt to derive the internal key.
/// </summary>
/// <param name="hmac">The HMAC implementation to use.</param>
/// <param name="key">The key used to derive the internal key.</param>
/// <param name="salt">The salt used to derive the internal key.</param>
/// <exception cref="ArgumentNullException">The hmac or key is null.</exception>
public Rfc5869DeriveBytes(HMAC hmac, byte[] key, byte[] salt = null)
{
if (hmac == null)
{
throw new ArgumentNullException(nameof(hmac));
}
Initialize(hmac, key, salt);
}
private void Initialize(HMAC hmac, byte[] key, byte[] salt)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
_hmac = hmac;
_hmac.Key = salt ?? new byte[_hmac.HashSize / 8];
_hmac.Key = _hmac.ComputeHash(key);
_hashLength = _hmac.HashSize / 8;
}
#endregion
#region " Derivation "
/// <summary>
/// Returns the pseudo-random key for this object.
/// </summary>
/// <param name="cb">The number of pseudo-random key bytes to generate.</param>
/// <param name="info">Optional context and application specific information.</param>
/// <returns>A byte array filled with pseudo-random key bytes.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="cb"/> is out of range. This parameter requires a non-negative number less than <see cref="MaxLength"/>.</exception>
public byte[] GetBytes(int cb, byte[] info = null)
{
if (cb < 0 || cb > this.MaxLength)
{
throw new ArgumentOutOfRangeException(nameof(cb));
}
if (info == null)
{
info = new byte[0];
}
byte[] hash = new byte[0];
byte[] output = new byte[cb];
byte[] buffer = new byte[info.Length + 1];
int bytesToCopy = 0;
int bytesCopied = 0;
int bufferSize = _hashLength + info.Length + 1;
byte counter = 1;
while (bytesCopied < cb)
{
Buffer.BlockCopy(hash, 0, buffer, 0, hash.Length);
Buffer.BlockCopy(info, 0, buffer, hash.Length, info.Length);
buffer[buffer.Length - 1] = counter++;
hash = _hmac.ComputeHash(buffer);
if (buffer.Length < bufferSize)
{
buffer = new byte[bufferSize];
}
bytesToCopy = Math.Min(hash.Length, cb - bytesCopied);
Buffer.BlockCopy(hash, 0, output, bytesCopied, bytesToCopy);
bytesCopied += bytesToCopy;
}
return output;
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment