SSH + RSA: A Primer

The Secure Shell (SSH) Protocol is a networking protocol for secure login and command execution on a remote machine over TCP. The protocol follows the client/server model, where the initiator of the SSH request is the client, and the recipient of that request is the server.

Basic Usage

The command-line client available on most unix-like systems is the ssh program, and its counterpart on the server is sshd, which stands for the Secure Shell Daemon. The server must have sshd or an equivalent daemon running in order to accept an SSH connection. The default listening port is 22, although this can be changed as an additional security measure.

The simplest usage of ssh takes a single argument, which is a string that contains two parts:

  1. The user to login as
  2. The host to login to

These two pieces of information are joined by an @ sign, so when put together, the full command to login as the root user to a hypothetical remote machine with IP address 192.168.1.12 would be formatted as follows:

ssh [email protected]

Note: snippets in this article show logging in as the root user for the sake of example only. In practice, this should only be done if no other users have been provisioned on the remote machine, as allowing root access over any network protocol is a security hazard. Once additional user(s) have been created, root logins should be disabled in the sshd config file at /etc/ssh/sshd_config by adding the line PermitRootLogin no (or modifing the existing line if it is set to yes). Restart sshd with the following command to make the configuration changes take effect: sudo systemctl restart sshd.

When ssh is invoked with an argument supplying the login user and a target IP address or domain name, it will cycle through all supported authentication methods in an attempt to authenticate with the remote machine. In the absense of additional configuration, this will eventually default to password auth for the specified user. The program will prompt for the user’s password (e.g. the root password for the command in the previous snippet), then initiate an SSH connection and spawn on new remote shell session on succesful password entry.

While password authentication is the base case, ssh supports many different authentication strategies, including RSA keys. This is one of the most common strategies, and is the subject of focus for this article.

RSA Keys

An RSA key pair consists of a private key file and a public key file. These files are generated together from the same seed, such that the original private key can easily be verified as the authentic counterpart of the public key, but reverse-engineering a functioning private key from the public key is computationally infeasible.

This allows for authentication by way of matching a private key on a local resource (the client) with a public key on a remote resource (the server), and conditionally authorizing a connection on the basis of whether the keys are proven to match.

RSA keys for SSH are generated and managed by a family of related programs:

  • ssh-keygen – generates a new public/private key pair
  • ssh-copy-id – adds a public key to a remote machine
  • ssh-agent – manages private keys for the ssh client
  • ssh-add – adds keys (makes them available) to ssh-agent

To enable RSA authentication, a new key pair must be generated. Then, the public key must be added to the server, and the private key must be added to the client.

Generating a new RSA key pair

The ssh-keygen program, as its name implies, is a key generator. It will produce RSA keys by default, although it supports other types that can be specified via the -t option. Invoke the program without any arguments to use the default settings for RSA:

ssh-keygen

Expect the program to display the following prompts:

Generating public/private rsa key pair.
Enter file in which to save the key (/home/<you>/.ssh/id_rsa):

Press enter to accept the suggested default, or type in the absolute path for a different location. The input will be applied to the private key; the public key will have the path and filename but with a .pub suffix for “public.”

Using the default path and filename allows the ssh client to locate and add the private key automatically without any extra configuration. However, there are legitimate use cases for giving the file a custom name, e.g. if a single machine stores multiple key pairs in the same directory and therefore requires file names to be unique. The additional configuraiton required to facilitate this setup is covered later in the article.

Enter passphrase (empty for no passphrase):

A “passphrase” is an optional layer of security that encrypts the at-rest private key, making the key unusable unless it is first decrypted by the same passphrase. This is not to be confused with the “password” of a user on a remote machine that is the basis for password auth in SSH. Passphrases for RSA private keys are considered a best practice. The inconvenience of adding an extra step in the connection process can be offset by various passphrase caching strategies that will be covered later.

Enter same passphrase again:

Press enter if using no passphrase, or re-enter the passphrase from the previous prompt.

The final output of ssh-keygen will be the location of the newly generated keypair, along with some other details like the SHA254 fingerprint.

Adding the public key to the server

If it is possible to establish a password-based SSH connection with the server, the easiest way to add the public key is to use the ssh-copy-id program. This program takes the public key as an argument using the -i option, and appends the key to the remote user’s ~/.ssh/authorized_keys file on the server.

ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]

If there is no existing means of connecting to the server over SSH, any method of data transfer will work. The public key is in fact public, so it’s safe to transmit the file in clear text.

  • If the server is a physically accessible machine, e.g. an old laptop refitted as a hobby server, then email, usb etc will suffice. Once the public key is on the server filesystem, copy its contents and manually add them to ~/.ssh/authorized_keys as a single line. You may need to create this file if it does not already exist, and ensure it is readable/writeable only by the current user with command chmod 600 ~/.ssh/authorized_keys.
  • If the server is managed by a third-party company, read their documentation to determine if RSA key-based authentication for SSH is supported. Some infrastructure providers that offer SSH integration with their services, such as Github and Digital Ocean, facilitate RSA public key upload through their web UI. If documentation is lacking, check any available management consoles or web portals for this option.

