Password-less SSH remote login demystified

This post was written by eli on February 4, 2016
Posted Under: crypto,Linux,Software

This is documented everywhere, and still, I always find myself messing around with this. So once and for all:

The files

In any user’s .ssh/ directory, there should be (among others) two files: id_rsa and id_rsa.pub. Or maybe with dsa instead of rsa. Doesn’t matter too much. These are the keys that are used when you try to login from this account to another host.

id_rsa is the secret key, and is id_rsa.pub is public. The former should be readable only by the user (and root), and the latter by anyone. If anyone has the secret key, he or she may login to whatever host that identifies you with it.

If these files aren’t in .ssh/, they can be generated with ssh-keygen. This should be done once for each new shell account you generate, and maybe even once in a lifetime: It’s much more convenient to copy these files from your old user account, or you’ll have to re-establish the automatic login on each remote server with the new key.

So it goes:

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/eli/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/eli/.ssh/id_rsa.
Your public key has been saved in /home/eli/.ssh/id_rsa.pub.
The key fingerprint is:
77:7c:bf:4d:3b:a9:8a:e7:56:09:24:03:6f:22:d7:ca eli@myhost.localdomain
The key's randomart image is:
+--[ RSA 2048]----+
|       ..        |
|        oo .     |
|     . o ++      |
|      + +  o     |
|       ES . + o  |
|         . . + . |
|            .   +|
|          .o   ++|
|         .+o...oo|
+-----------------+

The public key file (id_rsa.pub) looks something like this:

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAs/ggsf1ZXbvyqQ7NbzIT+UDnGqo1LOgV3PpEUpVt8lw44jDgDCNGXXMZepMVwp3LgcGPKrrZ4n7b9/5zgXVrH86HZVyi+guu0IWLsYA4K+OgQY0m6rmXss/v7lt6ItIZTTJWhgTr4E8DE8+9PibYfBrvdITxdVAVl+FxmDEHhunnMzeqUsTMD7hniEWvlvHE0aE6Gp2rQPMU5sx3+LEGJ4y1BDzChrNa6dc2L7GP1ViGaP9SZBYVFPqbdkdCOOoR6N+FU/VHYIBeK5RdkTkfxGHKHfec1p8sXzveDHT69ouDaw0+c+3j2KlNq4ugnbTGKWrJaQBxQBEzvLgTdePCtQ== eli@myhost.localdomain

Note the eli@myhost.localdomain part at the end. It has no significance crypto-wise. It’s considered a comment. More about it below.

The private (secret) file, id_rsa, looks something like this (I don’t really use it, right? Don’t publish your public key!)

-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEAs/ggsf1ZXbvyqQ7NbzIT+UDnGqo1LOgV3PpEUpVt8lw44jDg
DCNGXXMZepMVwp3LgcGPKrrZ4n7b9/5zgXVrH86HZVyi+guu0IWLsYA4K+OgQY0m
6rmXss/v7lt6ItIZTTJWhgTr4E8DE8+9PibYfBrvdITxdVAVl+FxmDEHhunnMzeq
UsTMD7hniEWvlvHE0aE6Gp2rQPMU5sx3+LEGJ4y1BDzChrNa6dc2L7GP1ViGaP9S
ZBYVFPqbdkdCOOoR6N+FU/VHYIBeK5RdkTkfxGHKHfec1p8sXzveDHT69ouDaw0+
c+3j2KlNq4ugnbTGKWrJaQBxQBEzvLgTdePCtQIBIwKCAQAFJFi0oNaq6BzgQkBi
Q0JmNQ3q0am/dFhlZjx3Y1rpqt0NxuHUdglTIIuzC4RHY5gZpnHOBVausyrbMyff
IJypIyhwnD8rtzDhYua77bh2SFUJL+srRyGXZQba7Ku32h365C5bmb2YsczjTxQJ
Fw1/44MvNv+VozPRI7LJ1YPfSHanPoc77ZvKC/5hsXBgBioIaacO63HNaUeSgIwg
WTNo3zjRBGHPsDmNIR0rMT1STlpMQ/2kJ4BzV0HKKc0F6rDazIDTKTXBiziRSKfM
ftbayNu0iqCcGJWLvMlTNYB36VXBrb3NcKiFfsx99xIKvtG/UV/Slh7wz/ol2PnP
KTmrAoGBAOYpirjibbF2kP2zD6jJi/g6BiKl2xPumzFCEurqgLRWdT5Ns3hbS+F1
c/WhZyCRuYK/ZlQTo7D+FCE9Vft5nsSnZLpOu9kJ2pW4LuAfpNVQCvAcjtRWmMcX
dl0pH68/rdfC/oO3oMcUY8tZrJ/4NOD6dUyXZ+Ahjr5lEznFQWhNAoGBAMgsIHQ+
2s35g6J586msjg1xKUBqkgg88xqdJmSh/kp6krIi7+rGT5so3EOmjw0C6Ks8TVDf
C9RR+HuVOj7wNR9XhS4mlxTgnQyWdox8POqK4NBSdNMoqfMs9fqDBLtR9vItTcel
5hKD740ZF4ktaTgG1WMHElYyE0Iq+rJd/3gJAoGAdl6B220ieIYerlwWrpOJ0B3X
RQTXEZCnlazzyUVmwyUmWo5cTIa5T2D5zsgJJrFYF1seruWHYlbIhh+LTiFKVoH5
Sd9ZSwxhyVdokIVNdQSX6TNCJA9HQdGNVHuMo0VSFzEVLcwmzMioWfOam2m0y3l+
J2PPBY2Z3kKcLFbRLlMCgYEAvLvkFdTc7hckV10KT4Vv/gub7Ec5OvejYjxmBxxk
yeFIfBJP6/zOtt1h9qRa/aOoLGwOYjFi7MJQrwkLCCRPWBCwxR0SGv+qBI3dfSSu
dr104azUjJQN8+iQJrYLxo8cCOji73CId9t7dmgdgVazqdqOrdN3sFsZeOax21/w
3uMCgYBa0ZqQiFgL/sYUYysgqCF6N+aL/Nr19tdp/025feZgwG/9Q1196YTUiADn
jQzU3vpFpTpMnvTybE/+Zq3nGPXthOnsUBRK0/Lc5I8Ofgc9s9T0YrLwio6FGTAm
Hj0oC0CwrDMtSPtm7HOG+wpA4qxO6gf3OkgGzfZccyZjB2NiDQ==
-----END RSA PRIVATE KEY-----

How it works

The gory details left aside, the authentication goes like this: When you attempt to log in, your ssh client checks your .ssh/ directory for the key files. If it finds such, it notifies the server that it wants to try these key files, and sends information on the public keys it has.

The server on the remote host looks up the user’s home directory for a .ssh/authorized_keys file. If such exists, it should find a line that is identical to the id_rsa.pub file on the client’s side. If such match is found, the server uses the public key to create a challenge for the client. The client, which has the secret key passes this challenge, and the authentication is done.

So .ssh/authorized_keys is just a concatenation of id_rsa.pub files, each line for a different key.

Now to the eli@myhost.localdomain part I mentioned above. It goes into the .ssh/authorized_keys file as well. It’s there to help people, who have several authentication keys for logging in from different computers, to keep track which line in .ssh/authorized_keys belongs to which. Just in case they wanted to delete a line or so.

Important: The home directory’s on the remote host must not be writable by anyone else than the user (and root, of course), or ssh will ignore authorized_keys. In other words, the home directory’s permission can be 0755 for example (viewable by all, but writable by user only) or more restrictive, but if it’s 0775 or 0777, password-less login will not work. Rationale: If someone else can rename and replace your .ssh directory, that someone else can log in to your account.

Making the remote host recognize you

There’s a command-line utility for this, namely ssh-copy-id. It merely uses SSH to log into the remote host (this time a password will be required, or why are you doing this?). All it does is to append id_rsa.pub to .ssh/authorized_keys on the remote host. That is, in fact, all that is required.

Alternatively, manually copy the line from id_rsa.pub into .ssh/authorized_keys.

Remember that there is no problem disclosing id_rsa.pub to anyone. It’s really public. It’s the secret file you need to keep to yourself. And it’s quite easy to tell the difference between the two.

Having multiple SSH keys

It’s sometimes required to maintain multiple SSH keys. For example, in order to access Github as multiple users.

First, create a new SSH key pair:

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/eli/.ssh/id_rsa): id_rsa_github2

Note that the utility allows choosing a different file name. This is how a new key pair lives side-by-side with the existing one.

The next step is to create (or edit) .ssh/config. This file should have permission mode 0600 (accessible only to user) because it’s sensitive by its nature, but also because ssh may ignore it otherwise. See “man ssh_config”.

Now let’s follow the scenario of multiple keys on Github. Say that .ssh/config reads as follows:

# Github access as second user
Host github-amigo
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_rsa_github2

If no entry in the config file matches, ssh uses the default settings. So existing ssh connections remain unaffected. In other words, this impacts only the host name that we’ve just invented. No need to state the default behavior explicitly. No collateral damage.

It’s of course possible to add several entries as shown above.

The setting above means is that ssh now recognizes “github-amigo” as a legit name of a host. If that name is used, ssh will connect with github.com, identify itself as “git” and use the said key.

It’s hence perfectly reasonable to connect with github.com with something like:

$ ssh github-amigo
PTY allocation request failed on channel 0
Hi amigouser! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

The line in .git/config is accordingly

