ZNHOO Whatever you are, be a good one!

OpenSSH

  1. Overview
  2. Goal
  3. Generate Public-key pair
  4. Distribute the public key
  5. ssh
  6. Passwordless
  7. ssh-find-agent
  8. SSH config
  9. SSH tunnel
  10. Make it simple
  11. Backup
  12. Change passphrase
  13. Reference

Overview

Secure Shell, or SSH, is a cryptographic (encrypted) network protocol to allow remote login and other network services to operate securely over an unsecured network.

In the old ages, telnet protocol traffic is plain text subject to interception. Now services on SSH can be encrypted by different ciphers (i.e. RSA, DSA etc.). In addition to remote login, other services can reside on SSH protocol like content transfer.

protocol (i.e. SSH) is different from implementation (OpenSSH) or command (i.e. ssh, scp etc.). OpenSSH gives tool set like ssh, scp, sftp, ssh-keygen etc.

Goal

In this post, I will setup remote login with Public-key infrastructure to achieve passwordless connection.

  1. local host
  2. remote host
  3. ssh from local host to remote host with key pair.

Generate Public-key pair

The core of Public-key infrastructure is creating personal public and private key pair. The public key literally is distributed publicly over the Internet, while the private key is privately kept by yourself.

On local host that we connect from, run ssh-keygen:

~ $ ssh-keygen -l [ -f ~/.ssh/id_rsa_github.pub ]

~ $ ssh-keygen -t ecdsa -b 384 -o -C "zachary.hu@konghq.com (Kong Dev)" -f ~/.ssh/id_rsa_kh

~ $ ssh-keygen -t rsa -b 4096 -o -C "email@example.com (openssh-rsa-4096)" -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/username/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/username/.ssh/id_rsa.
Your public key has been saved in /home/username/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:abcdedajfajqiefafanfaqrnafafaf8rqhfafdafjajfafeaqn rsa-keys
The key's randomart image is:
+---[RSA 2048]----+
|        .+o+o.+==|
|       oE.o..q..=|
|      . o=o......|
|     + o.=o..s. .|
|    . + S . .... |
|     . + . ..q.  |
|    . . +    .   |
|   o ..+     w   |
|  .o==+ +        |
+----[SHA256]-----+
  1. Do NOT use "ssh-dss" (DSA) public key algorithm. Refer to openssh legacy. The default algorithm is rsa such that -t rsa may be omitted..
  2. ~/.ssh/id_rsa is the private key (also termed as identity in SSH); ~/.ssh/id_rsa.pub is the public key to be shared over the Internet.
  3. It prompts for passphrase to encrypt private key locally. If someone steal the private key file, he cannot impersonate you to communicate on the Internet. You are encouraged to set a passphrase.

    You can just press ENTER key to not use passphrase - empty passphrase to achieve passwordless. This does make a difference when connecting to remote host. Details refer below.

    It can be changed afterwards by -p -f argument.

  4. The default key size is 2048 which is subject to modernized brute force break. Use 4096!
  5. The CLI option -o is deprecated. Read What does ssh-keygen "-o" do?.

Distribute the public key

Now share the public key file ~/.ssh/id_rsa.pub over the Internet to remote host. If we lost hte public key, we can restore it from the private key:

~ $ ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub

append the public key to ~/.ssh/authorized_keys on remote host.

authorized_keys is a file that stores SSH public keys. Each time a new publick-key pair is generated, append the new public key to it. To accomplish this, run ssh-copy-id:

ssh-copy-id -i ~/.ssh/id_rsa.pub -p 22 uname@192.168.0.10

Alternatively, we can use:

cat ~/.ssh/id_rsa.pub | ssh -p 22 uname@192.168.0.10 "mkdir -p ~/.ssh && cat - >>  ~/.ssh/authorized_keys"
  1. 192.168.0.10 can also be the domain name of remote host.
  2. Both ways achieve the same purpose: append the public key to authorized_keys file. ssh-copy-id automatically creates ~/.ssh/authorized_keys if it does not exist.
  3. Pay attention to -p argument of mkdir: make parent directories as needed, no error if existing.

