using System; using System.Security.Cryptography; using System.Text; namespace S22.Sasl.Mechanisms.Ntlm { /// /// Contains methods for calculating the various Type 3 challenge /// responses. /// internal static class Responses { /// /// Computes the LM-response to the challenge sent as part of an /// NTLM type 2 message. /// /// The challenge sent by the server. /// The user account password. /// An array of bytes representing the response to the /// specified challenge. internal static byte[] ComputeLMResponse(byte[] challenge, string password) { byte[] lmHash = LMHash(password); return LMResponse(lmHash, challenge); } /// /// Computes the NTLM-response to the challenge sent as part of an /// NTLM type 2 message. /// /// The challenge sent by the server. /// The user account password. /// An array of bytes representing the response to the /// specified challenge. internal static byte[] ComputeNtlmResponse(byte[] challenge, string password) { byte[] ntlmHash = NtlmHash(password); return LMResponse(ntlmHash, challenge); } /// /// Computes the NTLMv2-response to the challenge sent as part of an /// NTLM type 2 message. /// /// The name of the authentication target. /// The user account name to authenticate with. /// The user account password. /// The target information block from /// the NTLM type 2 message. /// The challenge sent by the server. /// A random 8-byte client nonce. /// An array of bytes representing the response to the /// specified challenge. internal static byte[] ComputeNtlmv2Response(string target, string username, string password, byte[] targetInformation, byte[] challenge, byte[] clientNonce) { byte[] ntlmv2Hash = Ntlmv2Hash(target, username, password), blob = CreateBlob(targetInformation, clientNonce); return LMv2Response(ntlmv2Hash, blob, challenge); } /// /// Computes the LMv2-response to the challenge sent as part of an /// NTLM type 2 message. /// /// The name of the authentication target. /// The user account to authenticate with. /// The user account password. /// The challenge sent by the server. /// A random 8-byte client nonce. /// An array of bytes representing the response to the /// specified challenge. internal static byte[] ComputeLMv2Response(string target, string username, string password, byte[] challenge, byte[] clientNonce) { byte[] ntlmv2Hash = Ntlmv2Hash(target, username, password); return LMv2Response(ntlmv2Hash, clientNonce, challenge); } /// /// Creates the LM Hash of the specified password. /// /// The password to create the LM Hash of. /// The LM Hash of the given password, used in the calculation /// of the LM Response. /// Thrown if the password argument /// is null. private static byte[] LMHash(string password) { // Precondition: password != null. password.ThrowIfNull("password"); byte[] oemPassword = Encoding.ASCII.GetBytes(password.ToUpperInvariant()), magic = new byte[] { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 }, // This is the pre-encrypted magic value with a null DES key. nullEncMagic = { 0xAA, 0xD3, 0xB4, 0x35, 0xB5, 0x14, 0x04, 0xEE }, keyBytes = new byte[14], lmHash = new byte[16]; int length = Math.Min(oemPassword.Length, 14); Array.Copy(oemPassword, keyBytes, length); byte[] lowKey = CreateDESKey(keyBytes, 0), highKey = CreateDESKey(keyBytes, 7); using (DES des = DES.Create("DES")) { byte[] output = new byte[8]; des.Mode = CipherMode.ECB; // Note: In .NET DES cannot accept a weak key. This can happen for // an empty password or if the password is shorter than 8 characters. if (password.Length < 1) { Buffer.BlockCopy(nullEncMagic, 0, lmHash, 0, 8); } else { des.Key = lowKey; using (var encryptor = des.CreateEncryptor()) { encryptor.TransformBlock(magic, 0, magic.Length, lmHash, 0); } } if (password.Length < 8) { Buffer.BlockCopy(nullEncMagic, 0, lmHash, 8, 8); } else { des.Key = highKey; using (var encryptor = des.CreateEncryptor()) { encryptor.TransformBlock(magic, 0, magic.Length, lmHash, 8); } } return lmHash; } } /// /// Creates a DES encryption key from the specified key material. /// /// The key material to create the DES encryption /// key from. /// An offset into the byte array at which to /// extract the key material from. /// A 56-bit DES encryption key as an array of bytes. private static byte[] CreateDESKey(byte[] bytes, int offset) { byte[] keyBytes = new byte[7]; Array.Copy(bytes, offset, keyBytes, 0, 7); byte[] material = new byte[8]; material[0] = keyBytes[0]; material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >> 1); material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >> 2); material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >> 3); material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >> 4); material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >> 5); material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >> 6); material[7] = (byte) (keyBytes[6] << 1); return OddParity(material); } /// /// Applies odd parity to the specified byte array. /// /// The byte array to apply odd parity to. /// A reference to the byte array. private static byte[] OddParity(byte[] bytes) { for (int i = 0; i < bytes.Length; i++) { byte b = bytes[i]; bool needsParity = (((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ (b >> 1)) & 0x01) == 0; if (needsParity) bytes[i] |= (byte) 0x01; else bytes[i] &= (byte) 0xFE; } return bytes; } /// /// Creates the LM Response from the specified hash and Type 2 challenge. /// /// An LM or NTLM hash. /// The server challenge from the Type 2 /// message. /// The challenge response as an array of bytes. /// Thrown if the hash or the /// challenge parameter is null. private static byte[] LMResponse(byte[] hash, byte[] challenge) { hash.ThrowIfNull("hash"); challenge.ThrowIfNull("challenge"); byte[] keyBytes = new byte[21], lmResponse = new byte[24]; Array.Copy(hash, 0, keyBytes, 0, 16); byte[] lowKey = CreateDESKey(keyBytes, 0), middleKey = CreateDESKey(keyBytes, 7), highKey = CreateDESKey(keyBytes, 14); using (DES des = DES.Create("DES")) { des.Mode = CipherMode.ECB; des.Key = lowKey; using (var encryptor = des.CreateEncryptor()) { encryptor.TransformBlock(challenge, 0, challenge.Length, lmResponse, 0); } des.Key = middleKey; using (var encryptor = des.CreateEncryptor()) { encryptor.TransformBlock(challenge, 0, challenge.Length, lmResponse, 8); } des.Key = highKey; using (var encryptor = des.CreateEncryptor()) { encryptor.TransformBlock(challenge, 0, challenge.Length, lmResponse, 16); } return lmResponse; } } /// /// Creates the NTLM Hash of the specified password. /// /// The password to create the NTLM hash of. /// The NTLM hash for the specified password. /// Thrown if the password /// parameter is null. private static byte[] NtlmHash(String password) { password.ThrowIfNull("password"); byte[] data = Encoding.Unicode.GetBytes(password); using (MD4 md4 = new MD4()) { return md4.ComputeHash(data); } } /// /// Creates the NTLMv2 Hash of the specified target, username /// and password values. /// /// The name of the authentication target as is /// specified in the target name field of the NTLM type 3 message. /// The user account name. /// The password for the user account. /// The NTLMv2 hash for the specified input values. /// Thrown if the username or /// the password parameter is null. private static byte[] Ntlmv2Hash(string target, string username, string password) { username.ThrowIfNull("username"); password.ThrowIfNull("password"); byte[] ntlmHash = NtlmHash(password); string identity = username.ToUpperInvariant() + target; using (var hmac = new HMACMD5(ntlmHash)) return hmac.ComputeHash(Encoding.Unicode.GetBytes(identity)); } /// /// Returns the current time as the number of tenths of a microsecond /// since January 1, 1601. /// /// The current time as the number of tenths of a microsecond /// since January 1, 1601. private static long GetTimestamp() { return DateTime.Now.ToFileTimeUtc(); } /// /// Creates the "blob" data block which is part of the NTLMv2 challenge /// response. /// /// The target information block from /// the NTLM type 2 message. /// A random 8-byte client nonce. /// The blob, used in the calculation of the NTLMv2 Response. private static byte[] CreateBlob(byte[] targetInformation, byte[] clientNonce) { return new ByteBuilder() .Append(0x00000101) .Append(0) .Append(GetTimestamp()) .Append(clientNonce) .Append(0) .Append(targetInformation) .Append(0) .ToArray(); } /// /// Creates the LMv2 Response from the given NTLMv2 hash, client data, and /// Type 2 challenge. /// /// The NTLMv2 Hash. /// The client data (blob or client nonce). /// The server challenge from the Type 2 message. /// The response which is either for NTLMv2 or LMv2, depending /// on the client data. private static byte[] LMv2Response(byte[] hash, byte[] clientData, byte[] challenge) { byte[] data = new ByteBuilder() .Append(challenge) .Append(clientData) .ToArray(); using (var hmac = new HMACMD5(hash)) { return new ByteBuilder() .Append(hmac.ComputeHash(data)) .Append(clientData) .ToArray(); } } } }