[remote "github"]
        url = github-amigo:amigouser/therepository.git
        fetch = +refs/heads/*:refs/remotes/github/*

In the url, the part before the colon is the name of the host. There is no need to state the user’s name, because ssh fills it in anyhow. After the colon, it’s the name of the repository.

A successful session

If it doesn’t work, the -v flag can be used to get debug info on an ssh session. This is what it looks like when it’s OK. YMMV.

$ ssh -v remotehost.org
OpenSSH_5.3p1, OpenSSL 1.0.0b-fips 16 Nov 2010
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Applying options for *
debug1: Connecting to remotehost.org [84.200.84.244] port 22.
debug1: Connection established.
debug1: identity file /home/eli/.ssh/identity type -1
debug1: identity file /home/eli/.ssh/id_rsa type 1
debug1: identity file /home/eli/.ssh/id_dsa type -1
debug1: Remote protocol version 2.0, remote software version OpenSSH_5.3
debug1: match: OpenSSH_5.3 pat OpenSSH*
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_5.3
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: server->client aes128-ctr hmac-md5 none
debug1: kex: client->server aes128-ctr hmac-md5 none
debug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP
debug1: SSH2_MSG_KEX_DH_GEX_INIT sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_REPLY
debug1: checking without port identifier
debug1: Host 'remotehost.org' is known and matches the RSA host key.
debug1: Found key in /home/eli/.ssh/known_hosts:50
debug1: found matching key w/out port
debug1: ssh_rsa_verify: signature correct
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: SSH2_MSG_SERVICE_REQUEST sent
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: gssapi-with-mic
debug1: Unspecified GSS failure.  Minor code may provide more information
Credentials cache file '/tmp/krb5cc_1010' not found

debug1: Unspecified GSS failure.  Minor code may provide more information
Credentials cache file '/tmp/krb5cc_1010' not found

debug1: Unspecified GSS failure.  Minor code may provide more information

debug1: Next authentication method: publickey
debug1: Offering public key: /home/eli/.ssh/id_rsa
debug1: Server accepts key: pkalg ssh-rsa blen 277
debug1: Authentication succeeded (publickey).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: Sending environment.
debug1: Sending env XMODIFIERS = @im=none
debug1: Sending env LANG = en_US.UTF-8

And shell prompt comes next.

… and then it didn’t work

Fast forward to December 2024 and with a OpenSSH_9.2p1 client (Debian-2+deb12u3, OpenSSL 3.0.15 3 Sep 2024), I failed to log in password-less into a really old ssh server:

$ ssh -v theserver.com 
[ ... ]
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering public key: /home/theuser/.ssh/id_rsa RSA SHA256:LKkjdfrgkGfdjgKJ35AfNKJYL+fIQ
debug1: send_pubkey_test: no mutual signature algorithm
debug1: Trying private key: /home/theuser/.ssh/id_ecdsa
debug1: Trying private key: /home/theuser/.ssh/id_ecdsa_sk
debug1: Trying private key: /home/theuser/.ssh/id_ed25519
debug1: Trying private key: /home/theuser/.ssh/id_ed25519_sk
debug1: Trying private key: /home/theuser/.ssh/id_xmss
debug1: Trying private key: /home/theuser/.ssh/id_dsa
debug1: Next authentication method: password
[ ... ]

Following this Q&A (the same person asking and answering), I tried:

$ ssh -v -o "PubkeyAcceptedAlgorithms=+ssh-rsa" theserver.com

And than worked, surprisingly enough!

[ ... ]
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering public key: /home/theuser/.ssh/id_rsa RSA SHA256:LKkjdfrgkGfdjgKJ35AfNKJYL+fIQ
debug1: Server accepts key: /home/theuser/.ssh/id_rsa RSA SHA256:LKkjdfrgkGfdjgKJ35AfNKJYL+fIQ
Authenticated to theserver.com ([193.12.56.12]:22) using "publickey".
debug1: channel 0: new session [client-session] (inactive timeout: 0)
debug1: Requesting no-more-sessions@openssh.com
[ ... ]

Possibly add this line to /etc/ssh/ssh_config (or to your local ~/.ssh/config):

    PubkeyAcceptedAlgorithms +ssh-rsa

Why did it help? I have no idea. Even before the change in the config file, the query on the said variable listed ssh-rsa as one of the candidates:

$ ssh -Q PubkeyAcceptedAlgorithms

So not clear what happened here.

Reader Comments

Thanks for this post! Two minor additions: I am used to have authorized_keys2 instead of authorized_keys, and it works the same for me. Not sure what is the difference between the two, if any, in terms of functionality. And the second thing is that one can use also ssh -vv and also ssh -vvv to get a higher level of verbosity.

Regards,
Rami Rosen

#1 
Written By Rami Rosen on March 10th, 2017 @ 01:25

Add a Comment

required, use real name
required, will not be published
optional, your blog address