No matter which command you chose, you should see something like:

The authenticity of host '[192.168.0.10]:22 ([192.168.0.10]:22)' can't be established.
RSA key fingerprint is SHA256:afjaijfjfajeiqrityqpqvnbvlaeafjadfj.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
uname@192.168.0.10's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh -p '22' 'uname@192.168.0.10'"
and check to make sure that only the key(s) you wanted were added.
  1. SSH with Public-key has not been configured yet. So you are required to provide the remote host uname password.
  2. SSH into remote host to make sure authorized_keys permission is 600.

    ssh-copy-id can handle permissions automatically while the alternative method might not.

  3. It is distribute/copy, NOT cut from local host. That means the public key should be kept locally.

ssh

By default, sshd on remote server accepts both key-based or password-based connection. The key method is superior to password method.

~$ ssh -p 22 uname@192.168.0.10

Enter passphrase for key '/home/username/.ssh/id_rsa': 
Last login: Thu Dec  3 07:59:14 2015 from 122.205.7.55

From the output, ssh prompts you to input passphrase of public-key files. Recall that we have set a passphrase when generating public-key pair to encrypt public-key files on local host.

If you leave the passphrase empty at setup, ssh will login immediately without any interaction - passwordless login. However, such login lose security of public-key files themselves.

On macOS, the Terminal app would set locale environment variables on startup when SSH to a remote server. This would disrupt remote locale. Therefore, either disable that option in Terminal settings or customize remote server's locale setting. For example CentOS, check /etc/locale.conf.

Passwordless

A better way on local host is to:

  1. Use passphrase to encrypt key pair.
  2. Use ssh-agent to unlock and cache private key.

The little program ssh-agent does you a favor by managing your keys for you. You enter the passphrase once, and after that, ssh-agent keeps your key cached in its memory and pulls it up whenever it is asked for it during current X/shell session.

To use the agent first create one. Just enter eval $(ssh-agent -s) and thats all. This will put you in a bash shell with two important environment variables SSH_AGENT_PID (agent process ID) and SSH_AUTH_SOCK (agent socket location on system) exported. ssh-agent -s alone does not export the two variables automatically, which applies to ssh-agent -k etc. as well.

Append:

# Start the ssh-agent if not already running
if ! pgrep -x -u "${USER}" ssh-agent >/dev/null 2>&1; then
    eval $(/usr/bin/gpg-agent -s)
fi

to ~/.bashrc.

