Vivian Voss

Obscurity Is a Waste of Money

it philosophy security freebsd ssh

A lock once sat in the window of Bramah's shop in London with a sign beside it: "The artist who can make an instrument that will pick or open this lock shall receive two hundred guineas the moment it is produced."

In the summer of 1851, with the Great Exhibition in full swing, an American locksmith called Alfred Hobbs took up the offer. He began on 24 July and had the lock open on 23 August, and the arbitrators recorded that he spent sixteen days on it and fifty-one hours in the room with it. On 2 September they awarded him the two hundred guineas, and London enjoyed a proper kerfuffle about whether it was decent to publish how such things are done.

The treatise on locks that came out of the affair two years later settled that question in a sentence every security engineer has since rediscovered for himself: "Rogues are very keen in their profession, and know already much more than we can teach them." That is the lesson usually drawn from Hobbs, and it is the right one. It leaves out a second lesson standing in the same room, which is that the best lock-picker alive needed fifty-one hours.

Machines do rather better now. masscan, a scanner written by Robert Graham, says of itself that it "can scan the entire Internet in under 5 minutes, transmitting 10 million packets per second, from a single machine." Five minutes is roughly the time it takes to make a pot of tea, and it covers one port.

The sentence everybody quotes

"Security through obscurity is no security" is the sentence that ends most conversations on the subject, usually in a forum and usually in the first reply. It has a respectable ancestry, of which the treatise on locks is the first entry. In January 1883 Auguste Kerckhoffs set out six requirements for a military cipher, and the second asked that the system "n'exige pas le secret, et qu'il puisse sans inconvénient tomber entre les mains de l'ennemi": it must not depend on secrecy, and it must be able to fall into enemy hands without harm. Claude Shannon carried the idea into engineering in 1949 with a single assumption, "we shall assume that the enemy knows the system being used." Saltzer and Schroeder wrote it into the design principles of computer protection in 1975 and called it open design: "The mechanisms should not depend on the ignorance of potential attackers, but rather on the possession of specific, more easily protected, keys or passwords."

Every one of those sentences concerns the mechanism, the cipher or the lock, and every one of them is right. The question of where the door is happens to come up in none of them, and it is the question this piece is about.

The same sources say more than the quotation keeps. Shannon defended his assumption as "pessimistic and hence safe, but in the long run realistic, since one must expect his system to be found out eventually", and the word that matters there is eventually. Saltzer and Schroeder followed their eight principles with two more which, they wrote, "apply only imperfectly to computer systems", and the first of those is the work factor: "Compare the cost of circumventing the mechanism with the resources of a potential attacker." The founders of the discipline said both things at once. Never let the lock depend on a secret, and count what it costs the other side to get in.

What the money buys

Here is the arithmetic the forum reply skips. At masscan's own advertised rate, one port across the whole IPv4 internet takes under five minutes, and all 65,535 ports at the same rate take 65,535 times as long, which comes to roughly 227 days. Moving a service off its expected port does nothing whatever to the lock. It multiplies the bill of everybody who was not looking for you in particular by sixty-five thousand.

In practice the whole manoeuvre is a single line in the daemon's configuration, shown here with an example value:

# /etc/ssh/sshd_config
Port 50022

A scanner that only knocks on 22, or tries 2222 after it, never meets the service. One that sweeps every port meets it in the end, having paid the 65,535-fold bill to get there.

What the scanner pays, in four numbers < 5 min one port across all of IPv4, as advertised ≈ 227 days all 65,535 ports at the same rate 103,000 : 0 logins on 22 against a high port, 2007 and 2008 1,500 / day probes on every privileged port, 2024 masscan README · Owens and Matthews, USENIX LEET 2008 · Griffioen et al., IMC 2024 · arithmetic 8 October 2026

Whether anybody pays that bill depends on the decade. Two researchers at Clarkson University ran SSH honeypots for eleven weeks across 2007 and 2008 and counted more than 103,000 login attempts on port 22, and of the servers they had listening on a high port they wrote that "no malicious login attempts directed at the servers running on these ports were observed". Eleven years later a handler at the SANS Internet Storm Center set port 22 against port 2222 on a honeypot for a week and found only about half as much again going to 22, so the popular alternative drew two thirds of the original's traffic. (2222 is simply the second place anybody looks, and the scanners worked that out some years ago.)

