Securing SSH with post-quantum algorithms
- Published on
- ·4 min read
Why post-quantum, and why now?
The typical argument: "quantum computers are years away, who cares?" Fair point, maybe. But there's a real threat happening right now: harvest now, decrypt later. Someone captures your encrypted traffic today, archives it, and waits 20 years for quantum computers to exist so they can decrypt it retroactively.
For a personal NAS? The risk is manageable. But if you're building something more serious, might as well develop good habits early. OpenSSH has supported post-quantum algorithms since version 9.x, and Debian 13 ships a recent enough build to use them.
Host keys: cleanup time
First order of business: dump the weak keys. By default, Debian generates ECDSA, Ed25519, and RSA. We're removing ECDSA and DSA (obsolete garbage), keeping only Ed25519 and RSA 4096-bit:
# Remove unwanted host keys
rm -f /etc/ssh/ssh_host_ecdsa_key*
rm -f /etc/ssh/ssh_host_dsa_key*
# Regenerate RSA key at 4096 bits if needed
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
Then in sshd_config, be explicit:
# /etc/ssh/sshd_config - Host keys
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
Ed25519 up front: small, fast, built on Curve25519 which is solid. RSA 4096 as a fallback for ancient clients.
Post-quantum key exchange
Now it gets interesting. OpenSSH now supports hybrid algorithms that combine a classical exchange (X25519) with a post-quantum one. If the post-quantum algorithm turns out to suck, classical security still holds. And vice versa. It's security hedging.
# /etc/ssh/sshd_config - Key exchange
KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com,curve25519-sha256,diffie-hellman-group16-sha512
Let's break this down:
- mlkem768x25519-sha256: hybrid ML-KEM 768 (the NIST FIPS 203 standard, formerly CRYSTALS-Kyber) + X25519. Most recent, most recommended.
- sntrup761x25519-sha512@openssh.com: hybrid NTRU Prime 761 + X25519. In OpenSSH longer, excellent fallback.
- curve25519-sha256: classical exchange, for clients that don't speak post-quantum yet.
- diffie-hellman-group16-sha512: classical DH as a last resort.
Ciphers and MACs
For symmetric encryption and authentication codes, stick with proven stuff:
# /etc/ssh/sshd_config - Ciphers
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
ChaCha20-Poly1305 by default - performant, well-studied. AES-GCM variants for machines with AES-NI acceleration (the Intel N95 supports this). Solid choices.
Hardened authentication
Passwords? Nope. SSH keys only:
# /etc/ssh/sshd_config - Authentication
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
Critical points:
- PermitRootLogin no: connect as
nasadmin, thensudoif needed - MaxAuthTries 3: limits brute-force attempts (bots get discouraged quick)
- LoginGraceTime 30: 30 seconds to auth, no more. Slows down automated attacks.
Access warning banner
Often overlooked, but legally important in many jurisdictions. This banner is sometimes required to prosecute unauthorized access:
# /etc/ssh/sshd_config
Banner /etc/ssh/banner.txt
# /etc/ssh/banner.txt
*************************************************************
WARNING: Unauthorized access to this system is prohibited.
All connections are monitored and recorded.
By connecting, you agree to comply with applicable policies.
*************************************************************
Not just window dressing. Actually matters if compliance is part of your world.
Automation with Ansible
All of this gets deployed via an Ansible role, integrated with the devsec.hardening collection. Applies hundreds of hardening rules based on CIS and NIST benchmarks.
The playbook looks like:
# playbook.yml - excerpt
- name: Harden SSH
hosts: nas
roles:
- role: devsec.hardening.ssh_hardening
vars:
ssh_kex:
- mlkem768x25519-sha256
- sntrup761x25519-sha512@openssh.com
- curve25519-sha256
- diffie-hellman-group16-sha512
ssh_host_key_algorithms:
- ssh-ed25519
- rsa-sha2-512
- rsa-sha2-256
ssh_ciphers:
- chacha20-poly1305@openssh.com
- aes256-gcm@openssh.com
- aes128-gcm@openssh.com
ssh_allow_users: nasadmin
ssh_max_auth_retries: 3
Ansible's magic: it's idempotent. Run it 50 times, nothing breaks. That's exactly what we want.
Client side: your key
To connect to the NAS, you need an Ed25519 key:
# Generate an Ed25519 key
ssh-keygen -t ed25519 -C "user@workstation"
# Check what your client supports
ssh -Q kex
The ssh -Q kex command shows your client's capabilities. If mlkem768x25519-sha256 appears, you're golden.
Verification
After deployment, validate it works:
# Check negotiated algorithms during a connection
ssh -vv nasadmin@192.168.1.50 2>&1 | grep "kex:"
# Expected output:
# debug1: kex: algorithm: mlkem768x25519-sha256
Done.
Setting up SSH with post-quantum algorithms is straightforward. Zero performance penalty. It's a proactive measure that protects today against tomorrow's threats. With OpenSSH 9.x and Debian 13, everything exists out of the box - just configure the right parameters and let Ansible handle the rest.
Debian NAS from scratch series — This article is part of a complete series on building a Debian NAS.
Previous: Hardening your NAS Linux kernel to CIS Level 2 standards | Next: Secure remote access to your NAS with Tailscale