After that you should add your key (file) by ssh-add. Without arguments, it adds the standard identity ~/.ssh/id_rsa, ~/.ssh/id_dsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ed25519 and ~/.ssh/identity (SSHv1). To add an identity deviant from standard location or name, run ssh-add /location/of/key. ssh-add will ask for passphrase. After that the key is loaded into the key manager ssh-agent for the whole X/shell session.

  1. Check if any agents exist on system: ps -ef | grep -i ssh-agent.

    ssh-agent is usually started at the beginning of the X session, or from a shell startup script like "~/.bash_profile". It works by creating a unix-socket, and registering the appropriate environment variables so that all subsequent applications can take advantage of its services by connecting to that socket. Clearly, it only makes sense to start it in the parent process of an X session to use the set of decrypted private keys in all subsequent X applications.

    On my Gentoo system, "startx" will help launch ssh-agent automatically.

  2. printenv | grep -i ssh, check whether SSH_AGENT_PID (agent process ID) and SSH_AUTH_SOCK (agent socket location on system) are exported to current X/shell environment. All subsequent applications depend on these two variables to make use of ssh agent on system.
  3. Current agent

    Agents found on system does NOT guarantee those two variables exported. For example, a sever shared among multiple users has many agents running which might be created by some of the users. Now you login for an X/shell session. That very session (applications thereof) does NOT know anything about existing agents on system since SSH_AUTH_SOCK and SSH_AGENT_PID are not set to one of the agents.

    The current agent means the agent to which SSH_AGENT_PID and SSH_AUTH_SOCK are pointed. To switch to another agent, set the two variables to the new agent's process ID and socket path.

  4. Example of adding key to agent.

    ssh-add ~/.ssh/id_rsa
    Enter passphrase for /home/username/.ssh/id_rsa: 
    Identity added: /home/username/.ssh/id_rsa (/home/username/.ssh/id_rsa)
    

    Type your passhrase. Now, you should NOT be prompted for a password whenever you use ssh, scp, or sftp etc. commands.

  5. Limit: ssh-add does NOT persist across reboot/shutdown/logout due to loss of ssh agents or the two variables. So usuallly the passwordless login only lasts for the current agent.

    Each time of login and SSH connection, run ssh-add once to achieve passwordless login. For a server, this is fine. But on personal laptop/PC that require shutdown frequently, it is tedious/annoying to run ssh-add.

    This is a trade-off between security and utility. If you want passwordless forever, resort to keychain.

  6. Reuse existing agents.

    For example, on my Gentoo, ssh-agent started with startx XFCE4 DE is attached to that specific X session (Xfce4-terminal emulator included). If I don't launch startx or switch to another virtual terminal (Ctrl+Alt+F2) and login, this login shell does not either start ssh-agent or know the agent already launched!

    We can launch ssh-agent in shell's configuration script (.bashrc, .bash_profile, .xinitrc etc.). Add eval `ssh-agent -s` > /dev/null and ssh-add to .xinitrc (mainly for X), bash_profile (mainly for login shell and subsequent interactive shell), and .bashrc (mainly for interactive shell).

    At the end of .bash_profile, .bashrc is sourced. If you want to execute a shell script only once for the whole login, add to .bash_profile instead of .bashrc (would be executed each time a new terminal emulator launched).

    However, adding ssh-agent and ssh-add to shell's configuration script will always launch an new agent on every login. We only need SSH on demand! So the basic idea is to:

    1. let new X/shell reuse eixsting agents.
    2. If there is not any agents, create one.

ssh-find-agent

ssh-find-agent is a tool:

  1. locating existing ssh compatible agent sockets (e.g., ssh-agent, gpg-agent, gnome-keyring, osx-keychain).
  2. prompt to create one if no agents found.
  3. optionally (invoked with -a or -c), sets SSH_AUTH_SOCK, SSH_AGENT_PID environment variables accordingly.
    1. and set temporal ssh alias

For lastest update, check GitHub.