Then, in 2024, a group that had watched three unused /16 networks for ten years reported that ports 22, 80 and 8080 had carried more than a third of all scan packets in 2015 and under three per cent eight years later, with every privileged port now probed more than 1,500 times a day. Moving a port, they concluded, is "much more futile than one would think". Scanning has become cheap enough to cover everything, and the money obscurity wastes is worth a little less each year.

There is a second leak, and the administrator opens it himself. Every TLS certificate issued today is published in the Certificate Transparency logs, and the logs are watched. In 2018 a team put fresh certificates on unused subdomains and saw the first DNS queries for those names between 73 seconds and about three minutes later. In 2022 a developer documented attackers taking over freshly installed WordPress sites through the setup page within four minutes of the certificate being issued, sometimes in under one, whilst the plain HTTP version of the very same site was left untouched. Nobody found those servers by scanning. The servers had announced themselves.

So the bill that obscurity hands the untargeted attacker is real but shrinking, and one's own certificate can tear it up. What survives all of these findings is the part the forum reply never mentions. NIST's Guide to General Server Security states the open design principle in so many words and then recommends renaming default directories regardless, because "while this will not stop determined attackers, it will force them to work harder to compromise the server, and it also increases the likelihood of attack detection". FreeBSD's own security manual page, which Matthew Dillon wrote for FreeBSD 3.1 in December 1998, makes the same point about the system as a whole: "Half the job of the onion is to slow down the attacker rather than stop him in order to give the detection layer a chance to catch him in the act." A log that records ten thousand strangers a day goes unread. When it records three, somebody reads it in the morning and notices the fourth.

The onion, from the street inwards

The same manual page opens with a sentence I would hang above the door of every server room: "Security is best implemented through a layered onion approach. In a nutshell, what you want to do is to create as many layers of security as are convenient and then carefully monitor the system for intrusions."

The clearest way to see what each layer is for is to follow a visitor in from the street and to ask, at every stage, what that layer takes away from him. Only the first of them is obscurity, and several of them are set out at length in my book, Integrated by Design, which is where readers who want those builds in full will find them.

The onion, from the street inwards the visitor's way in The gate port knocking, or Single Packet Authorization takes away: knowing that a door exists obscurity, on a key The door sshd: no root, keys only, KbdInteractiveAuthentication no · pf rate limit · blocklistd takes away: guessing, which becomes arithmetic nobody can afford mechanism The stairs sudo asks for a password every time · su only for wheel, with root's password takes away: the step from a stolen key to root mechanism The rooms one service per jail · blind jails without an address · Unix sockets · no shell takes away: the walk from one break-in to everything else mechanism FreeBSD 15.1: sshd_config(5), pf.conf(5), blocklistd(8), su(1) and pam_group(8), jail(8) · sudo from the ports tree

The gate. Classic port knocking, which Martin Krzywinski described in 2003, keeps the door shut until somebody knocks a secret sequence on other ports. With knockd from the ports tree and a table in pf it takes a few lines, shown here with an example sequence and an example port:

# /usr/local/etc/knockd.conf
[openSSH]
    sequence    = 7000,8000,9000,7500
    seq_timeout = 5
    tcpflags    = syn
    command     = /sbin/pfctl -t ssh_friends -T add %IP%

# /etc/pf.conf
table <ssh_friends> persist
block in proto tcp to port 50022
pass  in proto tcp from <ssh_friends> to port 50022

Its weakness, as Michael Rash pointed out the following year, is that "an attacker need only replicate a knock sequence", so that some would argue it "suffers from the standard arguments against 'security through obscurity.'" Rash's answer was Single Packet Authorization: one encrypted packet that cannot be replayed, after which, in his words, "anyone using nmap to look for SSHD can't even tell that it is listening." His fwknop is in the ports tree as well, and the server side needs a port to open and two keys:

