Implemented a WDigest hash calculator

This commit is contained in:
Michael Grafnetter 2018-07-14 19:27:41 +02:00
parent 47da723143
commit b44d022c38
4 changed files with 222 additions and 0 deletions

View File

@ -0,0 +1,54 @@
namespace DSInternals.Common.Cryptography.Test
{
using DSInternals.Common;
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security;
[TestClass]
public class WDigestHashTester
{
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void WDigestHash_NullInput()
{
var hashes = WDigestHash.ComputeHash(null, null, null, null, null);
}
[TestMethod]
public void WDigestHash_TestVector1()
{
var hashes = WDigestHash.ComputeHash(@"Pa$$w0rd".ToSecureString(), null, "Administrator", "ADATUM", "Adatum.com");
Assert.AreEqual(WDigestHash.HashCount, hashes.Length);
Assert.AreEqual("3f602ffdac6b06655481a06cce08adf3", hashes[0].ToHex());
Assert.AreEqual("971256a81b883974fcbcf369f67ecf61", hashes[1].ToHex());
Assert.AreEqual("ec3d57e1caf31b9cd0fc1e3bf913bbf6", hashes[2].ToHex());
Assert.AreEqual("3f602ffdac6b06655481a06cce08adf3", hashes[3].ToHex());
Assert.AreEqual("30c8584ec6a52de03fead6e66443b896", hashes[4].ToHex());
Assert.AreEqual("da9aab75d2ad8152daedc92aea4e49ac", hashes[5].ToHex());
Assert.AreEqual("eb781ca6bf2d4f3a30bc8f8ba360b1a0", hashes[6].ToHex());
Assert.AreEqual("b70f192a3ee11e47bc1408e18835210e", hashes[7].ToHex());
Assert.AreEqual("afebaf56c0b704ab12276306c3524d9f", hashes[8].ToHex());
Assert.AreEqual("a4d5b0be4785b495b4b53e70c35d4990", hashes[9].ToHex());
Assert.AreEqual("1167280ac9a9bc1ec1b49d171a8e9b62", hashes[10].ToHex());
Assert.AreEqual("e17ff031d74e32e67ec1f1b6e0b2c26b", hashes[11].ToHex());
Assert.AreEqual("379c029a72f4d0e649e7aa6ad659dbe0", hashes[12].ToHex());
Assert.AreEqual("f7fcfe4c5ec50f9db38577cea6b99f28", hashes[13].ToHex());
Assert.AreEqual("ffde532cbd677b5562ce4455099cdafd", hashes[14].ToHex());
Assert.AreEqual("478561ada3cecab0094ea704b90b1c84", hashes[15].ToHex());
Assert.AreEqual("51ca360a2a2d35b184a0c78fd0432137", hashes[16].ToHex());
Assert.AreEqual("f7841c8b57a9a65875ee40a39ba270cb", hashes[17].ToHex());
Assert.AreEqual("3b8dc2b699160e41ce1c8f1cdacd0c2c", hashes[18].ToHex());
Assert.AreEqual("132d33c113262bd560b40c39d1cac291", hashes[19].ToHex());
Assert.AreEqual("8b8c4f339d3736ede2c4c0bc4e53f3ff", hashes[20].ToHex());
Assert.AreEqual("209ae3b1dc6789f90f352e2ff273111b", hashes[21].ToHex());
Assert.AreEqual("9b22dae245b82c3e55061265b288607c", hashes[22].ToHex());
Assert.AreEqual("bb1edeb4c9b818d829800192a1528a88", hashes[23].ToHex());
Assert.AreEqual("f78b1d41fe8372506e31461625743eb4", hashes[24].ToHex());
Assert.AreEqual("dd829e0cda63ba68f95f98b1750885f6", hashes[25].ToHex());
Assert.AreEqual("177eeed7b3cf4df4e114c4ca91beafcd", hashes[26].ToHex());
Assert.AreEqual("9c0d49ddf1ac7cff9d1e6f96cc8a55d5", hashes[27].ToHex());
Assert.AreEqual("8da34a0b94604b06cd58f9bf69142383", hashes[28].ToHex());
}
}
}

View File

