[EN] A Deep Dive into KeyTabExtractor Techniques
By: Lucas Hoffmann
Revision: Thiago Bispo & Allan Kardec
Technical Introduction
Many modern corporations rely on Kerberos, a widely used network authentication protocol designed to validate identities and authorize access within corporate environments. Within this ecosystem, there are mechanisms that allow services to authenticate automatically, without human interaction, and one of the main ones is the use of keytab files. These files store keys or hashes derived from credentials and are used so that applications and services can transparently authenticate against the KDC (Key Distribution Center).
However, the convenience of keytabs comes with a risk: if an attacker gains access to these files, they can use them as true “substitute passwords†to impersonate valid identities within the domain. In this article, we present KeyTabExtractor, an enhanced fork of the well-known keytabextract (originally developed by sosdave and twosevenzero), capable of extracting keys or hashes directly from keytab files and converting them into reusable credentials. Enabling privileged access, persistence, and lateral movement in Active Directory-based environments.
What is a Keytab and How Does It Work
In many modern corporate environments, Linux systems and services rely on Kerberos for authentication, and .keytab files are used to allow those services to log in automatically without human intervention. These files store cryptographic keys derived from password hashes of users or service accounts, enabling applications to authenticate against the KDC (Key Distribution Center) without the need for a plaintext password.
From a technical perspective, a .keytab works as a vault of persistent credentials, and each entry contains:
| Header | File format version (501 or 502). |
| Principal | User + Realm |
| Timestamp | When the key was generated. |
| KVNO (Key Version Number) | Key version counter. |
| Encryption Type | rc4-hmac, aes256-cts-hmac-sha1-96, DES, etc |
| Key | Hash derived from the password + Principal. |
The analysis of keytab binary files is essential for understanding their structure. To achieve this, we used xxd, an open-source tool available on most systems, which allows you to visualize bytes in hexadecimal format (hex dump).

