Last active
July 17, 2018 22:47
-
-
Save rjmholt/2d931a712d9b12443f1e3a1db57d088e to your computer and use it in GitHub Desktop.
C# snippet for doing HMAC-based authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Security.Cryptography; | |
using System.Text; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol | |
{ | |
public class AuthenticationValidator | |
{ | |
/// <summary> | |
/// A fast byte array comparer for tracking seen nonces. | |
/// </summary> | |
private static readonly IEqualityComparer<byte[]> s_fastComparer = new FastByteArrayComparer(); | |
/// <summary> | |
/// A slow byte array comparer for safely comparing HMAC hashes. | |
/// </summary> | |
private static readonly IEqualityComparer<byte[]> s_slowComparer = new SlowByteArrayComparer(); | |
private HashAlgorithmName _hashAlgorithmName; | |
private byte[] _secretKey; | |
private readonly HashSet<byte[]> _usedNonces; | |
public AuthenticationValidator(HashAlgorithmName hashAlgorithm, byte[] secretKey) | |
{ | |
_hashAlgorithmName = hashAlgorithm; | |
_secretKey = new byte[secretKey.Length]; | |
secretKey.CopyTo(_secretKey, 0); | |
} | |
public byte[] IssueChallenge(int size) | |
{ | |
using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) | |
{ | |
byte[] challenge = new byte[size]; | |
rng.GetBytes(challenge); | |
return challenge; | |
} | |
} | |
/// <summary> | |
/// Validate a message in a given encoding against its accompanying authorization header. | |
/// </summary> | |
public bool ValidateAuthentication(byte[] receivedHmacHash, params byte[][] msgParts) | |
{ | |
using (HMAC hmac = HMAC.Create(_hashAlgorithmName.Name)) | |
{ | |
hmac.Key = _secretKey; | |
byte[] computedHash = hmac.ComputeHash(ConcatBytes(msgParts)); | |
return s_slowComparer.Equals(receivedHmacHash, computedHash); | |
} | |
} | |
private static byte[] ConcatBytes(IEnumerable<byte[]> byteArrs) | |
{ | |
int totalSize = 0; | |
foreach (byte[] arr in byteArrs) | |
{ | |
totalSize += arr.Length; | |
} | |
byte[] destArr = new byte[totalSize]; | |
int insertIdx = 0; | |
foreach (byte[] arr in byteArrs) | |
{ | |
arr.CopyTo(destArr, insertIdx); | |
insertIdx += arr.Length; | |
} | |
return destArr; | |
} | |
} | |
/// <summary> | |
/// A fast-failing equality comparer for byte arrays. | |
/// </summary> | |
public class FastByteArrayComparer : IEqualityComparer<byte[]> | |
{ | |
/// <summary> | |
/// Perform a fast equality comparison of two byte arrays. | |
/// </summary> | |
/// <param name="x">First array to compare.</param> | |
/// <param name="y">Second array to compare.</param> | |
/// <returns>True if the two arrays are equal, false otherwise.</returns> | |
public bool Equals(byte[] x, byte[] y) | |
{ | |
if (x == y) | |
{ | |
return true; | |
} | |
if (x == null || y == null) | |
{ | |
return false; | |
} | |
if (x.Length != y.Length) | |
{ | |
return false; | |
} | |
for (int i = 0; i < x.Length; i++) | |
{ | |
if (x[i] != y[i]) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
/// <summary> | |
/// Get the hash code of an array of bytes. | |
/// </summary> | |
/// <param name="array">The array of bytes to hash.</param> | |
/// <returns>A hash code that is always the same for a byte array of the same values.</returns> | |
public int GetHashCode(byte[] array) | |
{ | |
// Adapted from Jon Skeet's answer at | |
// https://stackoverflow.com/a/7244729 | |
unchecked | |
{ | |
if (array == null) | |
{ | |
return 0; | |
} | |
int hash = 17; | |
foreach (byte b in array) | |
{ | |
hash = hash * 31 + b.GetHashCode(); | |
} | |
return hash; | |
} | |
} | |
} | |
/// <summary> | |
/// A byte array comparer intended to make timing attacks difficult. | |
/// </summary> | |
public class SlowByteArrayComparer : IEqualityComparer<byte[]> | |
{ | |
/// <summary> | |
/// A fast array comparer to use for hash codes. | |
/// </summary> | |
private static readonly FastByteArrayComparer s_fastComparer = new FastByteArrayComparer(); | |
/// <summary> | |
/// Compare two arrays for equality, with the number of operations depending | |
/// on the length of the longest array only (and not on the number of equal elements). | |
/// </summary> | |
/// <param name="x">First array to compare.</param> | |
/// <param name="y">Second array to compare.</param> | |
/// <returns>True if the arrays are equal, false otherwise.</returns> | |
public bool Equals(byte[] x, byte[] y) | |
{ | |
// We want to do an operation for every element in the longer array | |
byte[] longerHash; | |
byte[] shorterHash; | |
if (x.Length >= y.Length) | |
{ | |
longerHash = x; | |
shorterHash = y; | |
} | |
else | |
{ | |
longerHash = y; | |
shorterHash = x; | |
} | |
// Now roll down the arrays and flip the accumulator | |
// bit if two array elements differ or if the shorter | |
// one runs out of elements | |
int rollingXor = 0; | |
for (int i = 0; i < longerHash.Length; i++) | |
{ | |
if (i >= shorterHash.Length) | |
{ | |
rollingXor |= 1; | |
continue; | |
} | |
rollingXor |= longerHash[i] ^ shorterHash[i]; | |
} | |
// The arrays are equal iff the bit is not flipped | |
return rollingXor == 0; | |
} | |
/// <summary> | |
/// Get the hash code of a byte array using the fast comparer's method. | |
/// </summary> | |
/// <param name="array">A byte array to get the hash code of.</param> | |
/// <returns>A hash code that is the same for all byte arrays with the same values in the same order.</returns> | |
public int GetHashCode(byte[] array) | |
{ | |
return s_fastComparer.GetHashCode(array); | |
} | |
} | |
/// <summary> | |
/// Exception to represent errors that occur with the authentication process | |
/// </summary> | |
public class AuthenticationException : Exception | |
{ | |
/// <summary> | |
/// Create an authentication exception with just a message. | |
/// </summary> | |
/// <param name="message">A message describing the circumstances of the authentication exception.</param> | |
public AuthenticationException(string message) | |
: base(message) | |
{ | |
} | |
/// <summary> | |
/// Create an authentication with a message after a lower level exception has occurred. | |
/// </summary> | |
/// <param name="message">A message describing the circumstances of the authentication exception.</param> | |
/// <param name="innerException">The lower level exception that lead to the authentication exception being thrown.</param> | |
public AuthenticationException(string message, Exception innerException) | |
: base(message, innerException) | |
{ | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment