1# Copyright: (c) 2020, Jordan Borean (@jborean93) <jborean93@gmail.com> 2# MIT License (see LICENSE or https://opensource.org/licenses/MIT) 3 4"""NTLM Cryptographic Operations 5 6Implements the various cryptographic operations that are defined in `MS-NLMP Crypto Operations`_. Some crypto 7operations aren't implemented due to it being a simple operation in Python. 8 9.. _MS-NLMP Crypto Operations 10 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/26c42637-9549-46ae-be2e-90f6f1360193 11""" 12 13import base64 14import binascii 15import hashlib 16import hmac 17import io 18import re 19import struct 20import typing 21 22from cryptography.hazmat.backends import default_backend 23from cryptography.hazmat.primitives.ciphers import Cipher, algorithms 24 25from spnego._ntlm_raw.des import DES 26from spnego._ntlm_raw.messages import ( 27 FileTime, 28 NegotiateFlags, 29 NTClientChallengeV2, 30 TargetInfo, 31) 32 33# A user does not need to specify their actual plaintext password they can specify the LM and NT hash (from lmowfv1 and 34# ntowfv2) in the form 'lm_hash_hex:nt_hash_hex'. This is still considered a plaintext pass as we can use it to build 35# the LM and NT response but it's only usable for NTLM. 36_NTLM_HASH_PATTERN = re.compile(r'^[a-fA-F0-9]{32}:[a-fA-F0-9]{32}$') 37 38 39class RC4Handle: 40 """ RC4 class to wrap the underlying crypto function. """ 41 42 def __init__(self, key: bytes) -> None: 43 self._key = key 44 self._handle = None 45 self.reset() 46 47 def update(self, b_data: bytes) -> bytes: 48 """ Update the RC4 stream and return the encrypted/decrypted bytes. """ 49 return self._handle.update(b_data) 50 51 def reset(self) -> None: 52 """ Reset's the cipher stream back to the original state. """ 53 arc4 = algorithms.ARC4(self._key) 54 cipher = Cipher(arc4, mode=None, backend=default_backend()) 55 self._handle = cipher.encryptor() 56 57 58def is_ntlm_hash(password: str) -> bool: 59 return bool(_NTLM_HASH_PATTERN.match(password)) 60 61 62def compute_response_v1( 63 flags: int, 64 nt_hash: bytes, 65 lm_hash: bytes, 66 server_challenge: bytes, 67 client_challenge: bytes, 68 no_lm_response: bool = True, 69) -> typing.Tuple[bytes, bytes, bytes]: 70 """Compute NT and LM Response for NTLMv1. 71 72 Computes the NT and LM Response for NTLMv1 messages. The response is dependent on the flags that were negotiated 73 between the client and server. 74 75 The pseudo-code for this function as documented under `NTLM v1 Authentication`_ is:: 76 77 Define ComputeResponse(NegFlg, ResponseKeyNT, ResponseKeyLM, CHALLENGE_MESSAGE.ServerChallenge, 78 ClientChallenge, Time, ServerName) As 79 80 If (User is set to "" AND Passwd is set to "") 81 -- Special case for anonymous authentication 82 Set NtChallengeResponseLen to 0 83 Set NtChallengeResponseMaxLen to 0 84 Set NtChallengeResponseBufferOffset to 0 85 86 Set LmChallengeResponse to Z(1) 87 ElseIf 88 If (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is set in NegFlg) 89 Set NtChallengeResponse to DESL(ResponseKeyNT, 90 MD5(ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge, ClientChallenge))[0..7]) 91 92 Set LmChallengeResponse to ConcatenationOf{ClientChallenge, Z(16)} 93 Else 94 Set NtChallengeResponse to DESL(ResponseKeyNT, CHALLENGE_MESSAGE.ServerChallenge) 95 96 If (NoLMResponseNTLMv1 is TRUE) 97 Set LmChallengeResponse to NtChallengeResponse 98 Else 99 Set LmChallengeResponse to DESL(ResponseKeyLM, CHALLENGE_MESSAGE.ServerChallenge) 100 EndIf 101 EndIf 102 EndIf 103 104 Set SessionBaseKey to MD4(NTOWF) 105 106 Args: 107 flags: The negotiated flags between the initiator and acceptor. 108 nt_hash: The response key computed by :meth:`ntowfv1`. 109 lm_hash: The response key computed by :meth:`lmowfv1`. 110 server_challenge: The 8 byte nonce generated by the acceptor. 111 client_challenge: The 8 byte nonce generated by the initiator. 112 no_lm_response: Whether to compute (True) the `LmChallengeResponse` or not (False) when extended session 113 security was not negotiated. 114 115 Returns: 116 Tuple[bytes, bytes, bytes]: Returns the NTChallengeResponse, LMChallengeResponse and KeyExchangeKey. 117 118 .. _NTLM v1 Authentication: 119 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5 120 """ 121 if flags & NegotiateFlags.extended_session_security: 122 nt_response = desl(nt_hash, md5(server_challenge + client_challenge[:8])) 123 lm_response = client_challenge + (b"\x00" * 16) 124 125 else: 126 nt_response = lm_response = desl(nt_hash, server_challenge) 127 128 if not no_lm_response: 129 lm_response = desl(lm_hash, server_challenge) 130 131 session_base_key = md4(nt_hash) 132 key_exchange_key = kxkey(flags, session_base_key, lm_hash, lm_response, server_challenge) 133 134 return nt_response, lm_response, key_exchange_key 135 136 137def compute_response_v2( 138 nt_hash: bytes, 139 server_challenge: bytes, 140 client_challenge: bytes, 141 time: FileTime, 142 av_pairs: TargetInfo, 143) -> typing.Tuple[bytes, bytes, bytes]: 144 """Compute NT and LM Response for NTLMv2. 145 146 Computes the NT and LM Response for NTLMv2 messages. The response is dependent on the flags that were negotiated 147 between the client and server. 148 149 The pseudo-code for this function as documented under `NTLM v2 Authentication`_ is:: 150 151 Define ComputeResponse(NegFlg, ResponseKeyNT, ResponseKeyLM, CHALLENGE_MESSAGE.ServerChallenge, 152 ClientChallenge, Time, ServerName) As 153 154 If (User is set to "" && Passwd is set to "") 155 -- Special case for anonymous authentication 156 Set NtChallengeResponseLen to 0 157 Set NtChallengeResponseMaxLen to 0 158 Set NtChallengeResponseBufferOffset to 0 159 160 Set LmChallengeResponse to Z(1) 161 Else 162 Set temp to ConcatenationOf(Responserversion, HiResponserversion, Z(6), Time, ClientChallenge, Z(4), 163 ServerName, Z(4)) 164 165 Set NTProofStr to HMAC_MD5(ResponseKeyNT, ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge,temp)) 166 167 Set NtChallengeResponse to ConcatenationOf(NTProofStr, temp) 168 169 Set LmChallengeResponse to ConcatenationOf( 170 HMAC_MD5(ResponseKeyLM, ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge, ClientChallenge)), 171 ClientChallenge) 172 EndIf 173 174 Set SessionBaseKey to HMAC_MD5(ResponseKeyNT, NTProofStr) 175 176 Args: 177 nt_hash: The response key computed by :meth:`ntwofv2`. The `ResponseKeyLM` is the same value so we only 178 pass in the 1 key. 179 server_challenge: The 8 byte nonce generated by the acceptor. 180 client_challenge: The 8 byte nonce generated by the initiator. 181 time: The FileTime to place in the NT hash. 182 av_pairs: The TargetInfo AvPairs fields that are placed in the Authenticate message. 183 184 Returns: 185 Tuple[bytes, bytes, bytes]: Returns the NTChallengeResponse, LMChallengeResponse and KeyExchangeKey. 186 187 .. _NTLM v2 Authentication: 188 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/5e550938-91d4-459f-b67d-75d70009e3f3 189 """ 190 temp = NTClientChallengeV2(time_stamp=time, client_challenge=client_challenge, av_pairs=av_pairs) 191 b_temp = temp.pack() + b"\x00\x00\x00\x00" 192 nt_proof_str = hmac_md5(nt_hash, server_challenge + b_temp) 193 194 nt_response = nt_proof_str + b_temp 195 lm_response = hmac_md5(nt_hash, server_challenge + client_challenge) + client_challenge 196 session_base_key = hmac_md5(nt_hash, nt_proof_str) 197 198 return nt_response, lm_response, session_base_key # Is KeyExchangeKey in NTLMv2. 199 200 201def crc32(m: bytes) -> bytes: 202 """ Simple wrapper function to generate a CRC32 checksum. """ 203 # We bitand to ensure the value is the same across all Python versions. 204 return struct.pack("<I", binascii.crc32(m) & 0xFFFFFFFF) 205 206 207def des(k: bytes, d: bytes) -> bytes: 208 """DES encryption. 209 210 Indicates the encryption of an 8-byte data item `d` with the 7-byte key `k` using the Data Encryption Standard 211 (DES) algorithm in Electronic Codebook (ECB) mode. The result is 8 bytes in length ([FIPS46-2]). 212 213 Args: 214 k: The 7-byte key to use in the DES cipher. 215 d: The 8-byte data block to encrypt. 216 217 Returns: 218 bytes: The encrypted data block. 219 """ 220 return DES(DES.key56_to_key64(k)).encrypt(d) 221 222 223def desl(k: bytes, d: bytes) -> bytes: 224 """Encryption using the DES Long algorithm. 225 226 Indicates the encryption of an 8-byte data item `d` with the 16-byte key `k` using the Data Encryption 227 Standard Long (DESL) algorithm. The result is 24 bytes in length. 228 229 `DESL(K, D)` as by MS-NLMP `DESL`_ is computed as follows:: 230 231 ConcatenationOf( 232 DES(K[0..6], D), 233 DES(K[7..13], D), 234 DES(ConcatenationOf(K[14..15], Z(5)), D), 235 ); 236 237 Args: 238 k: The key to use for the DES cipher, will be truncated to 16 bytes and then padded to 21 bytes. 239 d: The value to run through the DESL algorithm, will be truncated to 8 bytes. 240 241 Returns: 242 bytes: The output of the DESL algorithm. 243 244 .. _DESL: 245 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/26c42637-9549-46ae-be2e-90f6f1360193 246 """ 247 k = k[:16].ljust(21, b"\x00") # Key needs to be stripped at 16 characters and then padded to 21 chars. 248 d = d[:8].ljust(8, b"\x00") # Data need to be at most 8 bytes long. 249 250 b_value = io.BytesIO() 251 252 b_value.write(des(k[:7], d)) 253 b_value.write(des(k[7:14], d)) 254 b_value.write(des(k[14:], d)) 255 256 return b_value.getvalue() 257 258 259def hmac_md5(key: bytes, data: bytes) -> bytes: 260 """ Simple wrapper function for a HMAC MD5 digest. """ 261 return hmac.new(key, data, digestmod=hashlib.md5).digest() 262 263 264def kxkey( 265 flags: int, 266 session_base_key: bytes, 267 lmowf: bytes, 268 lm_response: bytes, 269 server_challenge: bytes, 270) -> bytes: 271 """NTLM KXKEY function. 272 273 The MS-NLMP `KXKEY`_ function used to derive the key exchange key for a security context. This is only for NTLMv1 274 contexts as NTLMv2 just re-uses the session base key. 275 276 Args: 277 flags: The negotiate flags in the Challenge msg. 278 session_base_key: The session base key from :meth:`compute_response_v1`. 279 lmowf: The LM hash from :meth:`lmowfv1`. 280 lm_response: The lm response from :meth:`compute_response_v1`. 281 server_challenge: The server challenge in the Challenge msg. 282 283 Returns: 284 bytes: The derived key exchange key. 285 286 .. _KXKEY: 287 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/d86303b5-b29e-4fb9-b119-77579c761370 288 """ 289 if flags & NegotiateFlags.extended_session_security: 290 return hmac_md5(session_base_key, server_challenge + lm_response[:8]) 291 292 elif flags & NegotiateFlags.lm_key: 293 b_data = lm_response[:8] 294 return des(lmowf[:7], b_data) + des(lmowf[7:8] + b"\xBD\xBD\xBD\xBD\xBD\xBD", b_data) 295 296 elif flags & NegotiateFlags.non_nt_session_key: 297 return lmowf[:8] + b"\x00" * 8 298 299 else: 300 return session_base_key 301 302 303def lmowfv1(password: str) -> bytes: 304 """NTLMv1 LMOWFv1 function 305 306 The Lan Manager v1 one way function as documented under `NTLM v1 Authentication`_. 307 308 The pseudo-code for this function is:: 309 310 Define LMOWFv1(Passwd, User, UserDom) as 311 ConcatenationOf( 312 DES(UpperCase(Passwd)[0..6], "KGS!@#$%"), 313 DES(UpperCase(Passwd)[7..13], "KGS!@#$%"), 314 ); 315 316 Args: 317 password: The password for the user. 318 319 Returns: 320 bytes: The LMv1 one way hash of the user's password. 321 322 .. _NTLM v1 Authentication: 323 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5 324 """ 325 if is_ntlm_hash(password): 326 return base64.b16decode(password.split(':')[0].upper()) 327 328 # Fix the password to upper case and pad the length to exactly 14 bytes. While it is true LM only authentication 329 # will fail if the password exceeds 14 bytes typically it is used in conjunction with the NTv1 hash which has no 330 # such restrictions. 331 b_password = password.upper().encode('utf-8').ljust(14, b"\x00")[:14] 332 333 b_hash = io.BytesIO() 334 for start, end in [(0, 7), (7, 14)]: 335 b_hash.write(des(b_password[start:end], b'KGS!@#$%')) 336 337 return b_hash.getvalue() 338 339 340def md4(m: bytes) -> bytes: 341 """ Simple wrapper to generate a MD4 checksum. """ 342 return hashlib.new('md4', m).digest() 343 344 345def md5(m: bytes) -> bytes: 346 """ Simple wrapper to generate a MD5 checksum.""" 347 return hashlib.md5(m).digest() 348 349 350def ntowfv1(password: str) -> bytes: 351 """NTLMv1 NTOWFv1 function 352 353 The NT v1 one way function as documented under `NTLM v1 Authentication`_. 354 355 The pseudo-code for this function is:: 356 357 Define NTOWFv1(Passwd, User, UserDom) as MD4(UNICODE(Passwd)) 358 359 Args: 360 password: The password for the user. 361 362 Returns: 363 bytes: The NTv1 one way hash of the user's password. 364 365 .. _NTLM v1 Authentication: 366 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5 367 """ 368 if is_ntlm_hash(password): 369 return base64.b16decode(password.split(':')[1].upper()) 370 371 return md4(password.encode('utf-16-le')) 372 373 374def ntowfv2(username: str, nt_hash: bytes, domain_name: typing.Optional[str]) -> bytes: 375 """NTLMv2 NTOWFv2 function 376 377 The NT v2 one way function as documented under `NTLM v2 Authentication`_. 378 379 The pseudo-code for this function is:: 380 381 Define NTOWFv2(Passwd, User, UserDom) as 382 383 HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(ConcatenationOf(Uppercase(User), UserDom))) 384 385 Args: 386 username: The username. 387 nt_hash: The NT hash from :meth:`ntowfv1`. 388 domain_name: The optional domain name of the user. 389 390 Returns: 391 bytes: The NTv2 one way has of the user's credentials. 392 393 .. _NTLM v2 Authentication: 394 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/5e550938-91d4-459f-b67d-75d70009e3f3 395 """ 396 b_user = (username.upper() + (domain_name or "")).encode('utf-16-le') 397 return hmac_md5(nt_hash, b_user) 398 399 400def rc4(h: RC4Handle, d: bytes) -> bytes: 401 """ RC4 encryption of the data specified using the handle generated by rc4init. """ 402 return h.update(d) 403 404 405def rc4k(k: bytes, d: bytes) -> bytes: 406 """RC4 encryption with an explicit key. 407 408 Indicates the encryption of data item `d` with the key `k` using the `RC4`_ algorithm. 409 410 Args: 411 k: The key to use for the RC4 cipher. 412 d: The data to encrypt. 413 414 Returns: 415 bytes: The RC4 encrypted bytes. 416 417 .. _RC4K: 418 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/26c42637-9549-46ae-be2e-90f6f1360193 419 """ 420 return rc4init(k).update(d) 421 422 423def rc4init(k: bytes) -> RC4Handle: 424 """ Initialization of the RC4 handle using the key specified. """ 425 return RC4Handle(k) 426 427 428def sealkey(flags: int, session_key: bytes, usage: str) -> bytes: 429 """NTLM SEALKEY function. 430 431 The MS-NLMP `SEALKEY`_ function used to generate the sealing keys for a security context. 432 433 The pseudo-code for this function as documented under `SEALKEY`_ is:: 434 435 Define SEALKEY(NegFlg, ExportedSessionKey, Mode) as 436 437 If (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is set in NegFlg) 438 439 If ( NTLMSSP_NEGOTIATE_128 is set in NegFlg) 440 Set SealKey to ExportedSessionKey 441 442 ElseIf ( NTLMSSP_NEGOTIATE_56 flag is set in NegFlg) 443 Set SealKey to ExportedSessionKey[0..6] 444 445 Else 446 Set SealKey to ExportedSessionKey[0..4] 447 448 Endif 449 450 If (Mode equals "Client") 451 Set SealKey to MD5(ConcatenationOf(SealKey, 452 "session key to client-to-server sealing key magic constant")) 453 454 Else 455 Set SealKey to MD5(ConcatenationOf(SealKey, 456 "session key to server-to-client sealing key magic constant")) 457 458 Endif 459 ElseIf ((NTLMSSP_NEGOTIATE_LM_KEY is set in NegFlg) or ((NTLMSSP_NEGOTIATE_DATAGRAM is set in NegFlg) and 460 (NTLMRevisionCurrent >= NTLMSSP_REVISION_W2K3))) 461 462 If (NTLMSSP_NEGOTIATE_56 flag is set in NegFlg) 463 Set SealKey to ConcatenationOf(ExportedSessionKey[0..6], 0xA0) 464 465 Else 466 Set SealKey to ConcatenationOf(ExportedSessionKey[0..4], 0xE5, 0x38, 0xB0) 467 468 EndIf 469 470 Else 471 Set SealKey to ExportedSessionKey 472 Endif 473 EndDefine 474 475 Args: 476 flags: The negotiated flags between the initiator and acceptor. 477 session_key: The derived session key. 478 usage: Whether the sealing key is for the 'initiate' or 'accept' context. 479 480 Returns: 481 bytes: The derived sealing key. 482 483 .. _SEALKEY: 484 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/bf39181d-e95d-40d7-a740-ab4ec3dc363d 485 """ 486 if flags & NegotiateFlags.extended_session_security: 487 if flags & NegotiateFlags.key_128: 488 seal_key = session_key 489 490 elif flags & NegotiateFlags.key_56: 491 seal_key = session_key[:7] 492 493 else: 494 seal_key = session_key[:5] 495 496 direction = b"client-to-server" if usage == 'initiate' else b"server-to-client" 497 498 return md5(seal_key + b"session key to %s sealing key magic constant\x00" % direction) 499 500 elif flags & NegotiateFlags.lm_key or flags & NegotiateFlags.datagram: 501 if flags & NegotiateFlags.key_56: 502 return session_key[:7] + b"\xA0" 503 504 else: 505 return session_key[:5] + b"\xE5\x38\xB0" 506 507 else: 508 return session_key 509 510 511def signkey(flags: int, session_key: bytes, usage: str) -> typing.Optional[bytes]: 512 """NTLM SIGNKEY function. 513 514 The MS-NLMP `SIGNKEY`_ function used to generate the signing keys for a security context. 515 516 The pseudo-code for this function as documented under `SIGNKEY`_ is:: 517 518 Define SIGNKEY(NegFlg, ExportedSessionKey, Mode) as 519 520 If (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is set in NegFlg) 521 If (Mode equals "Client") 522 Set SignKey to MD5(ConcatenationOf(ExportedSessionKey, 523 "session key to client-to-server signing key magic constant")) 524 525 Else 526 Set SignKey to MD5(ConcatenationOf(ExportedSessionKey, 527 "session key to server-to-client signing key magic constant")) 528 529 Endif 530 Else 531 Set SignKey to NIL 532 533 Endif 534 EndDefine 535 536 Args: 537 flags: The negotiated flags between the initiator and acceptor. 538 session_key: The derived session key. 539 usage: Whether the signing key is for the 'initiate' or 'accept' context. 540 541 Returns: 542 bytes: The derived singing key. 543 544 .. _SIGNKEY: 545 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/524cdccb-563e-4793-92b0-7bc321fce096 546 """ 547 if flags & NegotiateFlags.extended_session_security == 0: 548 return 549 550 direction = b"client-to-server" if usage == 'initiate' else b"server-to-client" 551 552 return md5(session_key + b"session key to %s signing key magic constant\x00" % direction) 553