# /usr/local/etc/fwknop/access.conf
SOURCE              ANY
OPEN_PORTS          tcp/50022
KEY_BASE64          <generated with fwknop --key-gen>
HMAC_KEY_BASE64     <generated with fwknop --key-gen>

# on the client
% fwknop -A tcp/50022 -a 198.51.100.7 -D host.example.com --use-hmac

The daemon on the server reads and checks the packet, then opens the port for that one address for a short while, and until then the port gives nothing away to anybody. That is obscurity resting on a key, which is the arrangement Kerckhoffs asked for.

The door. Behind the gate sits sshd, and from here on the layers are mechanism. Root never logs in from the network, and nobody logs in with a password. OpenSSH changed its default for root from yes to prohibit-password with version 7.0 in August 2015, and FreeBSD's base system goes a step further; its manual page says simply: "The default is no." FreeBSD also ships with password authentication switched off, and here is the corner most hardening guides walk straight past: keyboard-interactive authentication is switched on, and through it PAM will still accept a password, as the manual notes in a single subordinate clause. It is the one line to change on a new server once the key is in place, and the configuration that means what it says reads like this:

# /etc/ssh/sshd_config, example values
Port 50022
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
UseBlocklist yes

The one account name every scanner on earth already knows becomes useless to all of them, and keys in place of passwords turn guessing into arithmetic nobody can afford.

The door keeps count as well. pf can limit how many new connections a single address may open in a given time and move anybody who exceeds it into a table that is then refused outright, in a handful of lines and with no daemon reading logs:

# /etc/pf.conf, example values
table <bruteforce> persist
block in quick from <bruteforce>
pass in proto tcp to port 50022 keep state \
    (max-src-conn-rate 3/30, overload <bruteforce> flush global)

blocklistd (formerly blacklistd), which came over from NetBSD and has been in FreeBSD since 11, works from the other end: sshd, once told to, reports every failed login to it directly, and it blocks the address at the firewall without parsing a single log line.

The stairs. The key opens the account and nothing more. Becoming root takes a second secret, typed after the first has already worked: the administrator's account may use sudo, which comes from the ports tree, and sudo asks for the password every single time.

# /usr/local/etc/sudoers, edited with visudo, example
%wheel ALL=(ALL:ALL) ALL
Defaults timestamp_timeout=0

There is no NOPASSWD in it, and the timeout of zero switches off the five minutes during which sudo would otherwise stop asking. A stolen key, or a session that somebody else has taken over, then ends at the user's door. The base system has the same arrangement without a single package: su lets only members of the wheel group become root, and then only with root's own password, which one line of the PAM configuration enforces as shipped.

# /etc/pam.d/su, as shipped
auth  requisite  pam_group.so  no_warn group=wheel root_only fail_safe ruser

For the jobs that must run unattended and cannot be asked for anything, mdo, new in FreeBSD 14.2, changes credentials through a kernel policy and needs no setuid program to do it.

The rooms. Each service gets a jail of its own, so that a break-in through the web application lands in a prison that holds the web application and nothing else. Only the jail at the front holds an address. The jails behind it are blind: no address of either family, no routing table, no DNS to ask and nothing to telephone home with. They talk through Unix sockets in directories that the host creates and mounts into each jail with nullfs, read-write for the jail that provides the socket and read-only for the one that uses it. Each of them is a room without doors, with one slot in the wall that the host cuts to size. In jail.conf it looks like this, with example names and an address from the documentation range:

# /etc/jail.conf
proxy {
    path = "/jails/proxy";
    ip4.addr = 192.0.2.10;
    mount.devfs;
    mount += "/shared/app /jails/proxy/var/run/app nullfs ro 0 0";
    exec.start = "/bin/sh /etc/rc";
}
app {
    path = "/jails/app";
    ip4 = disable;
    ip6 = disable;
    mount.devfs;
    mount += "/shared/app /jails/app/var/run/app nullfs rw 0 0";
    exec.jail_user = "app";
    exec.start = "/usr/local/bin/appd --foreground &";
}
A blind jail and its one slot in the wall internet every scanner host · pf decides what passes proxy jail address 192.0.2.10 the only one with an address /shared/app socket directory on the host nullfs, read-only app jail no address no routing table no shell nullfs, read-write no network path to the app jail at all Example names and a documentation address · jail(8), ip4 = disable, ip6 = disable · nullfs(4)