To facilitate the interpretation of the .keytab file, we highlighted its main components with different colors in the hexadecimal dump. This clear visual approach allows for easy identification of the positions and content of each entry (such as the header, principal name, realm, and encryption key data) within the file’s binary structure.
- Blue = Header (502)
- Pink =Â Entry size (59 bytes)
- Beige = Principal (SQUIRREL.HC + administrator)
- Cyan = Timestamp (null in this case)
- Brown = KVNO (1)
- Green = Encryption Type (RC4-HMAC)
- Red = Key Length (2 bytes)
- Yellow = Key (in this case [NTLM HASH]: 9cf00c7264c00d1c69fed27f3fce4cce)
The header version defines how the file is interpreted. Understanding these disparities is crucial when analyzing .keytab dumps:
| Feature | Keytab 501 (v5.1) | Keytab 502 (v5.2) |
| Age | Older / Legacy | Newer / Standard |
| Metadata | Simple structure | Richer structure |
| Compatibility | Legacy UNIX enviroment | Active Directory and Modern Linux |
| Common Use | Legacy services, SAMBA | Default in kadmin, ktutil, msktutil |
| Risk for attacker | Can store NTLM (RC4-HMAC) or AES the same way | Identical risk, but with more context (SPN, detailed Realm) |
The diagram below illustrates the basic Kerberos authentication flow, highlighting where the keytab comes into play. When a service needs to authenticate automatically, the keytab provides the credentials that allow it to transparently communicate with the KDC (Key Distribution Center) without any human intervention.
This “behind the scenes†mechanism explains how services can run unattended while still securely authenticating to the domain.
Diagram – Source: RedHat
The main benefit of using keytabs is automation: services can start up on their own and authenticate to the domain without administrator intervention. The problem is that, like any other artifact that stores credentials, .keytab files become a valuable target for attackers.
Keytab files are not encrypted. They are simply binary files, structured as a header plus sequential entries, containing keys or hashes in plain form (without additional encryption). Once obtained, they can be abused in attacks that resemble well-known techniques in the Windows world, such as Pass-the-Hash (PtH) and Pass-the-Key (PtK).
- AES-derived keys: If the keytab contains AES-derived keys, an attacker can reuse these keys directly in a Pass-the-Key attack, authenticating to Kerberos without ever needing the plaintext password.
- RC4-HMAC keys: When the keytab stores RC4-HMAC keys, the stored value is essentially the account’s NTLM hash. This means the attacker can not only use it for Pass-the-Key, but also leverage it in classic Pass-the-Hash attacks.
In other words, a keytab becomes a convergence point between two well-known attack vectors: direct key reuse (PtK) and hash reuse (PtH). This dual exposure significantly expands the attack surface, making the protection of keytab files critical in any Kerberos-based environment.
How KeyTabExtract Works
A keytab is not a “flat†file-it is composed of a sequence of entries, each one containing information about a principal (user/service) plus its corresponding hash, as explained earlier. Anyone with access to a keytab file can export the credentials stored inside it.
To demonstrate this, users sosdave and twosevenzero created a tool called KeyTabExtract, designed to dump the users and secrets from this credential vault.
Its operation works as follows: KeyTabExtract first reads the entire binary contents of the keytab file and converts it into hexadecimal, treating it as a string and then decoding it into UTF-8.
# Take argument 1 as keytab file, import and decode the hex
ktfile = sys.argv[1]
f = open(ktfile, 'rb').read()
hex_encoded = binascii.hexlify(f).decode('utf-8')
Here, the tool does not treat the keytab as “multiple records,†but rather as a continuous string. This is already an indication that there is no logic for iterating over multiple entries.
Fixed parsing of the first block
# Calculates the realm component (HTTP)
comp_array_offset = comp_array_calc + (comp_array * 2)
comp_array2 = hex_encoded[comp_array_calc:comp_array_offset]
# calculate number of bytes for the principal
principal_array_offset = comp_array_offset + 4
# extract the principal
principal_array = hex_encoded[comp_array_offset:principal_array_offset]
principal_array_int = (int(principal_array, 16) * 2)
prin_array_start = principal_array_offset
prin_array_finish = prin_array_start + principal_array_int
principal_array_value = hex_encoded[prin_array_start:prin_array_finish]
print("\tSERVICE PRINCIPAL : " + bytes.fromhex(comp_array2).decode('utf-8') + "/" + bytes.fromhex(principal_array_value).decode('utf-8'))
The keytab starts with: 0x0502 → the file header supported by the tool. Next comes an integer representing the size of the first entry.
The correct approach would be: read the size, process that entry’s block, move the pointer forward, and then process the next one repeating this until reaching EOF. → However, the script ignores this and only extracts the bytes related to the first entry.
Hash extraction
# Extract Key Value
key_val_start = key_val_offset
key_val_finish = key_val_start + (key_val_len * 2)
key_val = hex_encoded[key_val_start:key_val_finish]
if rc4hmac == True:
NTLMHash = hex_encoded.split("00170010")[1]
print("\tNTLM HASH : " + NTLMHash[:32])
if aes256 == True:
aes256hash = hex_encoded.split("00120020")[1]
print("\tAES-256 HASH : " + aes256hash[:64])
if aes128 == True:
aes128hash = hex_encoded.split("00110010")[1]
print("\tAES-128 HASH : " + aes128hash[:32])
if __name__ == "__main__":
if len(sys.argv) == 1:
displayhelp()
sys.exit()
else:
ktextract()
The use of .split(…)[1] on lines 111, 115, and 119 causes only the first occurrence of the cryptographic algorithm identifier in the file (00170010 for RC4-HMAC, 00120020 for AES-256, and 00110010 for AES-128) to be considered.
As a result, even if the keytab contains multiple users or multiple keys whether of the same encryption type or different ones the code will always extract only the first match, ignoring all subsequent entries.
KeyTabExtract Limitations
The limitations of KeyTabExtract were the main motivations for this research and for the development of KeyTabExtractor. These limitations include:
- Lack of compatibility to extract hashes from keytab files with a 501 header.
- Inability to extract hashes from multiple users within a single keytab file.
- Absence of flags to export output in a format compatible with hashcat/john.
Tool Improvements – The KeyTabExtractor
As corporate environments grew and became more complex, the need arose to extract multiple hashes from different users within a single .keytab file in order to fully explore system security. This gap led to the creation of KeyTabExtractor, available at https://github.com/hakaioffsec/keytabextractor/.
With this tool, it is not only possible to handle 502-type files, but also 501-type files, ensuring compatibility with cracking tools in cases where the keytab contains AES/DES-based hashes (not just NTLM).
def parse_keytab_entries(data_bytes):
"""Main parser that detects version and delegates to appropriate parser"""
if len(data_bytes) < 2:
return []
version = struct.unpack(">H", data_bytes[0:2])[0]
if version == 0x0502:
return parse_keytab_0x0502(data_bytes)
elif version == 0x0501:
return parse_keytab_0x0501(data_bytes)
else:
print(f"[!] Warning: Unknown keytab version 0x{version:04x}, attempting 0x0502 format...")
return parse_keytab_0x0502(data_bytes)
The parse_keytab_entries function, starting at line 173, is responsible for identifying the keytab file header version and directing the processing to the corresponding parser.
def parse_keytab_0x0501(data_bytes):
"""Parse keytab version 0x0501 (without size fields)"""
entries = []
offset = 2 # Skip version bytes
while offset < len(data_bytes):
# Try to parse an entry starting at current offset
remaining = data_bytes[offset:]
if len(remaining) < 10: # Minimum viable entry size
break
parsed = parse_single_entry(remaining)
if parsed:
entries.append(parsed)
offset += parsed["entry_size"]
else:
# If parsing fails, try to skip forward and find next valid entry
offset += 1
# Look for potential start of next entry
found_next = False
for skip_offset in range(offset, min(offset + 100, len(data_bytes) - 10)):
test_parsed = parse_single_entry(data_bytes[skip_offset:])
if test_parsed:
offset = skip_offset
found_next = True
break
if not found_next:
break
return entries
def parse_keytab_entries(data_bytes):
"""Main parser that detects version and delegates to appropriate parser"""
if len(data_bytes) < 2:
return []
version = struct.unpack(">H", data_bytes[0:2])[0]
if version == 0x0502:
return parse_keytab_0x0502(data_bytes)
elif version == 0x0501:
return parse_keytab_0x0501(data_bytes)
else:
print(f"[!] Warning: Unknown keytab version 0x{version:04x}, attempting 0x0502 format...")
return parse_keytab_0x0502(data_bytes)
The parser_keytab_0x0501 function, implemented to ensure compatibility for dumping and starting at line 140, processes keytabs with a 0x0501 header, which do not contain size fields for each entry. It sequentially reads the data, extracting key/hash entries with parse_single_entry and, in case of failure, progressively advances until it locates the next possible entry start. This approach ensures the recovery of all keys present in the file, preventing the loss of results during the dump.
It is important to note that the same logic also applies to the parse_keytab_0x0502 function. Below is the relevant code snippet.
def parse_keytab_0x0502(data_bytes):
"""Parse keytab version 0x0502 (with size fields)"""
entries = []
offset = 2 # Skip version bytes
while offset + 4 <= len(data_bytes):
# Read entry size (4 bytes, big-endian)
entry_size = struct.unpack(">I", data_bytes[offset:offset+4])[0]
offset += 4
# Check for valid entry size
if entry_size == 0 or entry_size > len(data_bytes) - offset:
break
# Extract entry data
entry_data = data_bytes[offset:offset+entry_size]
offset += entry_size
# Parse the entry
parsed = parse_single_entry(entry_data)
if parsed:
entries.append(parsed)
return entries
To address the last requirement regarding the export of dumped hashes from keytab files, the save_hashes_by_type function was developed. It saves the extracted keys into separate files according to their algorithm type (NTLM, AES128, AES256, DES, etc.), generating output filenames with the corresponding suffixes and applying the specified format (hashcat or john).
At the end, it returns the list of created files along with the number of entries in each.
def save_hashes_by_type(base_output, format_type, hash_collections):
"""Save different hash types to separate files"""
saved_files = []
for hash_type, entries in hash_collections.items():
if not entries:
continue
# Create filename with hash type suffix
base_path = Path(base_output)
stem = base_path.stem
suffix = base_path.suffix if base_path.suffix else ".hashes"
output_file = base_path.parent / f"{stem}-{hash_type}{suffix}"
lines = []
for entry in entries:
if format_type == "hashcat":
if hash_type == "ntlm":
lines.append(entry['hexkey'])
else:
lines.append(f"{entry['principal']}@{entry['realm']}:{entry['etype']}:{entry['hexkey']}")
else: # john format
if hash_type == "ntlm":
lines.append(f"$NT${entry['hexkey']}")
else:
lines.append(f"{entry['principal']}@{entry['realm']}:{entry['etype']}:{entry['hexkey']}")
if lines:
output_file.write_text("\n".join(lines) + "\n")
saved_files.append((hash_type, str(output_file), len(lines)))
return saved_files
In summary, this version of KeyTabExtractor turns a simple .keytab dump into a source of reusable credentials, making it easier to analyze and extract hashes for lateral movement, persistence, and privilege escalation in Active Directory domains without losing compatibility with the tools you already use in your workflow. The project was designed to be simple yet efficient.
It allows you to choose the output format either hashcat or John the Ripper ensuring that the results can be easily integrated into security testing, audits, and Red Team exercises.
Using KeyTabExtractor is relatively straightforward. Below is a PoC demonstrating how to run the tool together with export and format flags to ensure compatibility with common hash-cracking tools:
./keytabextractor.py [file.keytab] -o output.txt -f [hashcat/john]

