Cryptsus Blog rss-feed  |  We craft cyber security solutions.

How to secure your SSH server with
public key Ed25519 Elliptic Curve Cryptography

By: Jeroen van Kessel  |  July 3rd, 2019 | 10 min read

SSHD (Secure SHell Daemon) is the server-side program for secure remote connections cross-platform developed by none other than the OpenBSD team. However, not all SSH sessions are created equal.

The most important reason to choose public key authentication over password authentication is to defeat feasible brute-force attacks. Passwords should be avoided when possible because they are predictable and unavoidably weak. It is up to you to configure your SSH daemon in a secure manner. This blog post will explain how to master the SSH deamon, just as how Hercules tained the wild three-headed Kerberos beast.

Herclues tangles Kerberos, Gravure Sebald Beham 1540

Technical overview

SSH can generate DSA, RSA, ECDSA and Ed25519 key pairs. Let's go over these public-key algorithms:

DSA: This algorithm is deprecated due to very poor randomness. OpenSSH version 7.0 and newer even refuse DSA keys smaller than 1024-bits. DSA key pairs should not be used anymore.

RSA: This non-elliptic crypto algorithm which is based on prime numbers generates a relatively insecure key pair when you pick a key size below 2048-bits. The problem with RSA is its source of randomness. RSA is not vulnerable, but the source of entropy is the weakest link in the RSA algorithm. Many manufacturers are likely using the same source of randomness and perhaps even the same seeding. Furthermore, RSA will likely be the first to fall when quantum computations will get more mature.

ECDSA: The elliptic-curve (EC)DSA algorithm is supposed to help us combat these quantum computational attacks, while generating keys with significantly smaller key size without compromising the level of security. The size of the elliptic curve determines the difficulty to break the algorithm. However, secure implementations of the ECDSA curves are theoretically possible but very hard in practice. Furthermore, a weakness in RNG was publicly identified but still incorporated by NIST. We later learned from Snowden that the NSA had worked on the standardization process in order to become the sole editor of this Dual_EC_DRBG standard, and concluded that the Dual_EC_DRBG NIST standard did indeed contain a backdoor for the NSA. Why trust NIST curves when there is a more transparent way of doing crypto?

Ed25519: Long story short: it is not NIST and it is not NSA. The long story is that while NIST curves are advertised as being chosen verifiably at random, there is no explanation for the seeds used to generate these NIST curves. The process used to pick Ed25519 curves is fully documented and can be verified independently. This prevents a malicious party from manipulating the parameters. Furthermore, the Ed25519 algorithm is supposed to be resistant against side-channel attacks. Ed22519 key pairs have been supported since SSH version 6.5 (January 2014).

Generate an Ed25519 key pair

(Updated 2022): We are running Ubuntu 22.04 LTS together with OpenSSH 8.9p1 but the syntax in this post is the same for Debian based distro's:

$ lsb_release -d && ssh -V

Description:    Ubuntu 22.04.1 LTS
OpenSSH_8.9p1 Ubuntu-3, OpenSSL 3.0.2 15 Mar 2022

Lets generate a fresh pair of Ed25519 keys on the client machine, so not on the server-side. Use a passphrase to secure your private key in order to prevent unauthorized actions. Also enable full disk encryption on your systems when possible.

$ ssh-keygen -o -a 256 -t ed25519 -C "$(hostname)-$(date +'%d-%m-%Y')"

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/$USER/.ssh/id_ed25519): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/$USER/.ssh/id_ed25519.
Your public key has been saved in /home/$USER/.ssh/
The key fingerprint is:
SHA256:+zX9yMDeCyKoKSXT3QtfJyfsNHiZFxM020LiCbMERrE ubuntu-box1-01-07-2019
The key's randomart image is:
+--[ED25519 256]--+
|   .o.           |
|    o.           |
|   .E.           |
|      + . + o    |
|   . o *SB * o   |
|  o o +.=.&.*.   |
|   +  .oo*.X* .  |
|  .  o  oo.+ * o |
|   .o     . . =..|

The fingerprint is a short version of the server's public key. It is easier for a human to verify the fingerprint instead of the full key, while it is still hard to spoof another public key with the same fingerprint.

The following files will be generated from the above ssh-keygen command:

