> For the complete documentation index, see [llms.txt](https://www.impacket.wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.impacket.wiki/tutorials/authentication-in-impacket.md).

# Authentication in Impacket with NTLM, Hashes, and Kerberos

## What you'll learn

In [Establishing Your First SMB Session](/tutorials/your-first-smb-session.md), you authenticated via the SMB protocol to a remote machine with a single line:

```python
conn.login(username, password, domain)
```

It worked, but what actually traveled over the wire? What did Impacket do with your password before sending it? And what if you don't *have* a password and instead just a hash, or a Kerberos ticket?

By the end of this tutorial you will understand:

* What happens inside Impacket when you call `login()`, with regards to the NTLM three-way handshake
* How Impacket represents credentials internally, via NT hashes and LM hashes
* How to authenticate with a hash instead of a password (pass-the-hash)
* How to request and use Kerberos tickets through Impacket's `CCache`, `getTGT`, and `getST`
* How to authenticate any connection with Kerberos using `kerberosLogin()`
* How to swap authentication methods on connections you already know

Note that we won't be using anything different then Tutorial 1s `SMBConnection` you built in Tutorial 1. The protocol is still familiar to you but only the authentication layer and process will now change.

## Prerequisites

Everything from [Tutorial 1](https://github.com/ThatTotallyRealMyth/Impacket-Documentation/blob/main/tutorials/establishing-your-first-smb-session.md), plus:

**A domain controller (for the Kerberos sections).** The NTLM sections work against any Windows target, but Kerberos requires Active Directory. If you have a lab domain, use it. If not, you can still complete Steps 1–5 against a standalone Windows machine and return to the Kerberos sections later.

| Placeholder  | Meaning                           | Example         |
| ------------ | --------------------------------- | --------------- |
| `TARGET_IP`  | IP of the target machine          | `192.168.56.10` |
| `DC_IP`      | IP of the domain controller       | `192.168.56.5`  |
| `DOMAIN`     | Active Directory domain (FQDN)    | `lab.local`     |
| `USERNAME`   | A valid domain or local account   | `labuser`       |
| `PASSWORD`   | The password for that account     | `Password123!`  |
| `SHARE_NAME` | A writeable share from Tutorial 1 | `SharedFiles`   |

**DNS resolution for the domain controller.** Kerberos requires that the target hostname resolves correctly. To be able to resolve against the KDC, add an entry to `/etc/hosts`:

```bash
echo "192.168.56.5  dc01.lab.local lab.local" | sudo tee -a /etc/hosts
```

Additionally, you must update/sync your time with the target domain controller otherwise you will get an error when using the tickets as kerberos requires clocks on clients and servers be synced:

```bash
sudo ntpdate -u $DC_IP
```

Create a new scratch file for this tutorial:

```bash
touch auth_basics.py
```

## Step 1 Revisit what `login()` actually does

Open `auth_basics.py` and start with the same connection from Tutorial 1:

```python
from impacket.smbconnection import SMBConnection

target_ip = "192.168.56.10"
username  = "labuser"
password  = "Password123!"
domain    = "."

conn = SMBConnection(target_ip, target_ip)
conn.login(username, password, domain)
print(f"[+] Authenticated as {domain}\\{username}")

conn.logoff()
conn.close()
```

Now look at what `login()` accepted. Open a Python shell and inspect the signature:

```python
from impacket.smbconnection import SMBConnection
help(SMBConnection.login)
```

You'll see parameters beyond the three you've been using:

<figure><img src="/files/lE2J5LKjaPJ9AavQERBn" alt=""><figcaption></figcaption></figure>

Two of those, `lmhash` and `nthash`, are empty strings by default. When you pass a plaintext password, Impacket computes these values for you internally. When you pass them directly, there is no longer a need for a password.

## Step 2 Understand NT hashes and how Impacket computes them

Before you can authenticate with a hash, you need to understand what one is and where it comes from.

An NT hash is the MD4 digest of a password encoded as UTF-16LE. The hash has no salt, no iterations, and so no real/added complexity. Impacket gives you the function to compute it directly.

Add this to `auth_basics.py`:

```python
from impacket.smbconnection import SMBConnection
from impacket.ntlm import compute_nthash

password = "Password123!"

# Compute the NT hash from the plaintext password
nt_hash = compute_nthash(password)
print(f"[*] Password:  {password}")
print(f"[*] NT hash:   {nt_hash.hex()}")
```

Run it:

```bash
python3 auth_basics.py
```

You'll see output like:

```
[*] Password:  Password123!
[*] NT hash:   2b576acbe6bcfda7294d6bd18041b8fe
```

That hex string is the NT hash. This is the value Windows stores in the SAM database and in Active Directory's `ntds.dit`. When you call `conn.login(user, password, domain)`, Impacket calls this same function internally, computes the hash, and uses it in the NTLM exchange. The plaintext password never goes over the wire as instead the ntlm hash does.

> **You may have noticed LM hashes above:** Which are a legacy format from pre-Windows NT systems. Modern Windows versions disable LM hash storage by default. In practice you will almost always see them represented as `aad3b435b51404eeaad3b435b51404ee` (the hash of an empty string) or left blank. Impacket accepts them for compatibility, but you can ignore them in modern environments.

## Step 3 Understand the NTLM exchange inside Impacket

When `login()` runs with a password (or a hash), Impacket performs an NTLM three-message handshake. You don't need to implement this yourself, but knowing the shape of it will help you debug authentication failures and understand what you see in packet captures.

Impacket exposes the NTLM primitives in `impacket.ntlm`. Explore the messages directly:

```python
from impacket.ntlm import getNTLMSSPType1, getNTLMSSPType3

#Message 1 Negotiate
# The client tells the server what capabilities it supports.
negotiate_msg = getNTLMSSPType1()
print(f"[*] Type 1 (Negotiate) length: {len(negotiate_msg)} bytes")
print(f"    First 8 bytes: {negotiate_msg[:8]}")
```

Run this. You'll see the negotiate message is a short byte sequence starting with `NTLMSSP\x00`. This is the first message Impacket sends inside the SMB Session Setup request.

The full exchange works like this:

1. **Type 1 (Negotiate):** Impacket sends this to the server, advertising supported NTLM features.
2. **Type 2 (Challenge):** The server responds with a random 8-byte challenge.
3. **Type 3 (Authenticate):** Impacket uses your NT hash (computed from the password, or provided directly) to encrypt the challenge, proving knowledge of the credential without sending it in the clear.

You don't need to call these functions manually since thankfully`login()` handles the full sequence for you. The point of this step is to see that these are ordinary Python objects you can inspect, and that I would highly encourge you to explore/inspect by stepping through in a debugger.

> **Where do NetNTLMv2 hashes come from?** The Type 3 message contains the challenge-response. When you capture this response (for example with Responder), you get a NetNTLMv2 hash, which is the challenge-response value, not the NT hash itself. This is why a captured NetNTLMv2 hash needs to be cracked to recover the NT hash, while an NT hash can be used directly in pass the hash.

## Step 4 Authenticate with a hash instead of a password

Now put this together. You have an NT hash from Step 2. Use it to log in without the plaintext password:

```python
from impacket.smbconnection import SMBConnection

target_ip = "192.168.56.10"
username  = "labuser"
domain    = "."

# The NT hash you computed in Step 2, Note to paste your own value here
nt_hash_hex = "2b576acbe6bcfda7294d6bd18041b8fe"

# LM hash can be left empty or set to the "blank" value
lm_hash_hex = ""

conn = SMBConnection(target_ip, target_ip)

# Pass empty string for the password, provide hashes instead
conn.login(username, '', domain, lmhash=lm_hash_hex, nthash=nt_hash_hex)
print(f"[+] Authenticated with NT hash (no password used)")

# Prove the session work and so you can list shares, just like Tutorial 1
shares = conn.listShares()
print(f"[+] Accessible shares: {len(shares)}")
for share in shares:
    print(f"    {share['shi1_netname'][:-1]}")

conn.logoff()
conn.close()
```

Run it. You should see the same share listing as Tutorial 1, but this time you never gave Impacket the plaintext password. The `login()` call skipped the `compute_nthash()` step because you supplied the hash directly.

### Try it yourself

Go back to the file upload you did in Tutorial 1. Replace the `login()` call with hash-based authentication and upload a file. Everything works exactly the same because the `SMBConnection` session is identical regardless of how you authenticated.

## Step 5 Build a flexible credential object

Real Impacket code frequently needs to decide between password and hash authentication at runtime. Rather than scattering `if/else` blocks everywhere, establish a pattern now that you'll reuse in every future tutorial.

```python
from impacket.smbconnection import SMBConnection
from impacket.ntlm import compute_nthash


def smb_login(target, username, domain, password = '', nthash= ''):
    """
    Authenticate to a target over SMB.

    Accepts either a plaintext password or an NT hash.
    If both are provided, the hash takes priority (matching Impacket's behavior).
    """
    lmhash = ''

    if nthash:
        print(f"[*] Authenticating with NT hash")
    elif password:
        print(f"[*] Authenticating with password")
        # You could pre-compute the hash here if you wanted to store it,
        # but login() will handle it internally. We pass the password through.
    else:
        raise ValueError("Either password or nthash must be provided")

    conn = SMBConnection(target, target)
    conn.login(username, password, domain, lmhash=lmhash, nthash=nthash)
    print(f"[+] Session established as {domain}\\{username}")
    return conn
```

Now proceed to test it both ways:

```python
# With password
conn = smb_login("192.168.56.10", "labuser", ".", password="Password123!")
conn.logoff()
conn.close()

# With hash
conn = smb_login("192.168.56.10", "labuser", ".", nthash="2b576acbe6bcfda7294d6bd18041b8fe")
conn.logoff()
conn.close()
```

Both calls produce the same authenticated session. This pattern of a login helper that accepts either credential form is how most Impacket scripts handle authentication internally.

***

## Step 6 Request a Kerberos TGT

Everything up to this point used NTLM. Now switch to Kerberos, which takes a completely different path through both the network and the Impacket library.

**From here forward, you need a domain controller and a domain-joined target.**

Kerberos authentication starts by requesting a Ticket-Granting Ticket (TGT) from the domain controller's KDC (Key Distribution Center). Impacket handles this through `impacket.krb5`:

```python
from impacket.krb5.kerberosv5 import getKerberosTGT
from impacket.krb5.types import Principal, KerberosTime
from impacket.krb5 import constants

# Connection details. Note to use your domain values
domain    = "lab.local"
dc_ip     = "192.168.56.5"
username  = "labuser"
password  = "Password123!"

# Build the principal name (the "who" in the ticket request)
client_name = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)

# Request a TGT from the KDC
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(
    clientName=client_name,
    password=password,
    domain=domain,
    lmhash='',
    nthash='',
    kdcHost=dc_ip,
)

print(f"[+] Got TGT for {username}@{domain}")
print(f"    Cipher type:  {cipher.enctype}")
print(f"    TGT size:     {len(tgt)} bytes")
```

Now run this. If it succeeds, you now hold a TGT in memory, which is a blob of bytes that proves to the KDC that you've already authenticated.

### What happened

`getKerberosTGT` sent an `AS-REQ` (Authentication Service Request) to the KDC on port 88. The KDC verified the credentials, and returned an `AS-REP` containing the TGT. Impacket parsed the response and gave you back four things:

* `tgt` — the ticket itself (an opaque blob encrypted with the KDC's key; you can't read its contents, but you can present it)
* `cipher` — the encryption type used
* `oldSessionKey` — the session key from the response (used for subsequent requests)
* `sessionKey` — the session key Impacket will use going forward

You'll use `tgt` and `sessionKey` in the next step to request a service ticket.

***

## Step 7 Request a service ticket and inspect it

A TGT alone doesn't grant access to anything. To access a specific service (like SMB on a file server), you need a Service Ticket (TGS). The KDC issues one when you present your TGT:

```python
from impacket.krb5.kerberosv5 import getKerberosTGS
from impacket.krb5.types import Principal
from impacket.krb5 import constants

# The service you want a ticket for, which for us is SMB on the target machine
# SPN format: service/hostname@REALM
target_host = "dc01.lab.local"
spn = f"cifs/{target_host}"

server_name = Principal(spn, type=constants.PrincipalNameType.NT_SRV_INST.value)

# Request a TGS using the TGT from Step 6
tgs, cipher, oldSessionKey2, sessionKey2 = getKerberosTGS(
    serverName=server_name,
    domain=domain,
    kdcHost=dc_ip,
    tgt=tgt,
    cipher=cipher,
    sessionKey=sessionKey,
)

print(f"[+] Got TGS for {spn}")
print(f"    Cipher type:  {cipher.enctype}")
print(f"    TGS size:     {len(tgs)} bytes")
```

This sent a `TGS-REQ` to the KDC, presenting your TGT and asking for a ticket to the `cifs/dc01.lab.local` service. The KDC returned a `TGS-REP` containing the service ticket.

> **Why `cifs/`?** SMB file access uses the `cifs` service principal name. Different services use different prefixes: `ldap/` for LDAP, `MSSQLSvc/` for SQL Server, `HTTP/` for web services. The SPN tells the KDC which service's key to encrypt the ticket with.

***

## Step 8 Save and load tickets with CCache

So far your tickets exist only in Python variables. If the script exits, they're gone. Impacket can save tickets to a credential cache file (`.ccache`), which is also the same format used by MIT Kerberos and most Linux tools:

```python
from impacket.krb5.ccache import CCache

# Save the TGT to a ccache file
ccache = CCache()
ccache.fromTGT(tgt, oldSessionKey, sessionKey)
ccache.saveFile('labuser.ccache')
print(f"[+] TGT saved to labuser.ccache")
```

Now verify you can load it back:

```python
# Load the ccache from disk
loaded_ccache = CCache.loadFile('labuser.ccache')

# Extract the TGT credential
print(f"[+] Loaded ccache with {len(loaded_ccache.credentials)} credential(s)")
```

This file is portable and so you can pass it to other Impacket scripts using the `KRB5CCNAME` environment variable:

```bash
export KRB5CCNAME=labuser.ccache
```

Many Impacket modules check this variable automatically when Kerberos authentication is requested. You'll see how in the next step.

***

## Step 9 Authenticate an SMB session with Kerberos

Now close the loop by taking the Kerberos ticket and use it to authenticate the same `SMBConnection` from Tutorial 1. This is where NTLM and Kerberos converge back to the same exposed interface, or API if you will:

```python
from impacket.smbconnection import SMBConnection
from impacket.krb5.ccache import CCache

target_host = "dc01.lab.local"  # must be a hostname, not an IP
domain      = "lab.local"
dc_ip       = "192.168.56.5"

# Load the ccache you saved in Step 8
ccache = CCache.loadFile('labuser.ccache')

# Get the TGT from the ccache
tgt = ccache.credentials[0].toTGT()

#Now we connect to the target
conn = SMBConnection(target_host, dc_ip)

# Use kerberosLogin instead of login
conn.kerberosLogin(
    user='labuser',
    password='',
    domain=domain,
    kdcHost=dc_ip,
    TGT=tgt['KDC_REP'],
    useCache=False,
)
print(f"[+] Kerberos authentication successful")

# From here, the session behaves identically to an NTLM session.
# Everything you did in Tutorial 1 works exactly the same.
shares = conn.listShares()
print(f"[+] Accessible shares: {len(shares)}")
for share in shares:
    print(f"    {share['shi1_netname'][:-1]}")

conn.logoff()
conn.close()
```

The share listing is identical to what you saw in Tutorial 1 and in Step 4 of this tutorial. The server granted you a session. But the authentication path was completely different, as there was no NTLM messages, no NT hash computation, no challenge-response. Instead, Impacket presented the Kerberos service ticket, and the server validated it against its own.

You may also choose to see this in Wireshark, by opening it and going to the interface you are connected to currently(which is usutally eth0 or tun0 if you are on a VPN). You will see that depending on which authentication method used, you will either see the Kerberos V5 ASREQ-ASREQ-TGSREQ-TGSREP or the NTLMSSP messages type 1/2/3 exchange.

### The simpler path `useCache=True`

If the `KRB5CCNAME` environment variable points to your ccache file, you can skip the manual loading entirely:

```bash
export KRB5CCNAME=labuser.ccache
```

```python
conn = SMBConnection(target_host, dc_ip)
conn.kerberosLogin(
    user='labuser',
    password='',
    domain=domain,
    kdcHost=dc_ip,
    useCache=True,      # Impacket reads KRB5CCNAME automatically
)
```

This is how most Impacket scripts consume Kerberos tickets in practice. Otherwise note that you can also elect to pass kerberos AES keys but these are a bit advanced.

## Step 10 Compare the two paths side by side

You now know two authentication methods. Solidify the distinction by running them back to back against the same target and verifying they produce equivalent sessions:

```python
from impacket.smbconnection import SMBConnection
from impacket.krb5.ccache import CCache


def list_shares(conn: SMBConnection) -> list[str]:
    """Return share names from an authenticated connection."""
    return [s['shi1_netname'][:-1] for s in conn.listShares()]


# NTLM path 
conn_ntlm = SMBConnection("192.168.56.5", "192.168.56.5")
conn_ntlm.login("labuser", "Password123!", "lab.local")
ntlm_shares = list_shares(conn_ntlm)
conn_ntlm.logoff()
conn_ntlm.close()
print(f"[NTLM]     Shares: {ntlm_shares}")

# Kerberos path
ccache = CCache.loadFile('labuser.ccache')
tgt = ccache.credentials[0].toTGT()

conn_krb = SMBConnection("dc01.lab.local", "192.168.56.5")
conn_krb.kerberosLogin(
    user='labuser',
    password='',
    domain='lab.local',
    kdcHost='192.168.56.5',
    TGT=tgt['KDC_REP'],
    useCache=False,
)
krb_shares = list_shares(conn_krb)
conn_krb.logoff()
conn_krb.close()
print(f"[Kerberos] Shares: {krb_shares}")
```

From running above, you should see that regardless of the authentication method; the actions of the SMB connection are identical. Note that **after authentication, the SMBConnection behaves identically regardless of the method used.** `listShares()`, `listPath()`, `putFile()`, `getFile()` and every operation you learned in Tutorial 1 works the same way. This holds true for the whole library!

***

## Review what you learned

This tutorial unpacked the `login()` call from Tutorial 1 and taught you the authentication layer of Impacket:

1. **`login(user, password, domain, lmhash, nthash)`** : authenticates with NTLM using either a password or pre-computed hashes. The password is never sent over the wire; it's converted to an NT hash first.
2. **`compute_nthash(password):`** computes the MD4 digest that Windows uses to store and verify passwords.
3. **`getNTLMSSPType1()` and the NTLM message types:** the three-message handshake (Negotiate, Challenge, Authenticate) that Impacket performs inside `login()`.
4. **Pass-the-hash:** passing `nthash` directly to `login()`, skipping the password entirely.
5. **`getKerberosTGT():`** requests a TGT from the KDC using password or hash credentials.
6. **`getKerberosTGS():`** requests a service ticket by presenting a TGT.
7. **`CCache:`** saves and loads Kerberos tickets to `.ccache` files, integrating with the `KRB5CCNAME` environment variable.
8. **`kerberosLogin():`** authenticates an `SMBConnection` (or any Impacket connection) using Kerberos instead of NTLM.

An important takeaway from this tutoriail is that Impacket separates *authentication* from *protocol operations*. Once you have an authenticated session, however you got it, the rest of the API is the same. This pattern holds across every Impacket module, and so you will always have the option of using kerberos, password, nt hash or soon; certificates.

***

## Where to go next

* **Tutorial 1** used `login()` with a password. Go back and redo the file upload/download using pass-the-hash and Kerberos authentication to prove to yourself that the operations are truly interchangeable.
* The DCE/RPC tutorial will show you how `login()` and `kerberosLogin()` apply to RPC transports, not just SMB.
* The LDAP tutorial will introduce a third connection type (`LDAPConnection`) that follows the same `bind with password` / `bind with Kerberos` split you learned here.

## Related Topics

* [SMB Protocol](https://github.com/ThatTotallyRealMyth/Impacket-Documentation/blob/main/file-sharing/smb.md) - SMB with Kerberos auth
* [LDAP Protocol](https://github.com/ThatTotallyRealMyth/Impacket-Documentation/blob/main/tutorials/ldap.md) - LDAP with Kerberos auth
* [MS-RPC Protocol](https://github.com/ThatTotallyRealMyth/Impacket-Documentation/blob/main/rpc-and-execution/msrpc.md) - RPC with Kerberos

## References

* **Source**: `impacket/krb5/`
* **RFC 4120**: Kerberos V5 Protocol
* **\[MS-KILE]**: Kerberos Protocol Extensions
* **\[MS-PAC]**: Privilege Attribute Certificate


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://www.impacket.wiki/tutorials/authentication-in-impacket.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
