ntlm-auth-1.5.0/0000775000175000017500000000000013672062446014524 5ustar jboreanjborean00000000000000ntlm-auth-1.5.0/CHANGES.md0000664000175000017500000000570113672060602016111 0ustar jboreanjborean00000000000000# Changes ## 1.5.0 (Jun 16, 2020) * Added the `mic_present` property to the `NtlmContext` class to determine if a MIC has been added to the authentication message. * Added the `sign` and `verify` function to the `NtlmContext` to sign data and verify signatures. * Added the `reset_rc4_state` function to the `NtlmContext` to allow a caller to reset the incoming and outgoing RC4 cipher. * Added the `NTLMSSP_NEGOTIATE_UNICODE` flag to the negotiate message to ensure the challenge and authentication message's text fields can be unicode encoded ## 1.4.0 (Aug 19, 2019) * Added the `session_key` attribute to the `NtlmContext` class so the session key can be accessed in downstream libraries ## 1.3.0 (Apr 9, 2019) * Added optional dependency for `cryptography` for faster RC4 cipher calls * Removed the deprecation warning for Ntlm, this is still advised not to use but there's no major harm keep it in place for older hosts * Add CI test for Python 3.7 and 3.8 ## 1.2.0 (Jun 7, 2018) * Deprecated ntlm_auth.ntlm.Ntlm in favour of ntlm_auth.ntlm.NtlmContext * This is because `Ntlm` is heavily geared towards HTTP auth which is not always the case, `NtlmContext` makes things more generic * Updated docs and tests to reflect this * Dropped support for Python 3.3 ## 1.1.0 (Mar 7, 2018) * Removed DES code as the license was found to be incorrect from the source * Added new DES code not based on the original * Fixed up some deprecation warnings * Changed tests from running unittest to py.test * Changed licence from GPL to MIT as code is not all my own ## 1.0.6 (Oct 16, 2017) * More changes to packaging to better handle copyright and licensing ## 1.0.5 (Jun 22, 2017) * Added support for password hashes when using NTLMv1 authentication * Better handling of servers that fully conform to the NTLM spec, will check before trying to get the Version field in a challenge message instead of just failing. ## 1.0.2-1.0.4 * Various changes to get Python packaging to work with ordereddict no code changes ## 1.0.1 (Aug 29, 2016) Major fork from python-ntlm3 which is no longer in active maintenance. Features added since that latest release there; * Added support for Python 3.5 * Almost complete rewrite of how python-ntlm3 handled authentication to support newer features * Added support for NTLMv2 auth and fixed up some older auth methods * Moved code to separate classes to help cleanup the code * Added support for channel_bindings (CBT) when supplying a certificate hash * Added support for MIC data for authenticate messages * Support for signing and sealing of messages * More comments on each methods relating back to the MS-NLMP document pack on NTLM authentication for easier maintenance * Created target_info.py to handle AV_PAIRS and putting it in the target info * Renaming of some variables to match more closely with the Microsoft documentation, makes it easier to understand what is happening * Rewriting of tests to accommodate these new changes and to cover the new cases ntlm-auth-1.5.0/LICENSE0000664000175000017500000000206713672060540015527 0ustar jboreanjborean00000000000000MIT License Copyright (c) 2018 Jordan Borean, Red Hat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ntlm-auth-1.5.0/MANIFEST.in0000664000175000017500000000004213672060540016247 0ustar jboreanjborean00000000000000include LICENSE include CHANGES.mdntlm-auth-1.5.0/PKG-INFO0000664000175000017500000002752213672062446015631 0ustar jboreanjborean00000000000000Metadata-Version: 2.1 Name: ntlm-auth Version: 1.5.0 Summary: Creates NTLM authentication structures Home-page: https://github.com/jborean93/ntlm-auth Author: Jordan Borean Author-email: jborean93@gmail.com License: MIT Description: ntlm-auth ========= |Build Status|\ |Build status|\ |Coverage Status| About this library ------------------ This library handles the low-level details of NTLM authentication for use in authenticating with a service that uses NTLM. It will create and parse the 3 different message types in the order required and produce a base64 encoded value that can be attached to the HTTP header. The goal of this library is to offer full NTLM support including signing and sealing of messages as well as supporting MIC for message integrity and the ability to customise and set limits on the messages sent. Please see Features and Backlog for a list of what is and is not currently supported. Features -------- - LM, NTLM and NTLMv2 authentication - NTLM1 and NTLM2 extended session security - Set the The NTLM Compatibility level when sending messages - Channel Binding Tokens support, need to pass in the SHA256 hash of the certificate for it to work - Support for MIC to enhance the integrity of the messages - Support for session security with signing and sealing messages after authentication happens Installation ------------ ntlm-auth supports Python 2.6, 2.7 and 3.3+ To install, use pip: :: pip install ntlm-auth To install from source, download the source code, then run: :: python setup.py install Usage ----- Almost all users should use `requests-ntlm `__ instead of this library. The library requests-ntlm is a plugin that uses this library under the hood and provides an easier function to use and understand. If you are set on using ntlm-auth directly to compute the message structures this is a very basic outline of how it can be done. The code examples are psuedocode and should be adapted for your purpose. When initliasing the ntlm context you will have to supply the NTLM compatibility level. The key difference between the different auth levels are the ntlm_compatibility variable supplied when initialising Ntlm. An overview of what each sets is below; \* ``0`` - LM Auth and NTLMv1 Auth \* ``1`` - LM Auth and NTLMv1 Auth with Extended Session Security (NTLM2) \* ``2`` - NTLMv1 Auth with Extended Session Security (NTLM2) \* ``3`` - NTLMv2 Auth (Default Choice) \* ``4`` - NTLMv2 Auth \* ``5`` - NTLMv2 Auth Level 3 to 5 are the same from a client perspective but differ with how the server handles the auth which is outside this project’s scope. This setting is set independently on that server so choosing 3, 4 or 5 when calling Ntlm will make no difference at all. See `LmCompatibilityLevel `__ for more details. Extended Session Security is a security feature designed to increase the security of LM and NTLMv1 auth. It is no substitution for NTLMv2 but is better than nothing and should be used if possible when you need NTLMv1 compatibility. The variables required are outlined below; \* ``username`` - The username to authenticate with, should not have the domain prefix, i.e. USER not DOMAIN\USER \* ``password`` - The password of the user to authenticate with \* ``domain`` - The domain of the user, i.e. DOMAIN. Can be blank if not in a domain environment \* ``workstation`` - The workstation you are running on. Can be blank if you do not wish to send this \* ``cbt_data`` - (NTLMv2 only) The ``gss_channel_bindings.GssChannelBindingsStruct`` used to bind with the auth response. Can be None if no binding needs to occur LM Auth/NTLMv1 Auth ^^^^^^^^^^^^^^^^^^^ LM and NTLMv1 Auth are older authentication methods that should be avoided where possible. Choosing between these authentication methods are almost identical expect where you specify the ntlm_compatiblity level. .. code:: python import socket from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info ntlm_context = NtlmContext(username, password, domain, workstation, ntlm_compatibility=0) # Put the ntlm_compatibility level here, 0-2 for LM Auth/NTLMv1 Auth negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 NTLMv2 ^^^^^^ NTLMv2 Auth is the newest NTLM auth method from Microsoft and should be the option chosen by default unless you require an older auth method. The implementation is the same as NTLMv1 but with the addition of the optional ``server_certificate_hash`` variable and the ``ntlm_compatibility`` is not specified. .. code:: python import base64 import socket from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info # create the CBT struct if you wish to bind it with the auth response server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' certificate_digest = base64.b16decode(server_certificate_hash) cbt_data = GssChannelBindingsStruct() cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3) negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 Signing/Sealing ^^^^^^^^^^^^^^^ All version of NTLM supports signing (integrity) and sealing (confidentiality) of message content. This function can add these improvements to a message that is sent and received from the server. While it does encrypt the data if supported by the server it is only done with RC4 with a 128-bit key which is not very secure and on older systems this key length could be 56 or 40 bit. This functionality while tested and conforms with the Microsoft documentation has yet to be fully tested in an integrated environment. Once again this has not been thoroughly tested and has only passed unit tests and their expections. .. code:: python import base64 import socket from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info # create the CBT struct if you wish to bind it with the auth response server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' certificate_digest = base64.b16decode(server_certificate_hash) cbt_data = GssChannelBindingsStruct() cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3) negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 # Encrypt the message with the wrapping function and send the message enc_message = ntlm_context.wrap("Message to send", encrypt=True) request.body = msg_data request.send # Receive the response from the server and decrypt response_msg = response.content response = ntlm_context.unwrap(response_msg) Backlog ------- - Automatically get windows version if running on windows, use default if not that case - Add param when initialising the ntlm context to throw an exception and cancel auth if the server doesn’t support 128-bit keys for sealing - Add param when initialising the ntlm context to not send the MIC structure for older servers - Add param to independently verify the target name returned from the server and the value passed in .. |Build Status| image:: https://travis-ci.org/jborean93/ntlm-auth.svg?branch=master :target: https://travis-ci.org/jborean93/ntlm-auth .. |Build status| image:: https://ci.appveyor.com/api/projects/status/osvvfgmhfk4anvu0/branch/master?svg=true :target: https://ci.appveyor.com/project/jborean93/ntlm-auth/branch/master .. |Coverage Status| image:: https://coveralls.io/repos/github/jborean93/ntlm-auth/badge.svg?branch=master :target: https://coveralls.io/github/jborean93/ntlm-auth?branch=master Keywords: authentication auth microsoft ntlm lm Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* Provides-Extra: cryptography ntlm-auth-1.5.0/README.md0000664000175000017500000002146113672060540016000 0ustar jboreanjborean00000000000000ntlm-auth ========= [![Build Status](https://travis-ci.org/jborean93/ntlm-auth.svg?branch=master)](https://travis-ci.org/jborean93/ntlm-auth)[![Build status](https://ci.appveyor.com/api/projects/status/osvvfgmhfk4anvu0/branch/master?svg=true)](https://ci.appveyor.com/project/jborean93/ntlm-auth/branch/master)[![Coverage Status](https://coveralls.io/repos/github/jborean93/ntlm-auth/badge.svg?branch=master)](https://coveralls.io/github/jborean93/ntlm-auth?branch=master) About this library ------------------ This library handles the low-level details of NTLM authentication for use in authenticating with a service that uses NTLM. It will create and parse the 3 different message types in the order required and produce a base64 encoded value that can be attached to the HTTP header. The goal of this library is to offer full NTLM support including signing and sealing of messages as well as supporting MIC for message integrity and the ability to customise and set limits on the messages sent. Please see Features and Backlog for a list of what is and is not currently supported. Features -------- * LM, NTLM and NTLMv2 authentication * NTLM1 and NTLM2 extended session security * Set the The NTLM Compatibility level when sending messages * Channel Binding Tokens support, need to pass in the SHA256 hash of the certificate for it to work * Support for MIC to enhance the integrity of the messages * Support for session security with signing and sealing messages after authentication happens Installation ------------ ntlm-auth supports Python 2.6, 2.7 and 3.3+ To install, use pip: pip install ntlm-auth To install from source, download the source code, then run: python setup.py install Usage ------------ Almost all users should use [requests-ntlm](https://github.com/requests/requests-ntlm) instead of this library. The library requests-ntlm is a plugin that uses this library under the hood and provides an easier function to use and understand. If you are set on using ntlm-auth directly to compute the message structures this is a very basic outline of how it can be done. The code examples are psuedocode and should be adapted for your purpose. When initliasing the ntlm context you will have to supply the NTLM compatibility level. The key difference between the different auth levels are the ntlm_compatibility variable supplied when initialising Ntlm. An overview of what each sets is below; * `0` - LM Auth and NTLMv1 Auth * `1` - LM Auth and NTLMv1 Auth with Extended Session Security (NTLM2) * `2` - NTLMv1 Auth with Extended Session Security (NTLM2) * `3` - NTLMv2 Auth (Default Choice) * `4` - NTLMv2 Auth * `5` - NTLMv2 Auth Level 3 to 5 are the same from a client perspective but differ with how the server handles the auth which is outside this project's scope. This setting is set independently on that server so choosing 3, 4 or 5 when calling Ntlm will make no difference at all. See [LmCompatibilityLevel](https://technet.microsoft.com/en-us/library/cc960646.aspx) for more details. Extended Session Security is a security feature designed to increase the security of LM and NTLMv1 auth. It is no substitution for NTLMv2 but is better than nothing and should be used if possible when you need NTLMv1 compatibility. The variables required are outlined below; * `username` - The username to authenticate with, should not have the domain prefix, i.e. USER not DOMAIN\\USER * `password` - The password of the user to authenticate with * `domain` - The domain of the user, i.e. DOMAIN. Can be blank if not in a domain environment * `workstation` - The workstation you are running on. Can be blank if you do not wish to send this * `cbt_data` - (NTLMv2 only) The `gss_channel_bindings.GssChannelBindingsStruct` used to bind with the auth response. Can be None if no binding needs to occur #### LM Auth/NTLMv1 Auth LM and NTLMv1 Auth are older authentication methods that should be avoided where possible. Choosing between these authentication methods are almost identical expect where you specify the ntlm_compatiblity level. ```python import socket from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info ntlm_context = NtlmContext(username, password, domain, workstation, ntlm_compatibility=0) # Put the ntlm_compatibility level here, 0-2 for LM Auth/NTLMv1 Auth negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 ``` #### NTLMv2 NTLMv2 Auth is the newest NTLM auth method from Microsoft and should be the option chosen by default unless you require an older auth method. The implementation is the same as NTLMv1 but with the addition of the optional `server_certificate_hash` variable and the `ntlm_compatibility` is not specified. ```python import base64 import socket from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info # create the CBT struct if you wish to bind it with the auth response server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' certificate_digest = base64.b16decode(server_certificate_hash) cbt_data = GssChannelBindingsStruct() cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3) negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 ``` #### Signing/Sealing All version of NTLM supports signing (integrity) and sealing (confidentiality) of message content. This function can add these improvements to a message that is sent and received from the server. While it does encrypt the data if supported by the server it is only done with RC4 with a 128-bit key which is not very secure and on older systems this key length could be 56 or 40 bit. This functionality while tested and conforms with the Microsoft documentation has yet to be fully tested in an integrated environment. Once again this has not been thoroughly tested and has only passed unit tests and their expections. ```python import base64 import socket from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info # create the CBT struct if you wish to bind it with the auth response server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' certificate_digest = base64.b16decode(server_certificate_hash) cbt_data = GssChannelBindingsStruct() cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3) negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 # Encrypt the message with the wrapping function and send the message enc_message = ntlm_context.wrap("Message to send", encrypt=True) request.body = msg_data request.send # Receive the response from the server and decrypt response_msg = response.content response = ntlm_context.unwrap(response_msg) ``` Backlog ------- * Automatically get windows version if running on windows, use default if not that case * Add param when initialising the ntlm context to throw an exception and cancel auth if the server doesn't support 128-bit keys for sealing * Add param when initialising the ntlm context to not send the MIC structure for older servers * Add param to independently verify the target name returned from the server and the value passed inntlm-auth-1.5.0/ntlm_auth/0000775000175000017500000000000013672062446016517 5ustar jboreanjborean00000000000000ntlm-auth-1.5.0/ntlm_auth/__init__.py0000664000175000017500000000033113672060540020616 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) from . import ntlm, session_security __all__ = ('ntlm', 'session_security') ntlm-auth-1.5.0/ntlm_auth/compute_hash.py0000664000175000017500000000545013672060540021545 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import binascii import hashlib import hmac import re from ntlm_auth.des import DES def _lmowfv1(password): """ [MS-NLMP] v28.0 2016-07-14 3.3.1 NTLM v1 Authentication Same function as LMOWFv1 in document to create a one way hash of the password. Only used in NTLMv1 auth without session security :param password: The password or hash of the user we are trying to authenticate with :return res: A Lan Manager hash of the password supplied """ # if the password is a hash, return the LM hash if re.match(r'^[a-fA-F\d]{32}:[a-fA-F\d]{32}$', password): lm_hash = binascii.unhexlify(password.split(':')[0]) return lm_hash # fix the password to upper case and length to 14 bytes password = password.upper() lm_pw = password.encode('utf-8') padding_size = 0 if len(lm_pw) >= 14 else (14 - len(lm_pw)) lm_pw += b"\x00" * padding_size # do hash magic_str = b"KGS!@#$%" # page 56 in [MS-NLMP v28.0] res = b"" dobj = DES(DES.key56_to_key64(lm_pw[0:7])) res += dobj.encrypt(magic_str) dobj = DES(DES.key56_to_key64(lm_pw[7:14])) res += dobj.encrypt(magic_str) return res def _ntowfv1(password): """ [MS-NLMP] v28.0 2016-07-14 3.3.1 NTLM v1 Authentication Same function as NTOWFv1 in document to create a one way hash of the password. Only used in NTLMv1 auth without session security :param password: The password or hash of the user we are trying to authenticate with :return digest: An NT hash of the password supplied """ # if the password is a hash, return the NT hash if re.match(r'^[a-fA-F\d]{32}:[a-fA-F\d]{32}$', password): nt_hash = binascii.unhexlify(password.split(':')[1]) return nt_hash digest = hashlib.new('md4', password.encode('utf-16-le')).digest() return digest def _ntowfv2(user_name, password, domain_name): """ [MS-NLMP] v28.0 2016-07-14 3.3.2 NTLM v2 Authentication Same function as NTOWFv2 (and LMOWFv2) in document to create a one way hash of the password. This combines some extra security features over the v1 calculations used in NTLMv2 auth. :param user_name: The user name of the user we are trying to authenticate with :param password: The password of the user we are trying to authenticate with :param domain_name: The domain name of the user account we are authenticated with :return digest: An NT hash of the parameters supplied """ digest = _ntowfv1(password) user = (user_name.upper() + domain_name).encode('utf-16-le') digest = hmac.new(digest, user, digestmod=hashlib.md5).digest() return digest ntlm-auth-1.5.0/ntlm_auth/compute_keys.py0000664000175000017500000001377413672060540021605 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import hashlib import hmac from ntlm_auth.des import DES from ntlm_auth.constants import NegotiateFlags def _get_exchange_key_ntlm_v1(negotiate_flags, session_base_key, server_challenge, lm_challenge_response, lm_hash): """ [MS-NLMP] v28.0 2016-07-14 3.4.5.1 KXKEY Calculates the Key Exchange Key for NTLMv1 authentication. Used for signing and sealing messages :param negotiate_flags: The negotiated NTLM flags :param session_base_key: A session key calculated from the user password challenge :param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE :param lm_challenge_response: The LmChallengeResponse value computed in ComputeResponse :param lm_hash: The LMOWF computed in Compute Response :return: The Key Exchange Key (KXKEY) used to sign and seal messages and compute the ExportedSessionKey """ if negotiate_flags & \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: key_exchange_key = hmac.new( session_base_key, server_challenge + lm_challenge_response[:8], digestmod=hashlib.md5 ).digest() elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY: des_handler = DES(DES.key56_to_key64(lm_hash[:7])) first_des = des_handler.encrypt(lm_challenge_response[:8]) second_des_key = lm_hash[7:8] + b"\xbd\xbd\xbd\xbd\xbd\xbd" des_handler = DES(DES.key56_to_key64(second_des_key)) second_des = des_handler.encrypt(lm_challenge_response[:8]) key_exchange_key = first_des + second_des elif negotiate_flags & NegotiateFlags.NTLMSSP_REQUEST_NON_NT_SESSION_KEY: key_exchange_key = lm_hash[:8] + b'\0' * 8 else: key_exchange_key = session_base_key return key_exchange_key def _get_exchange_key_ntlm_v2(session_base_key): """ [MS-NLMP] v28.0 2016-07-14 4.3.5.1 KXKEY Calculates the Key Exchange Key for NTLMv2 authentication. Used for signing and sealing messages. According to docs, 'If NTLM v2 is used, KeyExchangeKey MUST be set to the given 128-bit SessionBaseKey :param session_base_key: A session key calculated from the user password challenge :return key_exchange_key: The Key Exchange Key (KXKEY) used to sign and seal messages """ return session_base_key def get_sign_key(exported_session_key, magic_constant): """ 3.4.5.2 SIGNKEY :param exported_session_key: A 128-bit session key used to derive signing and sealing keys :param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants) :return sign_key: Key used to sign messages """ sign_key = hashlib.md5(exported_session_key + magic_constant).digest() return sign_key def get_seal_key(negotiate_flags, exported_session_key, magic_constant): """ 3.4.5.3. SEALKEY Main method to use to calculate the seal_key used to seal (encrypt) messages. This will determine the correct method below to use based on the compatibility flags set and should be called instead of the others :param exported_session_key: A 128-bit session key used to derive signing and sealing keys :param negotiate_flags: The negotiate_flags structure sent by the server :param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants) :return seal_key: Key used to seal messages """ if negotiate_flags & \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: seal_key = _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant) elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY: seal_key = _get_seal_key_ntlm1(negotiate_flags, exported_session_key) else: seal_key = exported_session_key return seal_key def _get_seal_key_ntlm1(negotiate_flags, exported_session_key): """ 3.4.5.3 SEALKEY Calculates the seal_key used to seal (encrypt) messages. This for authentication where NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has not been negotiated. Will weaken the keys if NTLMSSP_NEGOTIATE_56 is not negotiated it will default to the 40-bit key :param negotiate_flags: The negotiate_flags structure sent by the server :param exported_session_key: A 128-bit session key used to derive signing and sealing keys :return seal_key: Key used to seal messages """ if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56: seal_key = exported_session_key[:7] + b"\xa0" else: seal_key = exported_session_key[:5] + b"\xe5\x38\xb0" return seal_key def _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant): """ 3.4.5.3 SEALKEY Calculates the seal_key used to seal (encrypt) messages. This for authentication where NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has been negotiated. Will weaken the keys if NTLMSSP_NEGOTIATE_128 is not negotiated, will try NEGOTIATE_56 and then will default to the 40-bit key :param negotiate_flags: The negotiate_flags structure sent by the server :param exported_session_key: A 128-bit session key used to derive signing and sealing keys :param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants) :return seal_key: Key used to seal messages """ if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_128: seal_key = exported_session_key elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56: seal_key = exported_session_key[:7] else: seal_key = exported_session_key[:5] seal_key = hashlib.md5(seal_key + magic_constant).digest() return seal_key ntlm-auth-1.5.0/ntlm_auth/compute_response.py0000664000175000017500000004756713672060540022477 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import base64 import calendar import hashlib import hmac import os import struct import time import ntlm_auth.compute_hash as comphash import ntlm_auth.compute_keys as compkeys import ntlm_auth.messages from ntlm_auth.des import DES from ntlm_auth.constants import AvId, AvFlags, NegotiateFlags from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct class ComputeResponse(): def __init__(self, user_name, password, domain_name, challenge_message, ntlm_compatibility): """ Constructor for the response computations. This class will compute the various nt and lm challenge responses. :param user_name: The user name of the user we are trying to authenticate with :param password: The password of the user we are trying to authenticate with :param domain_name: The domain name of the user account we are authenticated with, default is None :param challenge_message: A ChallengeMessage object that was received from the server after the negotiate_message :param ntlm_compatibility: The Lan Manager Compatibility Level, used to determine what NTLM auth version to use, see Ntlm in ntlm.py for more details """ self._user_name = user_name self._password = password self._domain_name = domain_name self._challenge_message = challenge_message self._negotiate_flags = challenge_message.negotiate_flags self._server_challenge = challenge_message.server_challenge self._server_target_info = challenge_message.target_info self._ntlm_compatibility = ntlm_compatibility self._client_challenge = os.urandom(8) def get_lm_challenge_response(self): """ [MS-NLMP] v28.0 2016-07-14 3.3.1 - NTLM v1 Authentication 3.3.2 - NTLM v2 Authentication This method returns the LmChallengeResponse key based on the ntlm_compatibility chosen and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one and calls separate methods based on the ntlm_compatibility flag chosen. :return: response (LmChallengeResponse) - The LM response to the server challenge. Computed by the client """ if self._negotiate_flags & \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \ self._ntlm_compatibility < 3: response = self._get_LMv1_with_session_security_response( self._client_challenge ) elif 0 <= self._ntlm_compatibility <= 1: response = self._get_LMv1_response(self._password, self._server_challenge) elif self._ntlm_compatibility == 2: # Based on the compatibility level we don't want to use LM # responses, ignore the session_base_key as it is returned in nt response, ignore_key = \ self._get_NTLMv1_response(self._password, self._server_challenge) else: """ [MS-NLMP] v28.0 page 45 - 2016-07-14 3.1.5.12 Client Received a CHALLENGE_MESSAGE from the Server If NTLMv2 authentication is used and the CHALLENGE_MESSAGE TargetInfo field has an MsvAvTimestamp present, the client SHOULD NOT send the LmChallengeResponse and SHOULD send Z(24) instead. """ response = self._get_LMv2_response(self._user_name, self._password, self._domain_name, self._server_challenge, self._client_challenge) if self._server_target_info is not None: timestamp = \ self._server_target_info[AvId.MSV_AV_TIMESTAMP] if timestamp is not None: response = b'\x00' * 24 return response def get_nt_challenge_response(self, lm_challenge_response, server_certificate_hash=None, cbt_data=None): """ [MS-NLMP] v28.0 2016-07-14 3.3.1 - NTLM v1 Authentication 3.3.2 - NTLM v2 Authentication This method returns the NtChallengeResponse key based on the ntlm_compatibility chosen and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one and calls separate methods based on the ntlm_compatibility value chosen. :param lm_challenge_response: The LmChallengeResponse calculated beforehand, used to get the key_exchange_key value :param server_certificate_hash: This is deprecated and will be removed in a future version, use cbt_data instead :param cbt_data: The GssChannelBindingsStruct to bind in the NTLM response :return response: (NtChallengeResponse) - The NT response to the server challenge. Computed by the client :return session_base_key: (SessionBaseKey) - A session key calculated from the user password challenge :return target_info: (AV_PAIR) - The AV_PAIR structure used in the nt_challenge calculations """ if self._negotiate_flags & \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and \ self._ntlm_compatibility < 3: # The compatibility level is less than 3 which means it doesn't # support NTLMv2 but we want extended security so use NTLM2 which # is different from NTLMv2 # [MS-NLMP] - 3.3.1 NTLMv1 Authentication response, session_base_key = \ self._get_NTLM2_response(self._password, self._server_challenge, self._client_challenge) lm_hash = comphash._lmowfv1(self._password) key_exchange_key = \ compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key, self._server_challenge, lm_challenge_response, lm_hash) target_info = None elif 0 <= self._ntlm_compatibility < 3: response, session_base_key = \ self._get_NTLMv1_response(self._password, self._server_challenge) lm_hash = comphash._lmowfv1(self._password) key_exchange_key = \ compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key, self._server_challenge, lm_challenge_response, lm_hash) target_info = None else: if self._server_target_info is None: target_info = ntlm_auth.messages.TargetInfo() else: target_info = self._server_target_info if target_info[AvId.MSV_AV_TIMESTAMP] is None: timestamp = get_windows_timestamp() else: timestamp = target_info[AvId.MSV_AV_TIMESTAMP] # [MS-NLMP] If the CHALLENGE_MESSAGE TargetInfo field has an # MsvAvTimestamp present, the client SHOULD provide a MIC target_info[AvId.MSV_AV_FLAGS] = \ struct.pack(" # MIT License (see LICENSE or https://opensource.org/licenses/MIT) # [MS-NLMP] 2.2 Message Syntax - The signature field used in NTLM messages NTLM_SIGNATURE = b'NTLMSSP\x00' class MessageTypes(object): """ [MS-NLMP] v28.0 2016-07-14 2.2 Message Syntax The 3 message type options you can have in a message. """ NTLM_NEGOTIATE = 0x1 NTLM_CHALLENGE = 0x2 NTLM_AUTHENTICATE = 0x3 class AvId(object): """ [MS-NLMP] 2.2.2.1 AV_PAIR AvId https://msdn.microsoft.com/en-us/library/cc236646.aspx 16-bit unsigned integer that defines the information type in the value field for an AV_PAIR. """ MSV_AV_EOL = 0x00 MSV_AV_NB_COMPUTER_NAME = 0x01 MSV_AV_NB_DOMAIN_NAME = 0x02 MSV_AV_DNS_COMPUTER_NAME = 0x03 MSV_AV_DNS_DOMAIN_NAME = 0x04 MSV_AV_DNS_TREE_NAME = 0x05 MSV_AV_FLAGS = 0x06 MSV_AV_TIMESTAMP = 0x07 MSV_AV_SINGLE_HOST = 0x08 MSV_AV_TARGET_NAME = 0x09 MSV_AV_CHANNEL_BINDINGS = 0x0a class AvFlags(object): """ [MS-NLMP] v28.0 2016-07-14 2.2.2.1 AV_PAIR (MsvAvFlags) A 32-bit value indicated server or client configuration """ AUTHENTICATION_CONSTRAINED = 0x1 MIC_PROVIDED = 0x2 UNTRUSTED_SPN_SOURCE = 0x4 class NegotiateFlags(object): """ [MS-NLMP] v28.0 2016-07-14 2.2.2.5 NEGOTIATE During NTLM authentication, each of the following flags is a possible value of the NegotiateFlags field of the NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE and AUTHENTICATE_MESSAGE, unless otherwise noted. These flags define client or server NTLM capabilities supported by the sender. """ NTLMSSP_NEGOTIATE_56 = 0x80000000 NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000 NTLMSSP_NEGOTIATE_128 = 0x20000000 NTLMSSP_RESERVED_R1 = 0x10000000 NTLMSSP_RESERVED_R2 = 0x08000000 NTLMSSP_RESERVED_R3 = 0x04000000 NTLMSSP_NEGOTIATE_VERSION = 0x02000000 NTLMSSP_RESERVED_R4 = 0x01000000 NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000 NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000 NTLMSSP_RESERVED_R5 = 0x00200000 NTLMSSP_NEGOTIATE_IDENTITY = 0x00100000 NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000 NTLMSSP_RESERVED_R6 = 0x00040000 NTLMSSP_TARGET_TYPE_SERVER = 0x00020000 NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000 NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000 NTLMSSP_RESERVED_R7 = 0x00004000 NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000 NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000 NTLMSSP_ANOYNMOUS = 0x00000800 NTLMSSP_RESERVED_R8 = 0x00000400 NTLMSSP_NEGOTIATE_NTLM = 0x00000200 NTLMSSP_RESERVED_R9 = 0x00000100 NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080 NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040 NTLMSSP_NEGOTIATE_SEAL = 0x00000020 NTLMSSP_NEGOTIATE_SIGN = 0x00000010 NTLMSSP_RESERVED_R10 = 0x00000008 NTLMSSP_REQUEST_TARGET = 0x00000004 NTLMSSP_NEGOTIATE_OEM = 0x00000002 NTLMSSP_NEGOTIATE_UNICODE = 0x00000001 class SignSealConstants(object): # Magic Contants used to get the signing and sealing key for # Extended Session Security CLIENT_SIGNING = b"session key to client-to-server signing key magic " \ b"constant\x00" SERVER_SIGNING = b"session key to server-to-client signing key magic " \ b"constant\x00" CLIENT_SEALING = b"session key to client-to-server sealing key magic " \ b"constant\x00" SERVER_SEALING = b"session key to server-to-client sealing key magic " \ b"constant\x00" ntlm-auth-1.5.0/ntlm_auth/des.py0000664000175000017500000003132113672060540017635 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import struct # lots of help from # http://page.math.tu-berlin.de/~kant/teaching/hess/krypto-ws2006/des.htm class DES(object): # first table used to derive the sub keys _pc1 = [ 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 ] # shifts the sub key from pc1 to calculate the 16 sub keys _shift_indexes = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1] # second table used to derive the sub keys _pc2 = [ 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 ] # initial permutation of the 64-bits of the message data _ip = [ 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6 ] # used to expand the each initial permuted half into a 48-bit values _e_bit_selection = [ 31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 19, 20, 21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0 ] # list of boxes used in the encryption process _s_boxes = [ [ 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 ], [ 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 ], [ 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 ], [ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 ], [ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 ], [ 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 ], [ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 ], [ 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 ] ] # converts the s-box permutation one more time _p = [ 15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30, 9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12, 29, 5, 21, 10, 3, 24 ] # final permutation of the message _final_ip = [ 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, 32, 0, 40, 8, 48, 16, 56, 24 ] def __init__(self, key): """ Creates a DES cipher class with the key initialised. This key must be 8 bytes in length. This only supports the ECB cipher mode as that is what is used in the LM hash calculation. :param key: The 8-byte key to use in the Cipher """ if len(key) != 8: raise ValueError("DES encryption key should be 8 bytes in length") self.key = key self._subkeys = self._create_subkeys(self.key) def encrypt(self, data, pad=True): """ DES encrypts the data based on the key it was initialised with. :param data: The bytes string to encrypt :param pad: Whether to right pad data with \x00 to a multiple of 8 :return: The encrypted bytes string """ encrypted_data = b"" for i in range(0, len(data), 8): block = data[i:i + 8] block_length = len(block) if block_length != 8 and pad: block += b"\x00" * (8 - block_length) elif block_length != 8: raise ValueError("DES encryption must be a multiple of 8 " "bytes") encrypted_data += self._encode_block(block) return encrypted_data def decrypt(self, data): """ DES decrypts the data based on the key it was initialised with. :param data: The encrypted bytes string to decrypt :return: The decrypted bytes string """ decrypted_data = b"" for i in range(0, len(data), 8): block = data[i:i + 8] block_length = len(block) if block_length != 8: raise ValueError("DES decryption must be a multiple of 8 " "bytes") decrypted_data += self._decode_block(block) return decrypted_data @staticmethod def key56_to_key64(key): """ This takes in an a bytes string of 7 bytes and converts it to a bytes string of 8 bytes with the odd parity bit being set to every 8 bits, For example b"\x01\x02\x03\x04\x05\x06\x07" 00000001 00000010 00000011 00000100 00000101 00000110 00000111 is converted to b"\x01\x80\x80\x61\x40\x29\x19\x0E" 00000001 10000000 10000000 01100001 01000000 00101001 00011001 00001110 https://crypto.stackexchange.com/questions/15799/des-with-actual-7-byte-key :param key: 7-byte string sized key :return: 8-byte string with the parity bits sets from the 7-byte string """ if len(key) != 7: raise ValueError("DES 7-byte key is not 7 bytes in length, " "actual: %d" % len(key)) new_key = b"" for i in range(0, 8): if i == 0: new_value = struct.unpack("B", key[i:i+1])[0] elif i == 7: new_value = struct.unpack("B", key[6:7])[0] new_value = (new_value << 1) & 0xFF else: new_value = struct.unpack("B", key[i - 1:i])[0] next_value = struct.unpack("B", key[i:i + 1])[0] new_value = ((new_value << (8 - i)) & 0xFF) | next_value >> i # clear the last bit so the count isn't off new_value = new_value & ~(1 << 0) # set the last bit if the number of set bits are even new_value = new_value | int(not DES.bit_count(new_value) & 0x1) new_key += struct.pack("B", new_value) return new_key @staticmethod def bit_count(i): # counts the number of bits that are 1 in the integer count = 0 while i: i &= i - 1 count += 1 return count def _create_subkeys(self, key): # convert the key into a list of bits key_bits = self._get_bits(key) # reorder the bits based on the pc1 table pc1_bits = [key_bits[x] for x in self._pc1] # split the table into 2 and append to the first entry c = [pc1_bits[0:28]] d = [pc1_bits[28:56]] # now populate the remaining blocks by shifting the values for i, shift_index in enumerate(self._shift_indexes): c.append(self._shift_bits(c[i], shift_index)) d.append(self._shift_bits(d[i], shift_index)) subkeys = list() for i in range(1, 17): cd = c[i] + d[i] subkey_bits = [cd[x] for x in self._pc2] subkeys.append(subkey_bits) return subkeys def _shift_bits(self, bits, shifts): new_bits = [None] * 28 for i in range(28): shift_index = i + shifts if shift_index >= 28: shift_index = shift_index - 28 new_bits[i] = bits[shift_index] return new_bits def _get_bits(self, data): bits = [] for i in range(len(data)): b = struct.unpack("B", data[i:i + 1])[0] bits.append(1 if b & 0x80 else 0) bits.append(1 if b & 0x40 else 0) bits.append(1 if b & 0x20 else 0) bits.append(1 if b & 0x10 else 0) bits.append(1 if b & 0x08 else 0) bits.append(1 if b & 0x04 else 0) bits.append(1 if b & 0x02 else 0) bits.append(1 if b & 0x01 else 0) return bits def _encode_block(self, block): block_bits = self._get_bits(block) lr = [block_bits[x] for x in self._ip] l = [lr[0:32]] r = [lr[32:64]] for i in range(16): computed_block = self._compute_block(r[i], self._subkeys[i]) new_r = [int(computed_block[k] != l[i][k]) for k in range(32)] l.append(r[i]) r.append(new_r) # apply the final permutation on the l and r bits backwards rl = r[16] + l[16] encrypted_bits = [rl[x] for x in self._final_ip] encrypted_bytes = b"" for i in range(0, 64, 8): i_byte = int("".join([str(x) for x in encrypted_bits[i:i + 8]]), 2) encrypted_bytes += struct.pack("B", i_byte) return encrypted_bytes def _decode_block(self, block): block_bits = self._get_bits(block) rl = [None] * 64 for i, idx in enumerate(self._final_ip): rl[idx] = block_bits[i] r = [None] * 17 l = [None] * 17 r[16] = rl[0:32] l[16] = rl[32:64] for i in range(15, -1, -1): computed_block = self._compute_block(l[i + 1], self._subkeys[i]) new_l = [int(computed_block[k] != r[i + 1][k]) for k in range(32)] r[i] = l[i + 1] l[i] = new_l lr = l[0] + r[0] decrypted_bits = [None] * 64 for i, idx in enumerate(self._ip): decrypted_bits[idx] = lr[i] decrypted_bytes = b"" for i in range(0, 64, 8): i_byte = int("".join([str(x) for x in decrypted_bits[i:i + 8]]), 2) decrypted_bytes += struct.pack("B", i_byte) return decrypted_bytes def _compute_block(self, block, key): expanded_block = [block[x] for x in self._e_bit_selection] new_block = [int(key[i] != expanded_block[i]) for i in range(48)] # calculate with the s-boxes s_box_perm = [] s_box_iter = 0 # now go through each block (8 groups of 6 bits) and run the s-boxes for i in range(0, 48, 6): current_block = new_block[i:i + 6] row_bits = [str(current_block[0]), str(current_block[-1])] column_bits = [str(x) for x in current_block[1:-1]] s_box_row = int("".join(row_bits), 2) s_box_column = int("".join(column_bits), 2) s_box_address = (s_box_row * 16) + s_box_column s_box_value = self._s_boxes[s_box_iter][s_box_address] s_box_iter += 1 s_box_perm.append(1 if s_box_value & 0x8 else 0) s_box_perm.append(1 if s_box_value & 0x4 else 0) s_box_perm.append(1 if s_box_value & 0x2 else 0) s_box_perm.append(1 if s_box_value & 0x1 else 0) final_block = [s_box_perm[x] for x in self._p] return final_block ntlm-auth-1.5.0/ntlm_auth/exceptions.py0000664000175000017500000000027313672060540021245 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) class NoAuthContextError(Exception): pass ntlm-auth-1.5.0/ntlm_auth/gss_channel_bindings.py0000664000175000017500000000470313672060540023227 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import struct class GssChannelBindingsStruct(object): INITIATOR_ADDTYPE = 'initiator_addtype' INITIATOR_ADDRESS_LENGTH = 'initiator_address_length' ACCEPTOR_ADDRTYPE = 'acceptor_addrtype' ACCEPTOR_ADDRESS_LENGTH = 'acceptor_address_length' APPLICATION_DATA_LENGTH = 'application_data_length' INITIATOR_ADDRESS = 'initiator_address' ACCEPTOR_ADDRESS = 'acceptor_address' APPLICATION_DATA = 'application_data' def __init__(self): """ Used to send the out of band channel info as part of the authentication process. This is used as a way of verifying the target is who it says it is as this information is provided by the higher layer. In most cases, the CBT is just the hash of the server's TLS certificate to the application_data field. This bytes string of the packed structure is then MD5 hashed and included in the NTv2 response. """ self.fields = { self.INITIATOR_ADDTYPE: 0, self.INITIATOR_ADDRESS_LENGTH: 0, self.ACCEPTOR_ADDRTYPE: 0, self.ACCEPTOR_ADDRESS_LENGTH: 0, self.APPLICATION_DATA_LENGTH: 0, self.INITIATOR_ADDRESS: b"", self.ACCEPTOR_ADDRESS: b"", self.APPLICATION_DATA: b"" } def __setitem__(self, key, value): self.fields[key] = value def __getitem__(self, key): return self.fields[key] def get_data(self): # Set the lengths of each len field in case they have changed self[self.INITIATOR_ADDRESS_LENGTH] = len(self[self.INITIATOR_ADDRESS]) self[self.ACCEPTOR_ADDRESS_LENGTH] = len(self[self.ACCEPTOR_ADDRESS]) self[self.APPLICATION_DATA_LENGTH] = len(self[self.APPLICATION_DATA]) # Add all the values together to create the gss_channel_bindings_struct data = struct.pack(" # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import hashlib import hmac import os import struct from ntlm_auth.compute_response import ComputeResponse from ntlm_auth.constants import AvId, AvFlags, MessageTypes, NegotiateFlags, \ NTLM_SIGNATURE from ntlm_auth.rc4 import ARC4 try: from collections import OrderedDict except ImportError: # pragma: no cover from ordereddict import OrderedDict class TargetInfo(object): def __init__(self): self.fields = OrderedDict() def __setitem__(self, key, value): self.fields[key] = value def __getitem__(self, key): return self.fields.get(key, None) def __delitem__(self, key): del self.fields[key] def pack(self): if AvId.MSV_AV_EOL in self.fields: del self[AvId.MSV_AV_EOL] data = b'' for attribute_type, attribute_value in self.fields.items(): data += struct.pack(" 48: self.version = struct.unpack(" # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import base64 import struct from ntlm_auth.constants import NegotiateFlags from ntlm_auth.exceptions import NoAuthContextError from ntlm_auth.messages import AuthenticateMessage, ChallengeMessage, \ NegotiateMessage from ntlm_auth.session_security import SessionSecurity class NtlmContext(object): def __init__(self, username, password, domain=None, workstation=None, cbt_data=None, ntlm_compatibility=3): r""" Initialises a NTLM context to use when authenticating using the NTLM protocol. Initialises the NTLM context to use when sending and receiving messages to and from the server. You should be using this object as it supports NTLMv2 authenticate and it easier to use than before. It also brings in the ability to use signing and sealing with session_security and generate a MIC structure. :param username: The username to authenticate with :param password: The password for the username :param domain: The domain part of the username (None if n/a) :param workstation: The localworkstation (None if n/a) :param cbt_data: A GssChannelBindingsStruct or None to bind channel data with the auth process :param ntlm_compatibility: (Default 3) The Lan Manager Compatibility Level to use with the auth message This is set by an Administrator in the registry key 'HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel' The values correspond to the following; 0 : LM and NTLMv1 1 : LM, NTLMv1 and NTLMv1 with Extended Session Security 2 : NTLMv1 and NTLMv1 with Extended Session Security 3-5 : NTLMv2 Only Note: Values 3 to 5 are no different from a client perspective """ self.username = username self.password = password self.domain = domain self.workstation = workstation self.cbt_data = cbt_data self._server_certificate_hash = None # deprecated for backwards compat self.ntlm_compatibility = ntlm_compatibility self.complete = False # Setting up our flags so the challenge message returns the target info # block if supported self.negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \ NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \ NegotiateFlags.NTLMSSP_NEGOTIATE_56 | \ NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE | \ NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION | \ NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH | \ NegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \ NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN | \ NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL # Setting the message types based on the ntlm_compatibility level self._set_ntlm_compatibility_flags(self.ntlm_compatibility) self._negotiate_message = None self._challenge_message = None self._authenticate_message = None self._session_security = None @property def mic_present(self): if self._authenticate_message: return bool(self._authenticate_message.mic) return False @property def session_key(self): if self._authenticate_message: return self._authenticate_message.exported_session_key def reset_rc4_state(self, outgoing=True): """ Resets the signing cipher for the incoming or outgoing cipher. For SPNEGO for calculating mechListMIC. """ if self._session_security: self._session_security.reset_rc4_state(outgoing=outgoing) def step(self, input_token=None): if self._negotiate_message is None: self._negotiate_message = NegotiateMessage(self.negotiate_flags, self.domain, self.workstation) return self._negotiate_message.get_data() else: self._challenge_message = ChallengeMessage(input_token) self._authenticate_message = AuthenticateMessage( self.username, self.password, self.domain, self.workstation, self._challenge_message, self.ntlm_compatibility, server_certificate_hash=self._server_certificate_hash, cbt_data=self.cbt_data ) self._authenticate_message.add_mic(self._negotiate_message, self._challenge_message) flag_bytes = self._authenticate_message.negotiate_flags flags = struct.unpack("= 0) and (ntlm_compatibility <= 5): if ntlm_compatibility == 0: self.negotiate_flags |= \ NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \ NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY elif ntlm_compatibility == 1: self.negotiate_flags |= \ NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY else: self.negotiate_flags |= \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY else: raise Exception("Unknown ntlm_compatibility level - " "expecting value between 0 and 5") # Deprecated in favour of NtlmContext - this current class is heavily geared # towards a HTTP API which is not always the case with NTLM. This is currently # just a thin wrapper over NtlmContext and will be removed in future ntlm-auth # versions class Ntlm(object): def __init__(self, ntlm_compatibility=3): self._context = NtlmContext(None, None, ntlm_compatibility=ntlm_compatibility) self._challenge_token = None @property def negotiate_flags(self): return self._context.negotiate_flags @negotiate_flags.setter def negotiate_flags(self, value): self._context.negotiate_flags = value @property def ntlm_compatibility(self): return self._context.ntlm_compatibility @ntlm_compatibility.setter def ntlm_compatibility(self, value): self._context.ntlm_compatibility = value @property def negotiate_message(self): return self._context._negotiate_message @negotiate_message.setter def negotiate_message(self, value): self._context._negotiate_message = value @property def challenge_message(self): return self._context._challenge_message @challenge_message.setter def challenge_message(self, value): self._context._challenge_message = value @property def authenticate_message(self): return self._context._authenticate_message @authenticate_message.setter def authenticate_message(self, value): self._context._authenticate_message = value @property def session_security(self): return self._context._session_security @session_security.setter def session_security(self, value): self._context._session_security = value def create_negotiate_message(self, domain_name=None, workstation=None): self._context.domain = domain_name self._context.workstation = workstation msg = self._context.step() return base64.b64encode(msg) def parse_challenge_message(self, msg2): self._challenge_token = base64.b64decode(msg2) def create_authenticate_message(self, user_name, password, domain_name=None, workstation=None, server_certificate_hash=None): self._context.username = user_name self._context.password = password self._context.domain = domain_name self._context.workstation = workstation self._context._server_certificate_hash = server_certificate_hash msg = self._context.step(self._challenge_token) return base64.b64encode(msg) ntlm-auth-1.5.0/ntlm_auth/rc4.py0000664000175000017500000000413213672060540017552 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import struct # Favour cryptography over our Python implementation as it is a lot faster try: from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend HAS_CRYPTOGRAPHY = True except ImportError: # pragma: no cover HAS_CRYPTOGRAPHY = False class _CryptographyARC4(object): def __init__(self, key): algo = algorithms.ARC4(key) cipher = Cipher(algo, mode=None, backend=default_backend()) self._encryptor = cipher.encryptor() def update(self, value): return self._encryptor.update(value) class _PythonARC4(object): state = None i = 0 j = 0 def __init__(self, key): # Split up the key into a list key_bytes = [] for i in range(len(key)): key_byte = struct.unpack("B", key[i:i + 1])[0] key_bytes.append(key_byte) # Key-scheduling algorithm (KSA) self.state = [n for n in range(256)] j = 0 for i in range(256): j = (j + self.state[i] + key_bytes[i % len(key_bytes)]) % 256 self.state[i], self.state[j] = self.state[j], self.state[i] def update(self, value): chars = [] random_gen = self._random_generator() for i in range(len(value)): byte = struct.unpack("B", value[i:i + 1])[0] updated_byte = byte ^ next(random_gen) chars.append(updated_byte) return bytes(bytearray(chars)) def _random_generator(self): # Pseudo-Random Generation Algorithm (PRGA) while True: self.i = (self.i + 1) % 256 self.j = (self.j + self.state[self.i]) % 256 self.state[self.i], self.state[self.j] = \ self.state[self.j], self.state[self.i] yield self.state[(self.state[self.i] + self.state[self.j]) % 256] if HAS_CRYPTOGRAPHY: ARC4 = _CryptographyARC4 else: # pragma: no cover ARC4 = _PythonARC4 ntlm-auth-1.5.0/ntlm_auth/session_security.py0000664000175000017500000002605113672060540022500 0ustar jboreanjborean00000000000000# Copyright: (c) 2018, Jordan Borean (@jborean93) # MIT License (see LICENSE or https://opensource.org/licenses/MIT) import binascii import hashlib import hmac import struct import ntlm_auth.compute_keys as compkeys from ntlm_auth.constants import NegotiateFlags, SignSealConstants from ntlm_auth.rc4 import ARC4 class _NtlmMessageSignature1(object): EXPECTED_BODY_LENGTH = 16 def __init__(self, random_pad, checksum, seq_num): """ [MS-NLMP] v28.0 2016-07-14 2.2.2.9.1 NTLMSSP_MESSAGE_SIGNATURE This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is not negotiated. :param random_pad: A 4-byte array that contains the random pad for the message :param checksum: A 4-byte array that contains the checksum for the message :param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message """ self.version = b"\x01\x00\x00\x00" self.random_pad = random_pad self.checksum = checksum self.seq_num = seq_num def get_data(self): signature = self.version signature += self.random_pad signature += self.checksum signature += self.seq_num assert self.EXPECTED_BODY_LENGTH == len(signature), \ "BODY_LENGTH: %d != signature: %d" \ % (self.EXPECTED_BODY_LENGTH, len(signature)) return signature class _NtlmMessageSignature2(object): EXPECTED_BODY_LENGTH = 16 def __init__(self, checksum, seq_num): """ [MS-NLMP] v28.0 2016-07-14 2.2.2.9.2 NTLMSSP_MESSAGE_SIGNATURE for Extended Session Security This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is negotiated :param checksum: An 8-byte array that contains the checksum for the message :param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message """ self.version = b"\x01\x00\x00\x00" self.checksum = checksum self.seq_num = seq_num def get_data(self): signature = self.version signature += self.checksum signature += self.seq_num assert self.EXPECTED_BODY_LENGTH == len(signature),\ "BODY_LENGTH: %d != signature: %d"\ % (self.EXPECTED_BODY_LENGTH, len(signature)) return signature class SessionSecurity(object): def __init__(self, negotiate_flags, exported_session_key, source="client"): """ Initialises a security session context that can be used by libraries that call ntlm-auth to sign and seal messages send to the server as well as verify and unseal messages that have been received from the server. This is similar to the GSS_Wrap functions specified in the MS-NLMP document which does the same task. :param negotiate_flags: The negotiate flag structure that has been negotiated with the server :param exported_session_key: A 128-bit session key used to derive signing and sealing keys :param source: The source of the message, only used in test scenarios when testing out a server sealing and unsealing """ self.negotiate_flags = negotiate_flags self.exported_session_key = exported_session_key self.outgoing_seq_num = 0 self.incoming_seq_num = 0 self._source = source self._client_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.CLIENT_SEALING) self._server_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.SERVER_SEALING) self.outgoing_handle = None self.incoming_handle = None self.reset_rc4_state(True) self.reset_rc4_state(False) if source == "client": self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING) self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING) elif source == "server": self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING) self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING) else: raise ValueError("Invalid source parameter %s, must be client " "or server" % source) def reset_rc4_state(self, outgoing=True): csk = self._client_sealing_key ssk = self._server_sealing_key if outgoing: self.outgoing_handle = ARC4(csk if self._source == 'client' else ssk) else: self.incoming_handle = ARC4(ssk if self._source == 'client' else csk) def wrap(self, message): """ [MS-NLMP] v28.0 2016-07-14 3.4.6 GSS_WrapEx() Emulates the GSS_Wrap() implementation to sign and seal messages if the correct flags are set. :param message: The message data that will be wrapped :return message: The message that has been sealed if flags are set :return signature: The signature of the message, None if flags are not set """ if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL: encrypted_message = self._seal_message(message) signature = self.get_signature(message) message = encrypted_message elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN: signature = self.get_signature(message) else: signature = None return message, signature def unwrap(self, message, signature): """ [MS-NLMP] v28.0 2016-07-14 3.4.7 GSS_UnwrapEx() Emulates the GSS_Unwrap() implementation to unseal messages and verify the signature sent matches what has been computed locally. Will throw an Exception if the signature doesn't match :param message: The message data received from the server :param signature: The signature of the message :return message: The message that has been unsealed if flags are set """ if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL: message = self._unseal_message(message) self.verify_signature(message, signature) elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN: self.verify_signature(message, signature) return message def _seal_message(self, message): """ [MS-NLMP] v28.0 2016-07-14 3.4.3 Message Confidentiality Will generate an encrypted message using RC4 based on the ClientSealingKey :param message: The message to be sealed (encrypted) :return encrypted_message: The encrypted message """ encrypted_message = self.outgoing_handle.update(message) return encrypted_message def _unseal_message(self, message): """ [MS-NLMP] v28.0 2016-07-14 3.4.3 Message Confidentiality Will generate a dencrypted message using RC4 based on the ServerSealingKey :param message: The message to be unsealed (dencrypted) :return decrypted_message: The decrypted message """ decrypted_message = self.incoming_handle.update(message) return decrypted_message def get_signature(self, message): """ [MS-NLMP] v28.0 2016-07-14 3.4.4 Message Signature Functions Will create the signature based on the message to send to the server. Depending on the negotiate_flags set this could either be an NTLMv1 signature or NTLMv2 with Extended Session Security signature. :param message: The message data that will be signed :return signature: Either _NtlmMessageSignature1 or _NtlmMessageSignature2 depending on the flags set """ signature = calc_signature(message, self.negotiate_flags, self.outgoing_signing_key, self.outgoing_seq_num, self.outgoing_handle) self.outgoing_seq_num += 1 return signature.get_data() def verify_signature(self, message, signature): """ Will verify that the signature received from the server matches up with the expected signature computed locally. Will throw an exception if they do not match :param message: The message data that is received from the server :param signature: The signature of the message received from the server """ if self.negotiate_flags & \ NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: actual_checksum = signature[4:12] actual_seq_num = struct.unpack("`__ instead of this library. The library requests-ntlm is a plugin that uses this library under the hood and provides an easier function to use and understand. If you are set on using ntlm-auth directly to compute the message structures this is a very basic outline of how it can be done. The code examples are psuedocode and should be adapted for your purpose. When initliasing the ntlm context you will have to supply the NTLM compatibility level. The key difference between the different auth levels are the ntlm_compatibility variable supplied when initialising Ntlm. An overview of what each sets is below; \* ``0`` - LM Auth and NTLMv1 Auth \* ``1`` - LM Auth and NTLMv1 Auth with Extended Session Security (NTLM2) \* ``2`` - NTLMv1 Auth with Extended Session Security (NTLM2) \* ``3`` - NTLMv2 Auth (Default Choice) \* ``4`` - NTLMv2 Auth \* ``5`` - NTLMv2 Auth Level 3 to 5 are the same from a client perspective but differ with how the server handles the auth which is outside this project’s scope. This setting is set independently on that server so choosing 3, 4 or 5 when calling Ntlm will make no difference at all. See `LmCompatibilityLevel `__ for more details. Extended Session Security is a security feature designed to increase the security of LM and NTLMv1 auth. It is no substitution for NTLMv2 but is better than nothing and should be used if possible when you need NTLMv1 compatibility. The variables required are outlined below; \* ``username`` - The username to authenticate with, should not have the domain prefix, i.e. USER not DOMAIN\USER \* ``password`` - The password of the user to authenticate with \* ``domain`` - The domain of the user, i.e. DOMAIN. Can be blank if not in a domain environment \* ``workstation`` - The workstation you are running on. Can be blank if you do not wish to send this \* ``cbt_data`` - (NTLMv2 only) The ``gss_channel_bindings.GssChannelBindingsStruct`` used to bind with the auth response. Can be None if no binding needs to occur LM Auth/NTLMv1 Auth ^^^^^^^^^^^^^^^^^^^ LM and NTLMv1 Auth are older authentication methods that should be avoided where possible. Choosing between these authentication methods are almost identical expect where you specify the ntlm_compatiblity level. .. code:: python import socket from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info ntlm_context = NtlmContext(username, password, domain, workstation, ntlm_compatibility=0) # Put the ntlm_compatibility level here, 0-2 for LM Auth/NTLMv1 Auth negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 NTLMv2 ^^^^^^ NTLMv2 Auth is the newest NTLM auth method from Microsoft and should be the option chosen by default unless you require an older auth method. The implementation is the same as NTLMv1 but with the addition of the optional ``server_certificate_hash`` variable and the ``ntlm_compatibility`` is not specified. .. code:: python import base64 import socket from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info # create the CBT struct if you wish to bind it with the auth response server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' certificate_digest = base64.b16decode(server_certificate_hash) cbt_data = GssChannelBindingsStruct() cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3) negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 Signing/Sealing ^^^^^^^^^^^^^^^ All version of NTLM supports signing (integrity) and sealing (confidentiality) of message content. This function can add these improvements to a message that is sent and received from the server. While it does encrypt the data if supported by the server it is only done with RC4 with a 128-bit key which is not very secure and on older systems this key length could be 56 or 40 bit. This functionality while tested and conforms with the Microsoft documentation has yet to be fully tested in an integrated environment. Once again this has not been thoroughly tested and has only passed unit tests and their expections. .. code:: python import base64 import socket from ntlm_auth.ntlm import NtlmContext username = 'User' password = 'Password' domain = 'Domain' # Can be blank if you are not in a domain workstation = socket.gethostname().upper() # Can be blank if you wish to not send this info # create the CBT struct if you wish to bind it with the auth response server_certificate_hash = '96B2FC1EC30792619286A0C7FD62863E81A6564E72829CBC0A46F7B1D5D92A18' certificate_digest = base64.b16decode(server_certificate_hash) cbt_data = GssChannelBindingsStruct() cbt_data[cbt_data.APPLICATION_DATA] = b'tls-server-end-point:' + certificate_digest ntlm_context = NtlmContext(username, password, domain, workstation, cbt_data, ntlm_compatibility=3) negotiate_message = ntlm_context.step() # Attach the negotiate_message to your NTLM/NEGOTIATE HTTP header and send to the server. Get the challenge response back from the server challenge_message = http.response.headers['HEADERFIELD'] authenticate_message = ntlm_context.step(challenge_message) # Attach the authenticate_message ot your NTLM_NEGOTIATE HTTP header and send to the server. You are now authenticated with NTLMv1 # Encrypt the message with the wrapping function and send the message enc_message = ntlm_context.wrap("Message to send", encrypt=True) request.body = msg_data request.send # Receive the response from the server and decrypt response_msg = response.content response = ntlm_context.unwrap(response_msg) Backlog ------- - Automatically get windows version if running on windows, use default if not that case - Add param when initialising the ntlm context to throw an exception and cancel auth if the server doesn’t support 128-bit keys for sealing - Add param when initialising the ntlm context to not send the MIC structure for older servers - Add param to independently verify the target name returned from the server and the value passed in .. |Build Status| image:: https://travis-ci.org/jborean93/ntlm-auth.svg?branch=master :target: https://travis-ci.org/jborean93/ntlm-auth .. |Build status| image:: https://ci.appveyor.com/api/projects/status/osvvfgmhfk4anvu0/branch/master?svg=true :target: https://ci.appveyor.com/project/jborean93/ntlm-auth/branch/master .. |Coverage Status| image:: https://coveralls.io/repos/github/jborean93/ntlm-auth/badge.svg?branch=master :target: https://coveralls.io/github/jborean93/ntlm-auth?branch=master Keywords: authentication auth microsoft ntlm lm Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* Provides-Extra: cryptography ntlm-auth-1.5.0/ntlm_auth.egg-info/SOURCES.txt0000664000175000017500000000100013672062446022064 0ustar jboreanjborean00000000000000CHANGES.md LICENSE MANIFEST.in README.md setup.cfg setup.py ntlm_auth/__init__.py ntlm_auth/compute_hash.py ntlm_auth/compute_keys.py ntlm_auth/compute_response.py ntlm_auth/constants.py ntlm_auth/des.py ntlm_auth/exceptions.py ntlm_auth/gss_channel_bindings.py ntlm_auth/messages.py ntlm_auth/ntlm.py ntlm_auth/rc4.py ntlm_auth/session_security.py ntlm_auth.egg-info/PKG-INFO ntlm_auth.egg-info/SOURCES.txt ntlm_auth.egg-info/dependency_links.txt ntlm_auth.egg-info/requires.txt ntlm_auth.egg-info/top_level.txtntlm-auth-1.5.0/ntlm_auth.egg-info/dependency_links.txt0000664000175000017500000000000113672062446024257 0ustar jboreanjborean00000000000000 ntlm-auth-1.5.0/ntlm_auth.egg-info/requires.txt0000664000175000017500000000021613672062446022610 0ustar jboreanjborean00000000000000 [:python_version<"2.7"] ordereddict [cryptography:python_version<"2.7"] cryptography<2.2 [cryptography:python_version>="2.7"] cryptography ntlm-auth-1.5.0/ntlm_auth.egg-info/top_level.txt0000664000175000017500000000001213672062446022734 0ustar jboreanjborean00000000000000ntlm_auth ntlm-auth-1.5.0/setup.cfg0000664000175000017500000000021513672062446016343 0ustar jboreanjborean00000000000000[bdist_wheel] universal = 1 [metadata] license_file = LICENSE [tool:pytest] pep8maxlinelength = 119 [egg_info] tag_build = tag_date = 0 ntlm-auth-1.5.0/setup.py0000664000175000017500000000323113672060650016230 0ustar jboreanjborean00000000000000#!/usr/bin/env python # coding: utf-8 from setuptools import setup # PyPi supports only reStructuredText, so pandoc should be installed # before uploading package try: import pypandoc long_description = pypandoc.convert('README.md', 'rst') except ImportError: long_description = '' setup( name='ntlm-auth', version='1.5.0', packages=['ntlm_auth'], install_requires=[], extras_require={ ':python_version<"2.7"': [ 'ordereddict' ], # Adds faster RC4 message encryption, optional as we can fallback # to the slower Python imp. 'cryptography:python_version<"2.7"': [ 'cryptography<2.2' # 2.2+ droppped Python 2.6 support ], 'cryptography:python_version>="2.7"': [ 'cryptography' ] }, author='Jordan Borean', author_email='jborean93@gmail.com', url='https://github.com/jborean93/ntlm-auth', description='Creates NTLM authentication structures', long_description=long_description, keywords='authentication auth microsoft ntlm lm', license='MIT', python_requires='>=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], )