How to Resolve BadHostKeyException: Host Key Changes and Man-in-the-Middle Attacks in Paramiko

Paramiko raises BadHostKeyException when the SSH server’s host key does not match the entry in your ~/.ssh/known_hosts. This is often due to legitimate key rotation or a potential man-in-the-middle (MITM) attack. Follow these steps to securely diagnose and fix mismatched host keys.

1. Understand Host Key Verification

SSH uses host keys to authenticate servers. On first connection, the server’s public key is stored in known_hosts. Subsequent mismatches trigger BadHostKeyException to prevent MITM attacks.

See also  How to Debug AuthenticationException: Common Causes and Solutions in Paramiko

2. Catch and Inspect the Exception

import paramiko
from paramiko import BadHostKeyException

ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.RejectPolicy())

try:
    ssh.connect('example.com', username='user')
except BadHostKeyException as e:
    print("Host key mismatch detected!")
    print("Expected:", e.expected_key.get_base64())
    print("Received:", e.hostkey.get_base64())
    raise
    

3. Verify Legitimate Key Rotation

  1. Obtain the new host key fingerprint from the server administrator via a secure channel (e.g., email, console, notarized file).
  2. Compare the fingerprint with ssh-keyscan output:
    ssh-keyscan example.com | ssh-keygen -lf -
          
Note: Never trust an unsolicited key change without verification.

4. Update known_hosts Safely

  1. Backup your known_hosts file:
    cp ~/.ssh/known_hosts ~/.ssh/known_hosts.bak
  2. Remove the old entry:
    ssh-keygen -R example.com
  3. Add the new key:
    ssh-keyscan example.com >> ~/.ssh/known_hosts

5. Automate Key Replacement in Paramiko

For controlled environments, automate removal and addition:

import os
import paramiko

host = 'example.com'
known_hosts = os.path.expanduser('~/.ssh/known_hosts')

# Remove old key
os.system(f'ssh-keygen -R {host}')

# Append new key
os.system(f'ssh-keyscan {host} >> {known_hosts}')

# Proceed with connection
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.connect(host, username='user')
    

Tip: Restrict automation to trusted infrastructure to avoid MITM risk.

6. Use Custom HostKeyPolicy for Warnings

Display warning instead of rejecting:

from paramiko import SSHClient, WarningPolicy

ssh = SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(WarningPolicy())
ssh.connect('example.com', username='user')
# WarningPolicy prints a warning and adds the key to host keys
    

7. Summary Checklist

  1. Catch BadHostKeyException and inspect expected vs. received fingerprints.
  2. Verify new host key via secure channel or ssh-keyscan.
  3. Backup and update known_hosts using ssh-keygen -R and ssh-keyscan.
  4. Automate update carefully in trusted environments.
  5. Optionally use WarningPolicy for non-blocking warnings in development.
See also  How to Handle SSHException in Multithreaded Applications: Thread Safety and Error Propagation in Paramiko