Documentation Index
Fetch the complete documentation index at: https://mintlify.com/fortra/impacket/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The impacket.krb5.crypto module provides cryptographic primitives for Kerberos v5, implementing encryption/decryption, key derivation, checksum operations, and string-to-key functions for multiple cipher suites.
Module Location
from impacket.krb5.crypto import (
Key, encrypt, decrypt, string_to_key,
make_checksum, verify_checksum,
random_to_key, prf, cf2
)
Source: impacket/krb5/crypto.py
Supported Encryption Types
Enctype Class
Encryption type identifiers:
class Enctype:
DES_CRC = 1 # DES-CBC-CRC (deprecated)
DES_MD4 = 2 # DES-CBC-MD4 (deprecated)
DES_MD5 = 3 # DES-CBC-MD5 (deprecated)
DES3 = 16 # DES3-CBC-SHA1
AES128 = 17 # AES128-CTS-HMAC-SHA1-96
AES256 = 18 # AES256-CTS-HMAC-SHA1-96 (recommended)
RC4 = 23 # RC4-HMAC
Cksumtype Class
Checksum type identifiers:
class Cksumtype:
CRC32 = 1
MD4 = 2
MD4_DES = 3
MD5 = 7
MD5_DES = 8
SHA1 = 9
SHA1_DES3 = 12
SHA1_AES128 = 15
SHA1_AES256 = 16
HMAC_MD5 = -138 # RC4-HMAC checksum
Key Class
Represents a Kerberos encryption key.
class Key:
def __init__(self, enctype, contents)
Attributes:
enctype: Encryption type identifier
contents: Raw key bytes
Example:
from impacket.krb5.crypto import Key, Enctype
from binascii import unhexlify
# Create AES256 key
key = Key(
Enctype.AES256,
unhexlify('a1b2c3d4e5f6...') # 32 bytes for AES256
)
print(f"Enctype: {key.enctype}")
print(f"Key length: {len(key.contents)}")
Key Sizes:
- DES: 8 bytes
- DES3: 24 bytes
- AES128: 16 bytes
- AES256: 32 bytes
- RC4: 16 bytes
String-to-Key Operations
string_to_key Function
Derive key from password and salt.
def string_to_key(enctype, string, salt, params=None)
Parameters:
enctype: Encryption type (int)
string: Password (str or bytes)
salt: Salt value (str or bytes)
params: Optional algorithm parameters
Returns: Key object
Example:
from impacket.krb5.crypto import string_to_key, Enctype
# Derive AES256 key
key = string_to_key(
Enctype.AES256,
'MyPassword123',
'DOMAIN.LOCALuser',
params=b'\x00\x00\x10\x00' # 4096 iterations
)
print(f"Derived key: {key.contents.hex()}")
Salt Generation
Standard Kerberos salt formats:
# User principal salt
username = 'john'
domain = 'DOMAIN.LOCAL'
salt = f"{domain.upper()}{username}"
# Service principal salt (machine accounts)
computername = 'SERVER'
domain = 'domain.local'
salt = f"{domain.upper()}host{computername.lower()}.{domain.lower()}"
# Examples:
# User: "DOMAIN.LOCALjohn"
# Computer: "DOMAIN.LOCALhostserver.domain.local"
Algorithm-Specific Details
AES String-to-Key (PBKDF2)
from impacket.krb5.crypto import string_to_key, Enctype
import struct
# Default 4096 iterations
iterations = 4096
params = struct.pack('>L', iterations)
key = string_to_key(
Enctype.AES256,
'password',
'DOMAIN.LOCALuser',
params=params
)
Process:
- PBKDF2-HMAC-SHA1(password, salt, iterations, keysize)
- Derive using “kerberos” constant
RC4 String-to-Key
key = string_to_key(
Enctype.RC4,
'Password123',
'', # Salt ignored for RC4
params=None
)
Process: MD4(UTF-16LE(password))
DES3 String-to-Key
key = string_to_key(
Enctype.DES3,
'password',
'DOMAIN.LOCALuser',
params=None
)
Process:
- n-fold(password + salt, 21)
- Random-to-key with parity bits
- Derive with “kerberos” constant
Encryption Operations
encrypt Function
Encrypt plaintext with key.
def encrypt(key, keyusage, plaintext, confounder=None)
Parameters:
key: Key object
keyusage: Key usage number (int)
plaintext: Data to encrypt (bytes)
confounder: Optional confounder (None = random)
Returns: Ciphertext bytes
Example:
from impacket.krb5.crypto import encrypt, string_to_key, Enctype
key = string_to_key(Enctype.AES256, 'password', 'DOMAIN.LOCALuser')
# Encrypt timestamp for AS-REQ
plaintext = b'encoded_timestamp_data'
ciphertext = encrypt(
key,
1, # Key usage: AS-REQ PA-ENC-TIMESTAMP
plaintext,
confounder=None # Auto-generate
)
print(f"Ciphertext: {ciphertext.hex()}")
decrypt Function
Decrypt ciphertext with key.
def decrypt(key, keyusage, ciphertext)
Parameters:
key: Key object
keyusage: Key usage number (int)
ciphertext: Encrypted data (bytes)
Returns: Plaintext bytes
Raises: InvalidChecksum if integrity check fails
Example:
from impacket.krb5.crypto import decrypt, InvalidChecksum
try:
plaintext = decrypt(
key,
3, # Key usage: AS-REP enc-part
ciphertext
)
print(f"Decrypted: {plaintext.hex()}")
except InvalidChecksum:
print("Decryption failed: invalid checksum")
Key Usage Numbers
Standard key usage values from RFC 4120:
# Ticket encryption
KU_TICKET = 2 # Ticket enc-part
# AS-REQ/AS-REP
KU_AS_REQ_PA_ENC_TIMESTAMP = 1 # PA-ENC-TIMESTAMP
KU_AS_REP_ENC_PART = 3 # AS-REP enc-part
# TGS-REQ/TGS-REP
KU_TGS_REQ_AUTH = 7 # TGS-REQ Authenticator
KU_TGS_REP_ENC_PART = 8 # TGS-REP enc-part
# AP-REQ/AP-REP
KU_AP_REQ_AUTH = 11 # AP-REQ Authenticator
KU_AP_REP_ENC_PART = 12 # AP-REP enc-part
# Other
KU_KRB_PRIV = 13 # KRB-PRIV enc-part
KU_KRB_CRED = 14 # KRB-CRED enc-part
KU_KRB_SAFE_CKSUM = 15 # KRB-SAFE checksum
Example Usage:
# Decrypt AS-REP
plaintext = decrypt(key, 3, asrep_ciphertext)
# Encrypt TGS-REQ authenticator
ciphertext = encrypt(sessionKey, 7, authenticator_data)
# Decrypt TGS-REP
plaintext = decrypt(sessionKey, 8, tgsrep_ciphertext)
Checksum Operations
make_checksum Function
Compute keyed checksum.
def make_checksum(cksumtype, key, keyusage, text)
Parameters:
cksumtype: Checksum type (int)
key: Key object
keyusage: Key usage number (int)
text: Data to checksum (bytes)
Returns: Checksum bytes
Example:
from impacket.krb5.crypto import make_checksum, Cksumtype
cksum = make_checksum(
Cksumtype.SHA1_AES256,
key,
11, # AP-REQ authenticator
plaintext
)
print(f"Checksum: {cksum.hex()}")
print(f"Length: {len(cksum)} bytes")
verify_checksum Function
Verify keyed checksum.
def verify_checksum(cksumtype, key, keyusage, text, cksum)
Parameters:
cksumtype: Checksum type (int)
key: Key object
keyusage: Key usage number (int)
text: Data that was checksummed (bytes)
cksum: Checksum to verify (bytes)
Raises: InvalidChecksum if verification fails
Example:
from impacket.krb5.crypto import verify_checksum, InvalidChecksum, Cksumtype
try:
verify_checksum(
Cksumtype.SHA1_AES256,
key,
11,
plaintext,
received_checksum
)
print("Checksum valid")
except InvalidChecksum:
print("Checksum verification failed")
Advanced Key Operations
random_to_key Function
Convert random bytes to key.
def random_to_key(enctype, seed)
Parameters:
enctype: Encryption type (int)
seed: Random seed bytes (seedsize length)
Returns: Key object
Example:
from impacket.krb5.crypto import random_to_key, get_random_bytes, Enctype
# Generate random AES256 key
seed = get_random_bytes(32) # AES256 seed size
key = random_to_key(Enctype.AES256, seed)
print(f"Key: {key.contents.hex()}")
Seed Sizes:
- DES: 8 bytes
- DES3: 21 bytes
- AES128: 16 bytes
- AES256: 32 bytes
- RC4: 16 bytes
prf Function
Pseudo-Random Function for key derivation.
Parameters:
key: Key object
string: Input string (bytes)
Returns: Output bytes
Example:
from impacket.krb5.crypto import prf
# Derive key material
output = prf(key, b'specific-usage')
print(f"PRF output: {output.hex()}")
cf2 Function
Combine two keys (RFC 6113 KRB-FX-CF2).
def cf2(enctype, key1, key2, pepper1, pepper2)
Parameters:
enctype: Target encryption type
key1: First Key object
key2: Second Key object
pepper1: First pepper (bytes)
pepper2: Second pepper (bytes)
Returns: Combined Key object
Example:
from impacket.krb5.crypto import cf2, Enctype
combined_key = cf2(
Enctype.AES256,
key1,
key2,
b'first-pepper',
b'second-pepper'
)
Use Case: FAST armor key generation
Cipher Suite Details
AES Encryption (Simplified Profile)
AES128 and AES256 use RFC 3961 simplified profile:
Encryption Process:
- Derive Ki = DK(key, usage | 0x55)
- Derive Ke = DK(key, usage | 0xAA)
- Generate random confounder (16 bytes)
- Plaintext’ = confounder + plaintext (zero-padded)
- HMAC = HMAC-SHA1(Ki, plaintext’)
- Ciphertext = E(Ke, plaintext’) + HMAC[0:12]
Decryption Process:
- Derive Ki and Ke
- Split ciphertext and MAC
- Decrypt: plaintext’ = D(Ke, ciphertext)
- Verify: HMAC-SHA1(Ki, plaintext’)[0:12] == MAC
- Remove confounder: plaintext = plaintext’[16:]
Example:
from impacket.krb5.crypto import _AES256CTS
# Direct cipher access
cipher = _AES256CTS()
plaintext = b'test data'
confounder = get_random_bytes(16)
# Encrypt with key usage
ciphertext = cipher.encrypt(key, 11, plaintext, confounder)
# Decrypt
recovered = cipher.decrypt(key, 11, ciphertext)
assert recovered == plaintext
RC4 Encryption
RC4-HMAC (also known as ARCFOUR-HMAC-MD5):
Encryption Process:
- Ki = HMAC-MD5(key, usage)
- Checksum = HMAC-MD5(Ki, confounder + plaintext)
- Ke = HMAC-MD5(Ki, checksum)
- Ciphertext = checksum + RC4(Ke, confounder + plaintext)
Example:
from impacket.krb5.crypto import _RC4
cipher = _RC4()
key = cipher.string_to_key('Password123', '', None)
ciphertext = cipher.encrypt(key, 11, b'test data', None)
plaintext = cipher.decrypt(key, 11, ciphertext)
DES3 Encryption
Triple DES with CBC mode:
Encryption Process:
- Derive Ki = DK(key, usage | 0x55)
- Derive Ke = DK(key, usage | 0xAA)
- Generate random confounder (8 bytes)
- Plaintext’ = confounder + plaintext (zero-padded to 8-byte boundary)
- HMAC = HMAC-SHA1(Ki, plaintext’)
- Ciphertext = E-DES3-CBC(Ke, plaintext’) + HMAC
Example:
from impacket.krb5.crypto import _DES3CBC
cipher = _DES3CBC()
key = cipher.string_to_key('password', 'DOMAIN.LOCALuser', None)
ciphertext = cipher.encrypt(key, 11, b'test data', None)
plaintext = cipher.decrypt(key, 11, ciphertext)
Key Derivation
DK Function (Key Derivation)
Internal function for deriving keys:
# Conceptual - internal to cipher classes
def derive(cls, key, constant):
plaintext = nfold(constant, blocksize)
rndseed = b''
while len(rndseed) < seedsize:
ciphertext = basic_encrypt(key, plaintext)
rndseed += ciphertext
plaintext = ciphertext
return random_to_key(rndseed[0:seedsize])
Constants:
usage | 0x55: Integrity key (Ki)
usage | 0xAA: Encryption key (Ke)
usage | 0x99: Checksum key (Kc)
b'kerberos': Base key derivation
Example (using public API):
from impacket.krb5.crypto import _AES256CTS
import struct
cipher = _AES256CTS()
# Derive encryption key for usage 11
constant = struct.pack('>IB', 11, 0xAA)
ke = cipher.derive(key, constant)
# Derive integrity key
constant = struct.pack('>IB', 11, 0x55)
ki = cipher.derive(key, constant)
Practical Examples
Generate Kerberos Keys
from impacket.krb5.crypto import string_to_key, Enctype
from binascii import hexlify
def generate_keys(password, username, domain):
"""
Generate Kerberos keys for user.
Args:
password: User password
username: User principal name
domain: Kerberos realm
Returns:
Dict of encryption type -> key hex
"""
# Construct salt
salt = f"{domain.upper()}{username}"
keys = {}
# AES256 (recommended)
key = string_to_key(Enctype.AES256, password, salt)
keys['AES256'] = hexlify(key.contents).decode()
# AES128
key = string_to_key(Enctype.AES128, password, salt)
keys['AES128'] = hexlify(key.contents).decode()
# RC4 (NT hash)
key = string_to_key(Enctype.RC4, password, '')
keys['RC4'] = hexlify(key.contents).decode()
return keys
# Usage
keys = generate_keys('P@ssw0rd', 'john', 'DOMAIN.LOCAL')
for enctype, key_hex in keys.items():
print(f"{enctype}: {key_hex}")
Encrypt/Decrypt Timestamp
from impacket.krb5.crypto import encrypt, decrypt, string_to_key, Enctype
from impacket.krb5.types import KerberosTime
from impacket.krb5.asn1 import PA_ENC_TS_ENC
from pyasn1.codec.der import encoder, decoder
import datetime
def create_encrypted_timestamp(password, salt):
"""
Create encrypted timestamp for AS-REQ.
"""
# Derive key
key = string_to_key(Enctype.AES256, password, salt)
# Build timestamp
pa_enc_ts = PA_ENC_TS_ENC()
now = datetime.datetime.now(datetime.timezone.utc)
pa_enc_ts['patimestamp'] = KerberosTime.to_asn1(now)
pa_enc_ts['pausec'] = now.microsecond
# Encode and encrypt
plaintext = encoder.encode(pa_enc_ts)
ciphertext = encrypt(key, 1, plaintext) # Key usage 1
return ciphertext, key
def decrypt_timestamp(ciphertext, key):
"""
Decrypt and parse timestamp.
"""
plaintext = decrypt(key, 1, ciphertext)
pa_enc_ts = decoder.decode(plaintext, asn1Spec=PA_ENC_TS_ENC())[0]
timestamp = KerberosTime.from_asn1(pa_enc_ts['patimestamp'])
microseconds = int(pa_enc_ts['pausec'])
return timestamp, microseconds
# Usage
ciphertext, key = create_encrypted_timestamp(
'password',
'DOMAIN.LOCALuser'
)
timestamp, usec = decrypt_timestamp(ciphertext, key)
print(f"Timestamp: {timestamp}.{usec}")
Decrypt AS-REP
from impacket.krb5.crypto import decrypt, string_to_key, InvalidChecksum
from impacket.krb5.asn1 import AS_REP, EncASRepPart
from pyasn1.codec.der import decoder
def decrypt_as_rep(as_rep_bytes, password, salt, enctype):
"""
Decrypt AS-REP and extract session key.
Args:
as_rep_bytes: Encoded AS-REP message
password: User password
salt: Kerberos salt
enctype: Encryption type from AS-REP
Returns:
Tuple of (session_key, enc_as_rep_part)
"""
# Decode AS-REP
as_rep = decoder.decode(as_rep_bytes, asn1Spec=AS_REP())[0]
# Derive key from password
key = string_to_key(enctype, password, salt)
# Extract and decrypt enc-part
ciphertext = as_rep['enc-part']['cipher']
try:
plaintext = decrypt(key, 3, ciphertext) # Key usage 3
except InvalidChecksum:
raise ValueError("Decryption failed - wrong password?")
# Decode encrypted part
enc_as_rep_part = decoder.decode(
plaintext,
asn1Spec=EncASRepPart()
)[0]
# Extract session key
session_key_type = int(enc_as_rep_part['key']['keytype'])
session_key_value = enc_as_rep_part['key']['keyvalue'].asOctets()
from impacket.krb5.crypto import Key
session_key = Key(session_key_type, session_key_value)
return session_key, enc_as_rep_part
# Usage
session_key, enc_part = decrypt_as_rep(
as_rep_bytes,
'password',
'DOMAIN.LOCALuser',
18 # AES256
)
print(f"Session key: {session_key.contents.hex()}")
print(f"Ticket expires: {enc_part['endtime']}")
Compute Authenticator Checksum
from impacket.krb5.crypto import make_checksum, Cksumtype
from impacket.krb5.gssapi import CheckSumField
def create_authenticator_checksum(session_key, channel_binding=None):
"""
Create checksum for AP-REQ authenticator.
"""
# Build checksum field
chk_field = CheckSumField()
chk_field['Lgth'] = 16
chk_field['Flags'] = 0x00004010 # GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG
if channel_binding:
chk_field['Bnd'] = channel_binding
data = chk_field.getData()
# Compute checksum
# Determine checksum type from key
if session_key.enctype == 18: # AES256
cksumtype = Cksumtype.SHA1_AES256
elif session_key.enctype == 17: # AES128
cksumtype = Cksumtype.SHA1_AES128
elif session_key.enctype == 23: # RC4
cksumtype = Cksumtype.HMAC_MD5
else:
raise ValueError(f"Unsupported enctype: {session_key.enctype}")
checksum = make_checksum(
cksumtype,
session_key,
11, # Key usage: AP-REQ authenticator
data
)
return checksum
Generate Keys from Hash
from impacket.krb5.crypto import Key, Enctype, generate_kerberos_keys
from binascii import unhexlify
def keys_from_credentials(username, domain, password=None, nthash=None, aeskey=None):
"""
Generate Kerberos keys from credentials.
Returns:
Dict of enctype -> Key object
"""
keys = {}
# RC4 key from NT hash
if nthash:
if isinstance(nthash, str):
nthash = unhexlify(nthash)
keys[Enctype.RC4] = Key(Enctype.RC4, nthash)
# AES key directly
if aeskey:
if isinstance(aeskey, str):
aeskey = unhexlify(aeskey)
if len(aeskey) == 32:
keys[Enctype.AES256] = Key(Enctype.AES256, aeskey)
elif len(aeskey) == 16:
keys[Enctype.AES128] = Key(Enctype.AES128, aeskey)
# Derive from password
if password:
from impacket.krb5.crypto import string_to_key
# Construct salt
if username.endswith('$'):
# Computer account
salt = f"{domain.upper()}host{username[:-1].lower()}.{domain.lower()}"
else:
# User account
salt = f"{domain.upper()}{username}"
# Generate AES keys
keys[Enctype.AES256] = string_to_key(Enctype.AES256, password, salt)
keys[Enctype.AES128] = string_to_key(Enctype.AES128, password, salt)
# RC4 from password
keys[Enctype.RC4] = string_to_key(Enctype.RC4, password, '')
return keys
# Usage
keys = keys_from_credentials(
username='john',
domain='DOMAIN.LOCAL',
password='P@ssw0rd'
)
for enctype, key in keys.items():
print(f"Enctype {enctype}: {key.contents.hex()}")
Security Considerations
Weak Encryption Types
Avoid deprecated algorithms:
# DO NOT USE
WEAK_ENCTYPES = [
Enctype.DES_CRC,
Enctype.DES_MD4,
Enctype.DES_MD5
]
# PREFERRED
STRONG_ENCTYPES = [
Enctype.AES256, # Best
Enctype.AES128, # Good
]
# ACCEPTABLE (but RC4 is deprecated)
ACCEPTABLE_ENCTYPES = [
Enctype.RC4, # Only if AES unavailable
Enctype.DES3 # Legacy compatibility
]
Key Storage
Protect cryptographic keys:
import os
from binascii import hexlify
# Securely wipe key material
def secure_delete(key_obj):
# Overwrite with zeros
key_length = len(key_obj.contents)
key_obj.contents = b'\x00' * key_length
# Use environment variables for sensitive data
aeskey_hex = os.environ.get('KERBEROS_AES_KEY')
if aeskey_hex:
aeskey = unhexlify(aeskey_hex)
key = Key(Enctype.AES256, aeskey)
Random Number Generation
Use cryptographically secure RNG:
from impacket.krb5.crypto import get_random_bytes
# Generate secure random bytes
confounder = get_random_bytes(16)
nonce = int.from_bytes(get_random_bytes(4), 'big')
Error Handling
InvalidChecksum Exception
from impacket.krb5.crypto import InvalidChecksum
try:
plaintext = decrypt(key, keyusage, ciphertext)
except InvalidChecksum as e:
# Possible causes:
# - Wrong key
# - Wrong key usage
# - Corrupted ciphertext
# - Wrong encryption type
print(f"Decryption failed: {e}")
ValueError Exceptions
try:
key = Key(Enctype.AES256, key_bytes)
except ValueError as e:
# Wrong key length
print(f"Invalid key: {e}")
try:
key = string_to_key(999, password, salt)
except ValueError as e:
# Invalid enctype
print(f"Unsupported encryption type: {e}")
Cipher Selection
Relative Performance (fastest to slowest):
- RC4 - Very fast but deprecated
- AES128 - Fast and secure
- AES256 - Secure, slightly slower
- DES3 - Slow, avoid
Key Derivation
# PBKDF2 iterations affect performance
import time
import struct
# Test different iteration counts
for iterations in [1000, 4096, 10000]:
params = struct.pack('>L', iterations)
start = time.time()
key = string_to_key(Enctype.AES256, 'password', 'salt', params)
elapsed = time.time() - start
print(f"{iterations} iterations: {elapsed:.3f}s")
# Output:
# 1000 iterations: 0.012s
# 4096 iterations: 0.048s (default)
# 10000 iterations: 0.118s
Caching Keys
Cache derived keys to avoid repeated computation:
class KeyCache:
def __init__(self):
self._cache = {}
def get_key(self, enctype, password, salt):
cache_key = (enctype, password, salt)
if cache_key not in self._cache:
self._cache[cache_key] = string_to_key(enctype, password, salt)
return self._cache[cache_key]
cache = KeyCache()
key = cache.get_key(Enctype.AES256, 'password', 'DOMAIN.LOCALuser')
See Also