fwknop: How Single Packet Authorization Makes Your SSH Server Invisible to Port Scanners
Hook
Your SSH server receives thousands of brute-force attempts daily, even though fail2ban is running. What if attackers couldn’t even see the port was open? That’s the promise of Single Packet Authorization.
Context
Traditional network security operates on a simple premise: open ports accept connections, closed ports don’t. This binary model creates an unavoidable dilemma—services like SSH must listen on open ports to be useful, but those same open ports broadcast their existence to every scanner on the internet. Port knocking emerged as a solution, requiring clients to hit a sequence of closed ports before access is granted. But port knocking has fatal flaws: sequences can be replayed, timing windows are fragile, and a single spoofed packet in the sequence can trigger a denial-of-service.
Single Packet Authorization (SPA), implemented by fwknop, collapses the entire authentication handshake into one encrypted, HMAC-authenticated UDP packet. Unlike port knocking’s observable knock sequences, SPA packets contain encrypted authorization data including the requested service, timestamp, and client identity. The server passively sniffs traffic via libpcap, validates packets cryptographically, and only then modifies firewall rules to grant temporary access. Services remain completely invisible to port scans—iptables shows no listening ports—while authenticated clients gain seamless access. It’s the network security equivalent of a cloaking device with a secret handshake.
Technical Insight
The architectural brilliance of fwknop lies in its layered validation approach that minimizes attack surface. The daemon (fwknopd) doesn’t bind to any ports; instead, it runs libpcap in promiscuous mode, examining every packet hitting the network interface. When a UDP packet arrives on the configured SPA port (default 62201), fwknopd performs HMAC verification before attempting decryption. This ordering is critical—HMAC validation is computationally cheap and happens before touching expensive crypto operations like AES decryption or GnuPG.
Here’s what a typical client-side SPA packet generation looks like:
# Generate SPA packet to access SSH on server 203.0.113.10
fwknop -A tcp/22 -a 203.0.113.10 -D 198.51.100.5 \
--rc-file ~/.fwknoprc --verbose
# The ~/.fwknoprc contains:
# [server1]
# ACCESS tcp/22
# SPA_SERVER 203.0.113.10
# KEY_BASE64 <base64-encoded-rijndael-key>
# HMAC_KEY_BASE64 <base64-encoded-hmac-key>
# USE_HMAC Y
Internally, the client constructs a payload containing: username, timestamp, requested access (tcp/22), client IP, and a random value for uniqueness. It encrypts this payload using Rijndael in CBC mode with PBKDF1-derived keys, then computes an HMAC-SHA256 over the ciphertext. The final SPA packet is a single UDP datagram—typically 150-300 bytes—sent to the target server.
On the server side, fwknopd’s packet processing pipeline demonstrates defense-in-depth:
# Simplified validation flow from fwknopd
1. Capture packet via libpcap filter
2. Extract HMAC from packet structure
3. Compute HMAC over received ciphertext
4. Constant-time comparison (prevents timing attacks)
5. If HMAC fails: silently drop packet, log nothing
6. If HMAC succeeds: decrypt payload
7. Parse decrypted data, validate timestamp (±2 min window)
8. Check digest against replay attack database
9. Verify requested access against access.conf rules
10. Execute firewall command (iptables, firewalld, pf, ipfw)
The firewall integration is particularly elegant. Rather than modifying your existing rules, fwknopd creates custom chains (FWKNOP_INPUT, FWKNOP_FORWARD for NAT scenarios) and injects jump rules at the top of INPUT/FORWARD chains. When an SPA packet validates, fwknopd adds a time-limited rule like:
iptables -I FWKNOP_INPUT 1 -s 198.51.100.5 -p tcp --dport 22 \
-m conntrack --ctstate NEW -j ACCEPT
This rule permits the client IP for exactly the requested service. After a configurable timeout (default 30 seconds), fwknopd automatically removes the rule. The client must complete their SSH handshake within this window. For persistent connections like SSH sessions, the established connection continues even after the temporary rule expires thanks to connection tracking.
The NAT traversal capability deserves special attention. In cloud environments, you often need to protect services behind NAT gateways (think AWS private subnets). fwknop supports forwarding rules that let the SPA packet authorize access to internal RFC 1918 addresses:
# Client requests access to internal server
fwknop -A tcp/22 -a 203.0.113.10 --fw-access 10.0.1.50
fwknopd then creates both FWKNOP_INPUT rules (to accept the SPA packet) and FWKNOP_FORWARD rules (to permit forwarded traffic to 10.0.1.50). This makes fwknop viable for protecting entire internal networks behind a single SPA gateway—a compelling zero-trust architecture for cloud deployments.
For environments requiring asymmetric cryptography, fwknop supports GnuPG-based SPA. Clients encrypt packets with the server’s public key; only the server’s private key can decrypt. This eliminates shared secret distribution problems but adds computational overhead. The hybrid approach—GnuPG for encryption with HMAC for authentication—combines both benefits.
Gotcha
The most critical limitation is that HMAC authentication, while strongly recommended, remains optional in fwknop. The access.conf allows configurations without HMAC keys, falling back to encryption-only validation. This is cryptographically weak—encryption provides confidentiality, not authentication. An attacker who obtains the Rijndael key (perhaps from a compromised client) could forge valid SPA packets. Always configure HMAC keys and verify REQUIRE_HMAC_BASE64_KEY Y in your access stanzas.
Performance considerations emerge in high-traffic environments. Because fwknopd uses libpcap to examine every packet on the monitored interface, it operates in the data plane rather than control plane. On a server handling 10Gbps of traffic, the packet inspection overhead becomes measurable. The Perl implementation, while mature and well-tested, doesn’t match the raw performance of compiled C/C++ alternatives for packet processing. In practice, this rarely matters for typical SPA use cases—you’re authorizing occasional SSH sessions, not processing millions of SPA packets per second. But if you’re building SPA infrastructure for thousands of concurrent users, the single-threaded Perl daemon may become a bottleneck. Additionally, the libpcap requirement means you need root privileges or specific capabilities (CAP_NET_RAW) to run fwknopd, complicating containerized deployments where you want to avoid privileged containers.
Verdict
Use if: You manage SSH or other critical services exposed to the public internet and want to eliminate the attack surface of visible open ports. It’s ideal for bastion hosts, cloud jump servers, or any zero-trust architecture where services should be invisible by default. fwknop excels when you need NAT traversal for protecting internal networks, or when managing fleet infrastructure where dynamic IP-based access control is required. It’s particularly valuable in hostile network environments—think servers in hostile jurisdictions or infrastructure that faces constant scanning and exploitation attempts.
Skip if: You need always-on connectivity where VPN solutions like WireGuard provide better user experience with similar security properties. If your deployment can’t accommodate the operational complexity of libpcap packet capture, firewall rule manipulation, and the careful key management SPA requires, simpler solutions like certificate-based SSH authentication suffice. Also skip if you’re running in environments with extremely high packet rates where the Perl-based packet inspection becomes a performance bottleneck, or if you need compliance with security standards that don’t recognize SPA as an approved control mechanism.