Usage – Keytabextractor
Mitigations practices
To reduce the likelihood and impact of incidents related to keytab usage, the following mitigation measures are recommended:
- Keytabs should be stored in restricted directories, with access permissions configured to allow read access exclusively to the process or service account that requires it. This practice minimizes the local attack surface. In modern environments, it is preferable to use secrets vault solutions such as HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault in order to avoid keeping keytabs directly on the filesystem.
- Keytab rotation should follow the corporate password policy, ensuring that keys are replaced at defined intervals. In the event of compromise or suspicion, revocation must be immediate by resetting the linked Kerberos account password and generating a new keytab. It is also recommended that keytabs always be associated with dedicated, least-privileged service accounts, reducing the potential impact in case of exploitation.
- Keytabs should only be used at the moment of authentication, avoiding long-lived tickets in memory and reducing credential exposure. Sensitive information derived from keytabs must not be exposed in logs, temporary files, plaintext backups, or monitoring systems.
The combined application of these measures contributes to a safer use of keytabs in Kerberos environments, balancing the need for automation with corporate security requirements, while mitigating the risks of credential leakage or misuse.
Reference: Uber’s implementation of keytab rotation techniques.
References
- [Using Kerberos – Red Hat Documentation]
https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/system-level_authentication_guide/using_kerberos - [Creating a Kerberos Configuration File – IBM Docs]
https://www.ibm.com/docs/pt-br/was/9.0.5?topic=server-creating-kerberos-configuration-file - [Keytab Definition – MIT Kerberos Documentation]
https://web.mit.edu/kerberos/krb5-devel/doc/basic/keytab_def.html - [Kerberos V5 System Administrator’s Guide – MIT]
https://web.mit.edu/kerberos/krb5-1.9/krb5-1.9.3/doc/krb5-admin.html - [Kerberos Protocol Tutorial – EventHelix]
https://www.eventhelix.com/networking/kerberos - [The Keytab File – MIT Kerberos Installation Guide]
https://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.4/doc/krb5-install/The-Keytab-File.html
