Overview
The ntlm module provides a complete implementation of the NT LAN Manager (NTLM) authentication protocol, including NTLMv1, NTLMv2, and NTLM2 Session Security. It handles challenge-response authentication, hash generation, and session key derivation.
Key Functions
getNTLMSSPType1()
Create an NTLM Type 1 (Negotiate) message.
from impacket import ntlm
type1 = ntlm.getNTLMSSPType1(workstation='CLIENT', domain='CORP',
signingRequired=False, use_ntlmv2=True)
Workstation name to send in negotiate message
Whether message signing is required
Use NTLMv2 protocol (recommended)
OS version structure to include
getNTLMSSPType3()
Create an NTLM Type 3 (Authenticate) message.
type3, session_key = ntlm.getNTLMSSPType3(
type1, type2, user, password, domain,
lmhash='', nthash='', use_ntlmv2=True
)
type1
NTLMAuthNegotiate
required
Type 1 message from getNTLMSSPType1()
Type 2 (Challenge) message from server
Username for authentication
Password (not used if hashes provided)
Channel binding data for EPA (Extended Protection for Authentication)
Service principal name (e.g., ‘cifs’, ‘http’, ‘ldap’)
type3
NTLMAuthChallengeResponse
Type 3 authenticate message
Session key for signing/encryption
Hash Computation
compute_lmhash()
Compute LM hash from password.
from impacket.ntlm import compute_lmhash
lmhash = compute_lmhash('Password123')
Password to hash (only Latin-1 characters supported)
If password contains non-Latin-1 characters, returns the default empty LM hash.
compute_nthash()
Compute NT hash from password.
from impacket.ntlm import compute_nthash
nthash = compute_nthash('Password123')
Password to hash (Unicode supported)
16-byte NT hash (MD4 of Unicode password)
Response Computation
computeResponse()
Compute NTLM challenge response.
ntResponse, lmResponse, sessionKey = ntlm.computeResponse(
flags, serverChallenge, clientChallenge, serverName,
domain, user, password, lmhash='', nthash='',
use_ntlmv2=True
)
8-byte challenge from server
Target information from Type 2 message
Signing and Sealing
SIGN()
Sign a message.
signature = ntlm.SIGN(flags, signingKey, message, seqNum, handle)
Signing key from SIGNKEY()
SEAL()
Encrypt and sign a message.
sealedMsg, signature = ntlm.SEAL(
flags, signingKey, sealingKey,
messageToSign, messageToEncrypt,
seqNum, handle
)
Message data for signature
SIGNKEY()
Derive signing key from session key.
signKey = ntlm.SIGNKEY(flags, randomSessionKey, mode='Client')
Direction: 'Client' or 'Server'
SEALKEY()
Derive sealing (encryption) key from session key.
sealKey = ntlm.SEALKEY(flags, randomSessionKey, mode='Client')
Direction: 'Client' or 'Server'
Sealing key (encryption key)
Classes
NTLMAuthNegotiate
NTLM Type 1 (Negotiate) message.
auth = ntlm.NTLMAuthNegotiate()
auth['flags'] = ntlm.NTLMSSP_NEGOTIATE_UNICODE | \
ntlm.NTLMSSP_NEGOTIATE_NTLM
data = auth.getData()
NTLMAuthChallenge
NTLM Type 2 (Challenge) message.
challenge = ntlm.NTLMAuthChallenge(data)
server_challenge = challenge['challenge']
target_info = challenge['TargetInfoFields']
AV_PAIRS structure with server information
Server’s negotiation flags
NTLMAuthChallengeResponse
NTLM Type 3 (Authenticate) message.
response = ntlm.NTLMAuthChallengeResponse(
username='admin',
password='password',
challenge=server_challenge
)
data = response.getData()
Workstation name (UTF-16LE)
Encrypted random session key
AV_PAIRS
Attribute-Value pairs for target information.
av_pairs = ntlm.AV_PAIRS(targetInfo)
# Access fields
hostname = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME]
dns_name = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME]
# Set fields
av_pairs[ntlm.NTLMSSP_AV_TARGET_NAME] = b'cifs/server'
# Get serialized data
data = av_pairs.getData()
NTLMSSP_AV_DNS_DOMAINNAME
DNS domain name
NTLMSSP_AV_CHANNEL_BINDINGS
Channel binding data
Constants
NTLM Flags
# Negotiation flags
NTLMSSP_NEGOTIATE_UNICODE = 0x00000001
NTLMSSP_NEGOTIATE_OEM = 0x00000002
NTLMSSP_REQUEST_TARGET = 0x00000004
NTLMSSP_NEGOTIATE_SIGN = 0x00000010
NTLMSSP_NEGOTIATE_SEAL = 0x00000020
NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040
NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080
NTLMSSP_NEGOTIATE_NTLM = 0x00000200
NTLMSSP_NEGOTIATE_ANONYMOUS = 0x00000800
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000
NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000
NTLMSSP_TARGET_TYPE_SERVER = 0x00020000
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
NTLMSSP_NEGOTIATE_IDENTIFY = 0x00100000
NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000
NTLMSSP_NEGOTIATE_VERSION = 0x02000000
NTLMSSP_NEGOTIATE_128 = 0x20000000
NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000
NTLMSSP_NEGOTIATE_56 = 0x80000000
AV Pair Types
NTLMSSP_AV_EOL = 0x00 # End of list
NTLMSSP_AV_HOSTNAME = 0x01 # Server hostname
NTLMSSP_AV_DOMAINNAME = 0x02 # Domain name
NTLMSSP_AV_DNS_HOSTNAME = 0x03 # DNS hostname
NTLMSSP_AV_DNS_DOMAINNAME = 0x04 # DNS domain
NTLMSSP_AV_DNS_TREENAME = 0x05 # DNS tree name
NTLMSSP_AV_FLAGS = 0x06 # Flags
NTLMSSP_AV_TIME = 0x07 # Timestamp
NTLMSSP_AV_RESTRICTIONS = 0x08 # Restrictions
NTLMSSP_AV_TARGET_NAME = 0x09 # Target SPN
NTLMSSP_AV_CHANNEL_BINDINGS = 0x0a # Channel bindings
Global Settings
# Set globally to control NTLMv2 usage
ntlm.USE_NTLMv2 = True # Recommended
# Default LM hash (empty password)
ntlm.DEFAULT_LM_HASH = b'\xaa\xd3\xb4...'
Usage Examples
Basic NTLM Authentication Flow
from impacket import ntlm
# Step 1: Create Type 1 (Negotiate) message
type1 = ntlm.getNTLMSSPType1(
workstation='WORKSTATION',
domain='CORP',
use_ntlmv2=True
)
# Send type1.getData() to server...
# Receive Type 2 (Challenge) from server
# Step 2: Parse Type 2 message
type2_data = b'...' # Received from server
type2 = ntlm.NTLMAuthChallenge(type2_data)
# Step 3: Create Type 3 (Authenticate) message
type3, session_key = ntlm.getNTLMSSPType3(
type1=type1,
type2=type2_data,
user='admin',
password='password',
domain='CORP',
use_ntlmv2=True
)
# Send type3.getData() to server
print(f"Session key: {session_key.hex()}")
Pass-the-Hash Authentication
from impacket import ntlm
# Use pre-computed hashes instead of password
lmhash = 'aad3b435b51404eeaad3b435b51404ee'
nthash = '8846f7eaee8fb117ad06bdd830b7586c'
type1 = ntlm.getNTLMSSPType1()
# ... receive type2 ...
type3, session_key = ntlm.getNTLMSSPType3(
type1=type1,
type2=type2_data,
user='admin',
password='', # Not used
domain='CORP',
lmhash=lmhash,
nthash=nthash,
use_ntlmv2=True
)
Computing Password Hashes
from impacket import ntlm
import binascii
password = 'Password123!'
# Compute hashes
lmhash = ntlm.compute_lmhash(password)
nthash = ntlm.compute_nthash(password)
print(f"LM Hash: {binascii.hexlify(lmhash).decode()}")
print(f"NT Hash: {binascii.hexlify(nthash).decode()}")
# Use hashes for authentication
type3, key = ntlm.getNTLMSSPType3(
type1, type2, 'user', '',
lmhash=binascii.hexlify(lmhash).decode(),
nthash=binascii.hexlify(nthash).decode()
)
Message Signing
from impacket import ntlm
from Cryptodome.Cipher import ARC4
# After authentication, derive signing key
flags = ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY | \
ntlm.NTLMSSP_NEGOTIATE_128
signingKey = ntlm.SIGNKEY(flags, session_key, mode='Client')
# Create RC4 cipher handle
cipher = ARC4.new(signingKey)
# Sign a message
message = b"Data to sign"
seqNum = 0
signature = ntlm.SIGN(
flags=flags,
signingKey=signingKey,
message=message,
seqNum=seqNum,
handle=cipher.encrypt
)
print(f"Signature: {signature.getData().hex()}")
Message Encryption (Sealing)
from impacket import ntlm
from Cryptodome.Cipher import ARC4
# Derive encryption key
flags = ntlm.NTLMSSP_NEGOTIATE_SEAL | \
ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
signingKey = ntlm.SIGNKEY(flags, session_key, mode='Client')
sealingKey = ntlm.SEALKEY(flags, session_key, mode='Client')
cipher = ARC4.new(sealingKey)
# Encrypt and sign message
message = b"Secret data"
seqNum = 0
sealedMsg, signature = ntlm.SEAL(
flags=flags,
signingKey=signingKey,
sealingKey=sealingKey,
messageToSign=message,
messageToEncrypt=message,
seqNum=seqNum,
handle=cipher.encrypt
)
print(f"Encrypted: {sealedMsg.hex()}")
print(f"Signature: {signature.getData().hex()}")
Working with AV_PAIRS
from impacket import ntlm
# Parse target info from Type 2
type2 = ntlm.NTLMAuthChallenge(type2_data)
av_pairs = ntlm.AV_PAIRS(type2['TargetInfoFields'])
# Extract information
if av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME]:
hostname = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1]
print(f"Server: {hostname.decode('utf-16le')}")
if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME]:
domain = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1]
print(f"Domain: {domain.decode('utf-16le')}")
# Add custom fields
av_pairs[ntlm.NTLMSSP_AV_TARGET_NAME] = \
b'http/server.domain.com'.encode('utf-16le')
# Serialize back to bytes
modified_target_info = av_pairs.getData()
HTTP NTLM Authentication
from impacket import ntlm
import base64
# Step 1: Send initial request, get 401 with WWW-Authenticate: NTLM
type1 = ntlm.getNTLMSSPType1()
type1_b64 = base64.b64encode(type1.getData()).decode()
# Send Authorization: NTLM <type1_b64>
# Receive 401 with WWW-Authenticate: NTLM <type2_b64>
# Step 2: Parse challenge
type2_b64 = "..." # From WWW-Authenticate header
type2_data = base64.b64decode(type2_b64)
type3, session_key = ntlm.getNTLMSSPType3(
type1=type1,
type2=type2_data,
user='admin',
password='password',
domain='CORP'
)
type3_b64 = base64.b64encode(type3.getData()).decode()
# Send Authorization: NTLM <type3_b64>
print(f"Authorization: NTLM {type3_b64}")
from impacket import ntlm
# Parse Type 3 message
type3_data = b'...' # Captured authenticate message
type3 = ntlm.NTLMAuthChallengeResponse()
type3.fromString(type3_data)
# Get user info
user_string = type3.getUserString()
print(f"User: {user_string}") # Format: DOMAIN/username
# Get individual fields
if type3['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:
user = type3['user_name'].decode('utf-16le')
domain = type3['domain_name'].decode('utf-16le')
workstation = type3['host_name'].decode('utf-16le')
else:
user = type3['user_name'].decode('cp437')
domain = type3['domain_name'].decode('cp437')
workstation = type3['host_name'].decode('cp437')
print(f"Username: {user}")
print(f"Domain: {domain}")
print(f"Workstation: {workstation}")
Security Considerations
NTLMv1 is deprecated and insecure. Always use NTLMv2 when possible by setting use_ntlmv2=True.
LM hashes are weak and should not be used for authentication. They are computed only from the first 14 characters of passwords converted to uppercase.
Best Practices
- Use NTLMv2 - Set
ntlm.USE_NTLMv2 = True globally
- Enable signing - Use
NTLMSSP_NEGOTIATE_SIGN flag
- Enable sealing - Use
NTLMSSP_NEGOTIATE_SEAL for encryption
- Strong passwords - Use complex passwords to prevent hash cracking
- Extended session security - Enable
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
Helper Functions
NTOWFv2()
Compute NTLMv2 hash.
hash = ntlm.NTOWFv2(user, password, domain, hash='')
LMOWFv2()
Compute LMv2 hash.
hash = ntlm.LMOWFv2(user, password, domain, lmhash='')
hmac_md5()
Compute HMAC-MD5.
result = ntlm.hmac_md5(key, data)
See Also
- SMBConnection - Uses NTLM for authentication
- SMB - SMB protocol with NTLM support