$ /home/$USER/.ssh/id_ed25519			#Private key Elliptic Curve Digital Signature Algorithm
$ /home/$USER/.ssh/		#Public key Elliptic Curve Digital Signature Algorithm
$ cat /home/$USER/.ssh/ 		#Later copy paste this public key to the server/traget

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBfJ2Qjt5GPi7DKRPGxJCkvk8xNsG9dA607tnWagOk2D ubuntu-box1-25-06-2019

Make the SSH key pair folder on the client-side only accessible for the local $USER. Note that root and your $USER username have different directories for storing generated SSH key pairs. Best practices dictate to leverage the $USER directory when generating your SSH key pair:

#Make the .ssh directory unreadable for other users and groups
$ chmod 700 ~/.ssh			
$ chmod 700 /home/$USER/.ssh	 		
#Make the private SSH key read only
$ chmod 400 /home/$USER/.ssh/id_ed25519 
$ chmod 400 ~/.ssh/id_ed25519 				 	    	
#Make the local $USER own the SSH key pair files
$ chown $USER:$USER ~/.ssh/id_ed25519*		 
$ chown $USER:$USER /home/$USER/.ssh/id_ed25519*

Server-side public key configuration

On the server side we set the correct permissions and copy the public key to the authorized_keys file:

$ rm /etc/ssh/ssh_host_* 					#Delete old SSH keys
$ rm ~/.ssh/id_* 						#Delete old SSH keys
$ sudo dpkg-reconfigure openssh-server				#Reset SSH config to defaults and generate new key files
$ rm /home/$USER/.ssh/id_*     					#Delete old SSH keys
$ vi /home/$USER/.ssh/authorized_keys				#paste public key here
$ cd /home/$USER/ && chmod g-w,o-w .ssh/			#The directory containing your .ssh directory must not be writeable by group or others
$ chmod 600 /home/$USER/.ssh/authorized_keys			#change permissions to r+w only for user
$ service sshd restart						#restart and reload keys into the SSH deamon

Let's test the authentication from the client to the server:

$ ssh USER@ssh-server-ip -i /home/$USER/.ssh/id_ed25519 -o PasswordAuthentication=no -vv

SSHD configuration

On the server side we harden the SSHD configuration file. Edit the following variables to your location environment:

ListenAddress: Change this to your IP-adres on which the SSH daemon should be listening.
AllowUsers: Change this to your $USER. Note: this is not root.

$ vi /etc/ssh/sshd_config

Protocol 2                                  	                #Protocol 1 is fundamentally broken
StrictModes yes                                                 #Protects from misconfiguration

#ListenAddress [ip-here]                                        #Listening address
Port 22                                                         #Listening port. Normal 22

AuthenticationMethods publickey                                 #Only public key authentication allowed
PubkeyAuthentication yes                                        #Allow public key authentication
HostKey /etc/ssh/ssh_host_ed25519_key                           #Only allow ECDSA pubic key authentication
HostKeyAlgorithms,ssh-ed25519  #Host keys the client should accepts
KexAlgorithms curve25519-sha256                                 #Specifies the available KEX (Key Exchange) algorithms
Ciphers,    #Specifies the ciphers allowed
MACs                		#Specifies the available MAC alg.
#Only allow incoming ECDSA and ed25519 sessions:
HostbasedAcceptedKeyTypes ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519
#CASignatureAlgorithms ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519

PermitRootLogin no                                              #Disable root login
#AllowUsers [username]                                          #Authorized SSH users are inside the admin group
MaxAuthTries 5                                                  #Maximum allowed authentication attempts
MaxSessions 2                                                   #Maximum allowed sessions by the user

PasswordAuthentication no                                       #No username password authentication
PermitEmptyPasswords no                                         #No empty password authentcation allowed
IgnoreRhosts yes                                                #Dont read users rhost files
HostbasedAuthentication no                                      #Disable host-based authentication
ChallengeResponseAuthentication no                              #Unused authentication scheme
X11Forwarding no                                                #Disable X11 forwarding

LogLevel VERBOSE                                                #Fingerprint details of failed login attempts
SyslogFacility AUTH                                             #Logging authentication and authorization related commands
UseDNS no                                                       #Client from a location without proper DNS generate a warning in the logs

