Skip to content

Cracking SSH private key passphrases with hashcat and John the Ripper

Extract the hash from an encrypted id_rsa or id_ed25519 with ssh2john, pick the right hashcat mode for legacy PEM keys, and know when only John the Ripper can touch a modern OpenSSH key.

Published on 4 min read

An encrypted SSH private key is just another password-protected secret, and like any of them it reduces to the same question: extract a hash, pick the right cracker and mode, run a wordlist. The wrinkle with SSH keys is that there are two very different formats hiding behind the same -----BEGIN ... PRIVATE KEY----- banner, and only one of them hashcat can touch today.

Two key formats, two different problems

Older keys — anything generated with ssh-keygen -m PEM, or produced by tools that still default to the traditional format — are OpenSSL-style PEM files. The passphrase derives a symmetric key via a simple MD5-based KDF, then decrypts the embedded RSA/DSA/EC key with DES, 3DES or AES-CBC depending on what the header advertises.

Since OpenSSH 7.8 (2018), ssh-keygen writes the newer openssh-key-v1 container by default for every key type, including the now-common id_ed25519. The only KDF that format supports is bcrypt-pbkdf, which is deliberately slower and memory-harder than the old MD5 scheme — good for you if it's your key, bad for an attacker, and the reason a lot of "I pointed hashcat at my key and it errored out" reports turn out to be a format mismatch rather than a cracking problem.

Extracting the hash with ssh2john

Both formats go through the same tool. ssh2john.py ships with the John the Ripper jumbo distribution (on Kali it's typically at /usr/share/john/ssh2john.py or /opt/john/ssh2john.py) and inspects the key header itself to decide how to parse it:

python3 /usr/share/john/ssh2john.py id_rsa > hash.txt

The output is a single line prefixed $sshng$, followed by a cipher id, the KDF parameters, and the encrypted key blob. That prefix is what both hashcat and John recognise — you never feed the raw key file to either cracker directly.

Cracking a legacy PEM key with hashcat

For a legacy key, the cipher id right after $sshng$ tells hashcat which mode to use:

Hash prefixhashcat mode
$sshng$0$-m 22911
$sshng$6$-m 22921
$sshng$1$ / $sshng$3$-m 22931
$sshng$4$-m 22941

Run it the same way you would any other hashcat job:

hashcat -m 22911 hash.txt /usr/share/wordlists/rockyou.txt

If the passphrase follows a known pattern — a word plus a year, say — a mask attack or a rule-mutated wordlist will get there far faster than a raw dictionary pass; see the wordlist and rules setup that actually works.

If hashcat reports a token length exception, don't assume the hash is corrupt first — check whether the key is actually a modern openssh-key-v1 key. That's the next section.

When hashcat can't help: modern OpenSSH keys

Hashcat's documented SSH modes (22911–22951) were built for the legacy PEM/MD5-KDF format. A key protected with bcrypt-pbkdf — which, again, is the default for every key ssh-keygen has produced since 2018 — isn't covered by a stable mode yet. Don't waste GPU time force-feeding it into one of the modes above; the token layout doesn't match and it will reject the hash.

This is where John the Ripper earns its keep. The same $sshng$ output from ssh2john.py cracks directly in John:

john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

If John doesn't auto-detect the right format, list what it has and pick the SSH-related one it offers (the exact name — ssh or ssh-ng — has moved around between jumbo releases):

john --list=formats | grep -i ssh
john --format=ssh-ng --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

Because bcrypt-pbkdf is deliberately expensive, expect throughput far below what you'd see cracking a legacy PEM key or an unsalted fast hash — a short dictionary word still falls quickly, but a long random passphrase is a different story.

Takeaways

Identify which container you're dealing with before picking a tool: -----BEGIN RSA PRIVATE KEY----- and friends are the old PEM format hashcat can crack directly; -----BEGIN OPENSSH PRIVATE KEY----- is the bcrypt-pbkdf container that currently needs John the Ripper. Either way, ssh2john.py is the only way to get a crackable hash out of the key file, and the hash identifier's SSH private key page has the full command set once you've confirmed which mode applies.

Related articles

Extract the hash from an encrypted Word, Excel or PDF file with office2john / pdf2john, then pick the right hashcat mode — 9400 to 10700 — to recover the password.
A practical comparison of hashcat and John the Ripper — GPU vs CPU strengths, autodetection, -m modes, jumbo formats, wordlists and rules — with example commands.
Why bcrypt drops cracking throughput from billions to thousands per second: the cost factor, its GPU-hostile key schedule, and the 72-byte truncation gotcha.