The app jail can go one step further and carry no shell at all. An exploit that gets in usually wants a shell next, which is why its payload is called shellcode, and a jail that contains the daemon and what the daemon needs offers the visitor nothing to start. Starting it that way is easier than it sounds, because jail(8), as its source shows, hands a command to /bin/sh only when the command contains one of the characters a shell would interpret, and a plain path with arguments and a trailing ampersand contains none of them. It also leaves the administrator debugging from outside, which is a faff and worth every minute of it.

How far is far enough

FreeBSD's manual answers its own question with unusual candour. Of making the base system immutable with schg flags it says: "This might be overdoing it", and a little further on: "being too draconian in what you attempt to protect may prevent the all-important detection of an intrusion." Saltzer and Schroeder, having listed their principles, warned that they "do not represent absolute rules--they serve best as warnings". Every layer is mechanism, and their first principle, economy of mechanism, asks for as little of that as one can manage.

The defender pays as well. The machine that serves this blog once locked its own administrator out for an afternoon, for opening connections a little too eagerly, which was the defence working exactly as written and the defender footing the bill. Every knock sequence has to be remembered, every jail without a shell has to be debugged from outside, every renamed directory has to be found again by the colleague who joins next year, and every rule that refuses strangers will, sooner or later, refuse the administrator.

The rule that falls out of all this is the work factor turned round to face the defender as well: a layer is worth having for as long as it costs the attacker more than it costs you. Keys, no root, a rate limit at the door, a password between the user and root, one service per jail, blind jails without a shell, sockets and an SPA gate pass that test with room to spare. A renamed port on its own still passes, more narrowly every year.

The strongest objection

The case against obscurity deserves its full weight, and it rests on four good arguments.

The first is complacency. A defender who moves a port and feels safer has bought a feeling with the attacker's money, and the FreeBSD manual names the danger exactly: security that cannot detect an incursion "presents you with a false sense of safety". The second is the 2024 measurement, because if every port is probed more than 1,500 times a day, hiding is a smaller thing than it used to be, and the authors were right to say so. The third is mechanism: every layer is code and configuration that can be wrong, and a knock daemon is one more program listening on the outside. The fourth is the one nobody disputes. None of it slows down somebody who has your name, your address, your certificate and an afternoon.

All four are true, and all four are arguments against relying on obscurity, which is what Kerckhoffs said in 1883 and what nobody sensible proposes. Spending the attacker's money is a separate activity, and it stays worth doing for as long as the bill lands on his side of the fence.

The limit

Four limits, stated plainly.

Nothing here stops a targeted attacker. Obscurity raises the cost of looking for anyone, and it barely touches the cost of looking for you.

The scanning figures are other people's. The 227 days rest on masscan's own advertised rate, and the telescope study counts probes sent to addresses where nothing runs, whereas a login attempt needs a service that answers. The clearest evidence for a moved port dates from 2007 and 2008.

Layers cost the defender. Every one of them is something to maintain, and a few of them will lock out the very people they were built to let in.

This essay is not a configuration guide. It names no port, no knock sequence, no jail and no socket from the machine that serves it, which is obscurity applied to the essay itself, and the reader has lost nothing by it.

Hobbs needed fifty-one hours for a lock that kept its workings to itself, and he earned every guinea. The scanner that reaches this server tonight has five minutes and a list of the usual doors, and nobody will pay it a guinea for finding an unusual one.

Obscurity is no lock, and it was never meant to be one. It multiplies the cost of looking for anyone, by 65,535 for a scanner that has to try every port, and its real dividend is a log somebody reads. The layers that carry the weight are mechanism: keys and no root at the door, a second secret on the stairs, blind jails with one service each in the rooms, and a key-protected gate in front of them all.