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