Optionally, the public key can also be added to the ~/.ssh/known_hosts file on the client machine. This file tracks the public key(s) on external hosts that have succesfully matched with the private key(s) on the local machine. If a newly generated public key is not added to this file manually, the following warning will be displayed by the ssh client on the first connection attempt with a remote machine which has that public key:

The authenticity of host '<host>' can't be established.
ECDSA key fingerprint is <fingerprint>.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Entering “yes” will add the public key to the client’s ~/.ssh/known_hosts file automatically, and the warning will not be displayed on subsequent connections – unless the public key on that host changes. If there is no plausible explanation for a public key to have changed, this could be an indicator that the key has been tampered with, so proceed with caution.

Adding the private key to the client

By default, ssh tries to add and use any RSA private key located at /home/<you>/.ssh/id_rsa, since this is the default location. If no private key is found here, the program will skip an RSA key-based authentication attempt altogether and proceed to try other auth methods. If a private key is found, but it does not match the public key on the server, the following error will display:

[email protected] Permission denied (publickey).

This is actually the generic error that is shown any time a private key used in a connection attempt does not match the public key on the machine that is the connection target, whether the private key is sourced from the default location or somewhere else on the client file system.

To add a key to ssh explicitly, there are several methods available. The following examples show how to add a key at ~/.ssh/id_rsa_example (note the custom filename, which will prevent the key from being added automatically).

  1. Use the -i option to pass the key as a command-line arg
ssh -i ~/.ssh/id_rsa_example [email protected]

While quick and easy, this strategy is only applicable when invoking the ssh client directly from the command line. Programs that use the SSH protocol under the hood, such as git, won’t add this option on a user’s behalf. To make a key accessible to any program that uses SSH, a different approach is required.

  1. Add the key to the ssh-agent key manager
    The man page for ssh-agent describes it as “a program to hold private keys used for public key authentication.” Once a key has been added to ssh-agent, it will be available to authenticate any SSH connections made during the current session. However, ssh-agent cannot add a key to itself; this must be done by a seperate program called ssh-add. In order to facilitate communication betweeen the 2 programs, ssh-agent exposes 2 environment variables, SSH_AUTH_SOCK and SSH_AGENT_PID; ssh-agent outputs a script to set these two variables when the program is invoked. The script can be run with the eval command. Putting all of this together produces the following sequence of commands:
cmmod 600 ~/.ssh/id_rsa_example
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa_example

The initial chmod command ensures the private key file is readable/writeable only by the current user. This is required for ssh-add to accept the key; otherwise it will reject the key with a permissions are too open error. Then, the output of ssh-agent is evaluated to set the aforementioned environment variables that will be referenced by ssh-add. Finally, ssh-add adds the key the ssh-agent key manager. The key will now be available to the regular ssh client, or any program that leverages the SSH protocol; git, scp, rsync, etc. Also, if the key is passphrase protected, the passphrase will only need to be entered oncefor that session; ssh-agent will cache it for subsequent connections.

The ssh-agent instance storing one or more keys is usually tied to the current shell session, so the whole 3-step process (set permissions, set env vars, add key) will need to be repeated for every new session. This can be automated by moving the logic for adding keys into a file that is sourced every time a new session is spawned, e.g. the .bash_rc file or the equivalent for a different shell. However, if the key is passphrase-protected, this means the passphrase prompt will be displayed at the beginning of every session. To automatically add private key(s) to the ssh-agent for the current session, but only show the passphrase prompt when using a program that initiates an SSH connection, use an SSH config file.

  1. Set the key in a SSH config file
    An optional SSH configuration file can be provided either for the current user at ~/.ssh/config, or system-wide at /etc/ssh/config. Settings are expressed as a key and a value seperated by a single space. The config can be partitioned into sections (or “stanzas”) that contain settings unique to a particular host using the Host keyword. This allows a single configuration file to map an unlimited number of hosts to their respective private keys. A stanza to satisfy the example criteria used in this section is as follows:
Host 192.168.1.12
        AddKeysToAgent yes
        User root
        IdentityFile ~/.ssh/id_rsa_example

This configuration specifes that every time an SSH connection is made to the host at IP address 192.168.1.12, the remote login should be as the root user and the private key at ~/.ssh/id_rsa_example should be supplied as the credential. The AddKeysToAgent keyword dictates that the private key should be added to ssh-agent instead of being added for a one-time use and then discarded. It is generally still a good practice to include a line for eval "$(ssh-agent -s)" in a startup script (.bash_rc or other) to ensure that the agent is actually running at the time when the config settings are read (if the agent isn’t available, the AddKeysToAgent directive may fail silently). Just evaluating the agent output won’t queue the passphrase prompt; that is triggered by ssh-add.

Full configuration settings are available in the man pages via man ssh_config.

Notes

  • To see the public key of any host, use the ssh-keyscan program:
ssh-keyscan -t rsa -H github.com
  • The keychain program can provision a single ssh-agent instance to be re-used across multiple shell sessions, meaning that once a private key is added, it will be available until the next system reboot. This program usually isn’t part of the core GNU/Linux utils but can be downloaded via a package manager. See the project website for more details: https://www.funtoo.org/Funtoo:Keychain
Published
Categorized as Post