@ -54,6 +54,7 @@
</Choose>
<ItemGroup>
<Compile Include="ByteArrayExtensionsTester.cs" />
<Compile Include="Cryptography\WDigestHashTester.cs" />
<Compile Include="DPAPIBackupKeyTester.cs" />
<Compile Include="Cryptography\GPPrefPwdObfuscatorTester.cs" />
<Compile Include="Cryptography\LMHashTester.cs" />

View File

@ -0,0 +1,166 @@
namespace DSInternals.Common.Cryptography
{
using DSInternals.Common;
using System;
using System.Collections.Generic;
using System.Security;
using System.Security.Cryptography;
using System.Text;
/// <summary>
/// Calculates hash forms that are used in the digest authentication protocols.
/// </summary>
/// <see>https://msdn.microsoft.com/en-us/library/cc245680.aspx</see>
public static class WDigestHash
{
/// <summary>
/// The size, in bytes, of the computed hash code.
/// </summary>
public const int HashSize = 16;
/// <summary>
/// Count of MD5 hashes that are calculated.
/// </summary>
public const int HashCount = 29;
private const string MagicRealm = "Digest";
// All strings are converted to ISO Latin I code page prior to the hashing
private const string StringEncodingName = "ISO-8859-1";
private static readonly Encoding StringEncoder = Encoding.GetEncoding(StringEncodingName);
/// <summary>
/// Calculates WDigest hashes of a password.
/// </summary>
/// <param name="password">User's password.</param>
/// <param name="userPrincipalName">The userPrincipalName attribute value.</param>
/// <param name="samAccountName">The sAMAccountName attribute value.</param>
/// <param name="netBiosDomainName">The name attribute of the account domain object.</param>
/// <param name="dnsDomainName">The fully qualified domain name (FQDN) of the domain.</param>
/// <returns>29 MD5 hashes.</returns>
/// <remarks>SecureString is copied into managed memory while calculating the hashes, which is not the best way to deal with it.</remarks>
public static byte[][] ComputeHash(SecureString password, string userPrincipalName, string samAccountName, string netBiosDomainName, string dnsDomainName)
{
// Validate the input
Validator.AssertNotNull(password, "password");
Validator.AssertNotNullOrWhiteSpace(samAccountName, "samAccountName");
Validator.AssertNotNullOrWhiteSpace(netBiosDomainName, "netBiosDomainName");
Validator.AssertNotNullOrWhiteSpace(dnsDomainName, "dnsDomainName");
// Note that a user does not need to have a UPN.
if(String.IsNullOrEmpty(userPrincipalName))
{
// Construct the UPN as samAccountName@dnsDomainName
userPrincipalName = String.Format(@"{0}@{1}", samAccountName, dnsDomainName);
}
// Construct the pre-Windows 2000 logon name as netBiosDomainName\samAccountName
string logonName = String.Format(@"{0}\{1}", netBiosDomainName, samAccountName);
// List of the resulting 29 hashes
var result = new List<byte[]>(HashCount);
using (var md5 = new MD5Cng())
{
// Hash1: MD5(sAMAccountName, NETBIOSDomainName, password)
result.Add(md5.ComputeHash(GetBytes(samAccountName, netBiosDomainName, password)));
// Hash2: MD5(LOWER(sAMAccountName), LOWER(NETBIOSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToLower(), netBiosDomainName.ToLower(), password)));
// Hash3: MD5(UPPER(sAMAccountName), UPPER(NETBIOSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToUpper(), netBiosDomainName.ToUpper(), password)));
// Hash4: MD5(sAMAccountName, UPPER(NETBIOSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName, netBiosDomainName.ToUpper(), password)));
// Hash5: MD5(sAMAccountName, LOWER(NETBIOSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName, netBiosDomainName.ToLower(), password)));
// Hash6: MD5(UPPER(sAMAccountName), LOWER(NETBIOSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToUpper(), netBiosDomainName.ToLower(), password)));
// Hash7: MD5(LOWER(sAMAccountName), UPPER(NETBIOSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToLower(), netBiosDomainName.ToUpper(), password)));
// Hash8: MD5(sAMAccountName, DNSDomainName, password)
result.Add(md5.ComputeHash(GetBytes(samAccountName, dnsDomainName, password)));
// Hash9: MD5(LOWER(sAMAccountName), LOWER(DNSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToLower(), dnsDomainName.ToLower(), password)));
// Hash10: MD5(UPPER(sAMAccountName), UPPER(DNSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToUpper(), dnsDomainName.ToUpper(), password)));
// Hash11: MD5(sAMAccountName, UPPER(DNSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName, dnsDomainName.ToUpper(), password)));
// Hash12: MD5(sAMAccountName, LOWER(DNSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName, dnsDomainName.ToLower(), password)));
// Hash13: MD5(UPPER(sAMAccountName), LOWER(DNSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToUpper(), dnsDomainName.ToLower(), password)));
// Hash14: MD5(LOWER(sAMAccountName), UPPER(DNSDomainName), password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToLower(), dnsDomainName.ToUpper(), password)));
// Hash15: MD5(userPrincipalName, password)
result.Add(md5.ComputeHash(GetBytes(userPrincipalName, String.Empty, password)));
// Hash16: MD5(LOWER(userPrincipalName), password)
result.Add(md5.ComputeHash(GetBytes(userPrincipalName.ToLower(), String.Empty, password)));
// Hash17: MD5(UPPER(userPrincipalName), password)
result.Add(md5.ComputeHash(GetBytes(userPrincipalName.ToUpper(), String.Empty, password)));
// Hash18: MD5(NETBIOSDomainName\sAMAccountName, password)
result.Add(md5.ComputeHash(GetBytes(logonName, password)));
// Hash19: MD5(LOWER(NETBIOSDomainName\sAMAccountName), password)
result.Add(md5.ComputeHash(GetBytes(logonName.ToLower(), password)));
// Hash20: MD5(UPPER(NETBIOSDomainName\sAMAccountName), password)
result.Add(md5.ComputeHash(GetBytes(logonName.ToUpper(), password)));
// Hash21: MD5(sAMAccountName, "Digest", password)
result.Add(md5.ComputeHash(GetBytes(samAccountName, MagicRealm, password)));
// Hash22: MD5(LOWER(sAMAccountName), "Digest", password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToLower(), MagicRealm, password)));
// Hash23: MD5(UPPER(sAMAccountName), "Digest", password)
result.Add(md5.ComputeHash(GetBytes(samAccountName.ToUpper(), MagicRealm, password)));
// Hash24: MD5(userPrincipalName, "Digest", password)
result.Add(md5.ComputeHash(GetBytes(userPrincipalName, MagicRealm, password)));
// Hash25: MD5(LOWER(userPrincipalName), "Digest", password)
result.Add(md5.ComputeHash(GetBytes(userPrincipalName.ToLower(), MagicRealm, password)));
// Hash26: MD5(UPPER(userPrincipalName), "Digest", password)
result.Add(md5.ComputeHash(GetBytes(userPrincipalName.ToUpper(), MagicRealm, password)));
// Hash27: MD5(NETBIOSDomainName\sAMAccountName, "Digest", password)
result.Add(md5.ComputeHash(GetBytes(logonName, MagicRealm, password)));
// Hash28: MD5(LOWER(NETBIOSDomainName\sAMAccountName), "Digest", password)
result.Add(md5.ComputeHash(GetBytes(logonName.ToLower(), MagicRealm, password)));
// Hash29: MD5(UPPER(NETBIOSDomainName\sAMAccountName), "Digest", password)
result.Add(md5.ComputeHash(GetBytes(logonName.ToUpper(), MagicRealm, password)));
}
return result.ToArray();
}
private static byte[] GetBytes(string userName, SecureString password)
{
return GetBytes(userName, String.Empty, password);
}
private static byte[] GetBytes(string userName, string realm, SecureString password)
{
var concatenatedString = String.Format("{0}:{1}:{2}", userName, realm, password.ToUnicodeString());
return StringEncoder.GetBytes(concatenatedString);
}
}
}

View File

@ -42,6 +42,7 @@
<Compile Include="ADSI\AdsiClient.cs" />
<Compile Include="ADSI\AdsiObjectAdapter.cs" />
<Compile Include="Cryptography\HashEqualityComparer.cs" />
<Compile Include="Cryptography\WDigestHash.cs" />
<Compile Include="Cryptography\Pbkdf2.cs" />
<Compile Include="Cryptography\PrivateKeyEncryptionType.cs" />
<Compile Include="Data\DPAPI\DPAPIObject.cs" />