Published: Thu 23 July 2020
Last year, curious, I asked a group of users and developers how they
manage their SSH keys. Most people responded that they keep one unique
set of keys per machine. Some have a work pair and a home pair. Some
have keys specific to automated processes, or particular functions. Keys
for common scheduled tasks are popular.
For my part, I have simply been using a single key on my laptop to reach
much of the outside world. But how to go the other direction? Today, I'd
like to add a keypair onto a machine in a datacenter where I have an
account, so I can use that to reach a machine at my home from anywhere.
Here's what I hope is an approachable guide to get a beginner started with keys. We will create a set of keys to use first for a simple connection, then one where a third party is needed to access machines or access files from a private home network (like my folder of Fiona the Hippo baby pictures) from the outside world.
We create pairs of public and private keys with
ssh-keygen(1). By default
these both live in ~/.ssh/ on your local machine. You should add the
contents of the public key (only!) to the remote authorized_keys file
(~/.ssh/authorized_keys) on any machines to which you plan to connect.
Everyone has a tortured metaphor for public key encryption, and here's mine:
Do you remember playing with "invisible ink" as a child? If you mail people a bottle
of your special ink blend, you can go to one of them, introduce yourself, and ask them to
sign something for you using that ink. Only you know the secret formula, so only you will
know whether to hold the paper to a light bulb, brush grape juice over it or hold
it under a black light to make their signature visible. You can then show them their own
signature as proof that you knew the secret.
In this metaphor, the remote server was never a child and is therefore
very impressed by your wizardry, but you can read about how this
actually works in RFC 4252 and friends.
When you connect to a remote server from your local machine, you present
public key options to the server to compare with the set of keys they
recognize. (Password authentication is your fallback, unless disabled).
If a match is found, the remote machine will use the shared public key
to randomly generate an encrypted message for your local private key
to unlock. If you can decrypt it and present your answer, the remote
server then knows you're using the correct private key. It would take
it absolutely ages to determine what that private key
is, but it will
trust that the key is your own and grant you access.
The upshot of all this is that you can share your public key around
however you like to gain access without needing a secure method to share
a password, and you can memorize a passphrase connected to one key usable
in multiple places, rather than individual passwords for each machine.
Several people know my laptop's public key and can give me access to any
remote machines they wish. (And they are actually doing that, so I'm a big
My crypto instructor emphasized two things to the point of annoyance:
Don't create your own cryptographic solutions in a vacuum. Their weaknesses will include every single thing which might occur to someone else but didn't occur to you.
Your perfect solution would be something which can never be guessed. We deal in guesses which take an extremely long time.
Neither point is particularly relevant here, except as an evergreen
reminder that it's best to use the cryptographic standards which are
well-studied and to accept that they are not infallible. But the weakest
link in our privacy might well be us, and whether we successfully keep
our private keys inaccessible. Retiring and replacing keys periodically
We're also limited by compatibility... your encryption method of choice
may not be available everywhere. At the moment, we have a number of
options for use with OpenSSH:
-t dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa
If you run ssh-keygen without specifying details, you currently receive
rsa keys. I use ed25519, and so do the others I asked, but if you
connect to something running a positively ancient incarnation of ssh you
might be out of luck with those and need to generate a different type.
I'll make us an example keypair to use, sticking with the defaults.
Generating public/private rsa key pair.
Enter file in which to save the key (/home/pamela/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/pamela/.ssh/id_rsa.
Your public key has been saved in /home/pamela/.ssh/id_rsa.pub.
The key fingerprint is:
The key's randomart image is:
| .. .o...o= |
|. . .o.o .+o.|
|E . .+ o .. .|
|.o. . . o. |
|+. . . oSo.oo. |
|o. . + .+o o. |
|*+o. . .. |
|O*+ o o |
|=o.+ . |
So now I have two new files corresponding to the two keys stored within
~/.ssh, and a lovely free octopus tentacle artwork. Score!
I also created a second set of keys for more specific purposes at the
same time, using ssh-keygen -t to specify ed25519. I'll easily be
able to tell these sets apart, since by default they're always named
id_(type) and id_(type).pub. OpenSSH will look for files in ~/.ssh
named any of the id_* combinations it offers by default. If you
choose to rename or move your keypair, use ssh -i (/path/file) to
point at it. I know people who separate out keys they expect to rotate
frequently, or use at particular events, for example.
When generating keys, you're given the chance to password-protect
access to the file containing your private key (and should probably
do so). That's just done locally. The passphrase can be updated
later, or even left empty and the key's use restricted in other ways.
You can be prompted for your passphrase with each use of the key, or
unlock the private key just once at the start of each X session thanks
ssh-agent(1). This should be
offered automatically on your next login if you are using one of the
default names for your keys. (If interested, you can see how the keys
are loaded in at /etc/X11/xenodm/Xsession, and my next post includes
a basic intro to use of ssh-agent/ssh-add on the command line).
A private key file's format is nothing more than the appropriate big
long string of gibberish, with ominous BEGIN PRIVATE KEY and END
PRIVATE KEY comments on either end. But with this implementation, what
information am I actually handing out in my
public key to all my
valefor:~$ cat .ssh/id_rsa.pub
Clear as mud. Take the contents of your .pub file and edit
~/.ssh/authorized_keys on your destination to add them, or ask whoever
is creating your account to do it for you. (Protip: use :r in vi, r in ed, C-x i
in emacs, C-r in nano to just slap in the entire contents of a file. You
laugh, but it took me over a year to notice I could do this. If that's
also you, you're welcome!)
Your authorized_keys file expects each public key on a separate line
(not required but please put a space between, yikes!) and ultimately
takes the following form:
[options, or leave blank] key-type KEY user@keyorigin
The last field is just a simple generated comment. You can change it to
anything which will remind you where the associated private key lives,
or even add in a more traditional comment using # on its own line.
That's all I needed. Now I can ssh or scp to that remote machine and
I'll be asked for the passphrase for my key if it's not already loaded into my
agent, and then I'm in! All done!
Oh no! I'm not actually in! This only takes me as far as the firewall at
my house. The machines inside, including the one containing the folder
of adorable baby hippo pics I'm trying to reach, are on a
local subnet. I can't just type in the IP address I know... it's internal
to the wrong network.
I'm extremely lazy. I definitely don't want to ssh into my firewall
and ssh from there to the correct machine, yuck! I want a way to punch
straight through, and ideally I'd get to set it up and never think about it
There are two straightforward ways to deal with this. (There are
probably more. I know of two). You can set up port forwarding, where
specifying a specific port at the end of your remote IP address will
tell the firewall which machine you want to contact on the inside,
like dialing an extension on the phone. This is within the realm of
pf(4) and we provide some instructions in
Or you can use the ssh ProxyJump configuration directive. It's still port
forwarding, but designed for this purpose specifically. At this
point we're moving over to looking at information contained within
ssh_config(5), but we actually
only need a few lines of configuration to get the job done here. (Note
that sshd_config(5) also exists, if you want to explore your options on
the daemon end of things, too).
Our ultimate goal here is to be able to type "ssh destination" and reach
the correct machine, without fussing over a login to the machine(s)
in-between, and without exposing the session itself to that intermediary
(merely using it for authentication).
We can do this on a single-use basis on the command line by adding our
key to the authorized_keys file on the "jump host" machine in the middle
as well as the destination, and then making a jump like so:
ssh -J jumphost destination
If your key is authorized on both machines, this should just work. You
don't need a private key on the jump host. Generate your local keys,
put the public key onto both remote machines' authorized_keys lists and
you're there. If you'll need more than one jump, separate with
Let's be real. As simple as that was, after a few times I'll still want
a permanent shortcut. Sometimes I work within test VMs and
other complicated networks, and I'd rather just type the name of the
machine I want and go.
For this case, I will always want the same jump host, and just one
jump, so I am first going to edit ~/.ssh/config to create an entry that
tells OpenSSH what I want to do whenever I try to connect to the remote
My bare minimum configuration here would be:
I know antlion by its internal IP on a different subnet, so let's add
that in as the HostName for ssh to use when talking to the jumphost.
Now we're getting somewhere. When I type "ssh antlion" on this machine,
it will connect to the jumphost machine (my firewall, an ocean away)
and then onward to the correct machine on the internal network. With
HostName I've given the jumphost an address I'm certain it knows how
to reach. This prevents me from needing to fuss with anything on
jumphost.example.com, such as adding an /etc/hosts entry, where I might
not have root access.
Now I can type "ssh antlion" and connect to it, without thinking about
what's in-between. Perfect. Fiona the Hippo awaits!