SSH config

  1. man ssh_config. The client side config file /etc/ssh/ssh_config or ~/.ssh/config. Local host - the host that connect from.

    ssh command supports a variety of different parameters. For example, in order to restrict malicious attempt, you are recommended to change default port number. Other parameters are remote host IP etc. difficult and tedious to remember and input.

    There's a much more elegant and flexible solution - put those arguments in ~/.ssh/config. The configuration files contain sections separated by “Host” specifications, and that section is only applied for hosts that match one of the patterns given in the specification.

    Each host section defines a host alias. When connecting, just ssh host-alias. ssh will look up alias from config file and choose the first matched to connect to.

    The first obtained value for each parameter is used, more host-specific declarations should be given near the beginning of the file, and general defaults at the end. Values at the beginning are superior to those afterwards. The order of privilege:

    1. command-line options
    2. user's configuration file (~/.ssh/config)
    3. system-wide configuration file (/etc/ssh/ssh_config)
    

    Here is an example:

    # ~/.ssh/config
    Host vps
         HostName 192.168.0.10
         Port 1025
         User bob
         IdentityFile ~/.ssh/vps_rsa_key
    Host github-project1
        User git
        HostName github.com
        IdentityFile ~/.ssh/github.project1.key
    Host github-org
        User git
        HostName github.com
        IdentityFile ~/.ssh/github.org.key
    Host github.com
        User git
        IdentityFile ~/.ssh/github.key
    Host tunnel
        HostName database.example.com
        User coolio
        LocalForward 9906 127.0.0.1:3306
        IdentityFile ~/.ssh/coolio.example.key
    

    From the example, different keys are set for different github.com projects.

    The "LocalForward" is interesting! 9906 (or 127.0.0.1:9906) is local port and xx.xx.xx.xx:3306 is a remote host port. SSH will establish a tunnel in between 127.0.0.1:9906 and xx.xx.xx.xx:3306 through the intermediate HostName. For example, the target host xx.xx.xx.xx only accepts connection to port 3306 locally. This example is special case for database operations. The target host is the same as HostName. Actually, it can even be "www.youtube.com:443"

    To create the tunnel, just run ssh -qfCNT tunnel:

    1. -q causes most warning and diagnostic messages to be suppressed.
    2. -f force ssh to background just before command execution but after password authentication.
    3. -C compresses all data (including stdin, stdout, stderr, and data for forwarded X11, TCP and UNIX-domain connections). Compression is desirable on modem lines and other slow connections, but will only slow down things on fast networks.
    4. -N does not execute a remote command.
    5. -T disable pseudo-terminal allocation. As database does not require pseduo-terminal.

    The options are useful for forwarding ports and dynamic application-layer tunnel (need extra -D parameter). We cans specify these options on a per-host basis in configuration file (refer to -o of man ssh).

    In addition to LocalForward, there is also RemoteRorward. No matter of local or remote forward, VPS should have a public IP while the LocalForward or RemoteForward host (NOT the vps - uname@remote-host) usually don't have public IP or are blocked from each other.

    For instance, two host A and C are separated by their NAT networks. Host B are on the Internet with public IP which is accessible by A and B but not the reverse! How to achieve A and C's communication by SSH? Use remoteforward! A establishes remote forward to B. After that C connection to B will be forwarded to A.

    Aside, there is another port forwarding - dynamic port forwarding (details below).

    Refer to SSH 隧道与端口转发(v2ex 首发).

  2. scp can also make use of the config file.

    ~ $ scp bandwagon:~/Document/file.txt .
    ~ $ scp ~/Document/video.mp4 bandwagon:/Downloads
    
  3. man sshd_config. The server side config file /etc/ssh/sshd_config. Remote host - the host that connect to.

    You can disable account password login by PasswordAuthentication no. Then remote host can only be accessed by public-key pair. Be careful, if you lost the key pair, you probably could no longer login.

    You can also put ~/.ssh/authorized_keys somewhere, and set AuthorsizedKeysFile /path/to/authorized_keys.

SSH tunnel

We can use SSH tunnel to bypass GFW.

$ ssh -qfNT -D 127.0.0.1:12345 user@vps.ip

On slow network connections, try -C to compress data. However, -C will downgrade connection on fast network.

Refer to SSH隧道翻墙的原理和实现 and ssh隧道翻墙.

Make it simple

  1. By ssh-find-agent:
    1. ssh-find-agent
    2. ssh
  2. What we have:
    1. eval `ssh-agent -s`
    2. ssh-add
    3. ssh

Backup

Different from Gnupg key's binary format, OpenSSH key (~/.ssh/id_rsa and ~/.ssh/id_rsa.pub) is stored in armor format. To backup the key, just copying the private key.

Public key can be easily generated from private key by ssh-keygen -f ~/.ssh/id_rsa -y > ~/.ssh/id_rsa.pub.

Change passphrase

~ $ ssh-keygen -p -f ~/.ssh/id_rsa

Reference

  1. 使用密钥登录并禁止口令登录实践
  2. setup SSH with DSA public key authentication (password less login)
  3. Using ssh-agent with ssh
  4. ssh无密码登入设置
  5. How To Set Up SSH Keys
  6. how to ssh without a password.
  7. Understanding ssh-agent and ssh-add
  8. ssh-find-agent
  9. ssh config
  10. ssh tunnel
  11. magic of ssh
  12. 实战 SSH 端口转发