PermitTunnel no                                                 #Only SSH connection and nothing else
AllowTcpForwarding no                                           #Disablow tunneling out via SSH
AllowStreamLocalForwarding no                                   #Disablow tunneling out via SSH
GatewayPorts no                                                 #Disablow tunneling out via SSH
AllowAgentForwarding no                                         #Do not allow agent forwarding

Banner /etc/                                           #Show legal login banner
PrintLastLog yes                                                #Show last login

ClientAliveInterval 900                                         #Client timeout (15 minutes)
ClientAliveCountMax 0                                           #This way enforces timeouts on the server side
LoginGraceTime 30                                               #Authenticatin must happen within 30 seconds
MaxStartups 2                                                   #Max concurrent SSH sessions
TCPKeepAlive yes                                                #Do not use TCP keep-alive

AcceptEnv LANG LC_*                                             #Allow client to pass locale environment variables
Subsystem sftp /usr/lib/ssh/sftp-server -f AUTHPRIV -l INFO     #Enable sFTP subsystem over SSH

Note: remove the and values from the PubkeyAcceptedKeyTypes setting if you run OpenSSH 8.0 or earlier. sk-based keys are only support in OpenSSH version 8.1 or higher.

Lets test the modified /etc/ssh/sshd_config file and load the changes into the SSH deamon:

$ sudo sshd -T
$ service sshd restart

You should be able to authenticate and connect again from your client:

$ ssh USER@ssh-server-ip -i /home/$USER/.ssh/id_ed25519 -o PasswordAuthentication=no -vv

Legal banner

The following SSH banner is created for a warning for legal and compliance purposes:

$ vi /etc/

                          NOTICE TO ANY USERS
This computer system is the private property of its corporate owner.
It is for authorized use only. Users (authorized or unauthorized)
have no explicit or implicit expectation of privacy.
Any or all uses of this system and all files on this system may be
intercepted, monitored, recorded, copied, audited, inspected, and
disclosed to your employer, to authorized site, government, and law
enforcement personnel, as well as authorized officials of government
agencies, both domestic and foreign.
By using this system, the user consents to such interception, monitoring,
recording, copying, auditing, inspection, and disclosure at the
discretion of such personnel or officials.  Unauthorized or improper use
of this system may result in civil and criminal penalties and
administrative or disciplinary action, as appropriate. By continuing to
use this system you indicate your awareness of and consent to these terms
and conditions of use. LOG OFF IMMEDIATELY if you do not agree to the
conditions stated in this warning and contact us.

Rate-limit SSH connections

Configure your host-based firewall on your server to take further take control of your SSH sessions as an alternative or addition to Fail2ban. Why install another package while iptables is already build-in:)

#SERVER_IP_MGMT = Listening IP in your /etc/ssh/sshd_config
#NIC_MGMT = (v)NIC of the server for listening on the ssh server

#Allow incoming SSH connections
iptables -A INPUT -i $NIC_MGMT -p tcp -s 0/0 -d $SERVER_IP_MGMT --sport 32768:65535 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o $NIC_MGMT -p tcp -s $SERVER_IP_MGMT -d 0/0 --sport 22 --dport 32768:65535 -m state --state ESTABLISHED -j ACCEPT

#Create new state for port 22 to combat brute-force attacks (new rule)
iptables -I INPUT -i $NIC_MGMT -p tcp -s 0/0 -d $SERVER_IP_MGMT --sport 32768:65535 --dport 22 -m state --state NEW -m recent --set
#Rule apply drop connection if there are more then 50 failed SSH login attempts connections every 3600 seconds (1 hour)
iptables -I INPUT -i $NIC_MGMT -p tcp -s 0/0 -d $SERVER_IP_MGMT --sport 32768:65535 --dport 22 -m state --state NEW -m recent --update --seconds 3605 --hitcount 50 -j DROP

By default the SSHD listens on all interfaces which is denoted as Make sure the SSHD is listening on a seperate magemenet NIC:

$ netstat -anltpu

Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 *               LISTEN      289/systemd-resolve 
tcp        0      0*               LISTEN      3359/sshd 

Troubleshoot any authentication errors in the auth.log file:

$ cat /var/log/auth.log | grep sshd*

2FA authentication

Lastly, use U2F hardware keys for two factor authentication to increase your level of security. You can read here how to configure YubiKeys with OpenSSH.

Discussion and questions