SSH Security
Everyone who uses Linux operating system today knows about Secure Server Shell. Although it is named secure server shell, is it really Secure? The answer really depends on the configuration. Let us go through the steps involved in installing and configuring SSH with focus on making the system more secure and following good practises. In general, following are the good practises:
- Use any port between 1024 and 65536 to listen for SSH connection
- Disable root login
- Allow users with a predefined list
- Enable SSH access only through SSH keys and only use SSH keys
- Use message to display legal notice during login
- Monitor last login and last bad logins to track if some one tried to login
- Block ICMP request
Installing SSH
In ubuntu operating system below is the command to install SSH service:
Once the installating is complete, the service starts listening on port 22. This can be verified by running port scan using nmap linux tool.
Good practise configuration
# Authentication:
LoginGraceTime 30
PermitRootLogin no
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords no
ChallengeResponseAuthentication no
PasswordAuthentication yes # Untill SSH keys are setup
Banner /etc/issue
UsePAM yes
Now create a file /etc/issue with the legal message you want to display before login.
Then restart the ssh service to get the new configurations in effect: sudo services ssh restart
Setting up SSH Keys
There can be a pair of keys generated and used for authentication over SSH. One key is private while the other is public. The public key is pushed to server while the private key is kept somewhere safe on the client computer. In order to generate such a key, the command is:
Then enter he passphrase and the file names. The passphrase is used to store the private file with some encryption on the local computer.
Once the files are generated, let us now push the public key to the server in which we want to login:
The above command adds the public key to the server's .ssh/authorized_keys file
Then to login, simply enter
Once you are able to login successfully, turn off the PasswordAuthentication to no in the config file described above.
Monitoring Last Logins
Last login and last back login attempt can be checked using the following command:
sudo lastb
ICMP Request
While ICMP request can be used to probe if the system is active, someone can also use to do the same. Hence a right way to ensure a server is up is by installing a monitoring service that send heartbeats to some external service or system. I prefer to block all ICMP requests using IP tables and below are the commands to do so:
iptables -A OUTPUT -p icmp -o eth0 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-reply -s 0/0 -i eth0 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type destination-unreachable -s 0/0 -i eth0 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type time-exceeded -s 0/0 -i eth0 -j ACCEPT
iptables -A INPUT -p icmp -i eth0 -j DROP Troubleshooting an error
Sometimes, we get an error although we have followed all the steps described above to add the ssh key to remote server:
# Permission denied (publickey).
One of the possible reason is that SSH keys that are generated are not added to ssh-agent, to fix this follow the below steps:
eval "$(ssh-agent -s)"
# Agent pid 59566
ssh-add ~/.ssh/<my_private_key>
A Deeper Dive: Keys, the Agent, Fingerprints, Git and Signing
The section above covered the basics of generating a key pair and copying it to a server. The rest of this article goes much deeper: how keys are generated and chosen, how the SSH agent stores and serves them, how to list, read, add and remove keys, how to verify a key by its SHA256 fingerprint, how to find out exactly which key Git uses when you push or pull, and finally how the signing that underpins all of this actually works.
Generating SSH Keys in Depth
The classic ssh-keygen -t rsa still works, but the algorithm you choose matters. Modern OpenSSH supports several key types and the recommendation today is Ed25519 — small, fast and very secure. Reach for RSA (at least 4096 bits) only when you must talk to an old server that does not understand Ed25519.
ssh-keygen -t ed25519 -C "you@example.com"
# RSA 4096 for older servers that lack Ed25519
ssh-keygen -t rsa -b 4096 -C "you@example.com"
# ECDSA, an alternative elliptic-curve option
ssh-keygen -t ecdsa -b 521 -C "you@example.com"
A breakdown of the flags:
- -t — the key type / algorithm (ed25519, rsa, ecdsa).
- -b — the number of bits (only meaningful for RSA and ECDSA; Ed25519 has a fixed size).
- -C — a comment, usually your email or user@machine, written into the public key so you can recognise it later.
- -f — the output filename, e.g. -f ~/.ssh/id_ed25519_github, which is how you keep separate keys for separate services.
- -N — the passphrase, supplied non-interactively (use an empty string only for unattended automation keys).
ssh-keygen writes two files: the private key (for example ~/.ssh/id_ed25519) and the public key with a .pub suffix (~/.ssh/id_ed25519.pub). Only the public key ever leaves your machine. The private key must stay private, and permissions matter — SSH refuses to use a private key that other users can read:
chmod 600 ~/.ssh/id_ed25519 # private key: owner read/write only
chmod 644 ~/.ssh/id_ed25519.pub # public key may be world-readable
The passphrase encrypts the private key file, so even if the file is stolen it is useless without the passphrase. The cost is that you must type it every time you use the key — which is exactly the problem the SSH agent solves. You can also change a passphrase, or re-derive a lost public key, without making a new pair:
ssh-keygen -p -f ~/.ssh/id_ed25519
# Re-derive the public key from a private key
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
The SSH Agent: List, Read, Add and Remove Keys
The ssh-agent is a small background program that holds your decrypted private keys in memory. You unlock a key once (by typing its passphrase when you add it) and the agent then performs all signing operations on your behalf, so you are not prompted again for that session. Crucially the private key never leaves the agent — clients ask the agent to sign challenges, they do not read the key out.
Start an agent (most desktop sessions already run one):
# Agent pid 59566
The agent advertises itself through two environment variables, SSH_AUTH_SOCK (the path to its unix socket) and SSH_AGENT_PID. Every ssh, git or scp command finds the running agent through SSH_AUTH_SOCK.
Add a key (you are asked for the passphrase once):
# Add with a 1-hour lifetime, then auto-removed
ssh-add -t 3600 ~/.ssh/id_ed25519
# Add every default key found in ~/.ssh
ssh-add
# macOS: also store the passphrase in the Keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
List and read the keys currently held. There are two views — fingerprints, and the full public keys:
ssh-add -L # read the full public keys the agent is holding
ssh-add -l prints one line per key (bit length, fingerprint, comment). ssh-add -L prints each complete public key in authorized_keys format — this is the "read" operation, handy when you want to copy a held key straight onto a server. If the agent is empty you will see "The agent has no identities."
Remove keys from the agent:
ssh-add -D # remove ALL keys from the agent
ssh-add -x # lock the agent with a password
ssh-add -X # unlock the agent
Tracking Which Keys the Agent Is Using
When you hold several keys it is easy to lose track of which one is actually offered to a server. The agent presents its keys in order until one is accepted, so the key you think is being used may not be the one that authenticates you.
First, see what the agent holds and with which fingerprints:
Then watch a real connection with verbose output. Lines beginning "Offering public key" show each key the agent presents; "Server accepts key" shows the one that worked:
# ...
# debug1: Offering public key: /home/me/.ssh/id_ed25519 ED25519 SHA256:abc...
# debug1: Server accepts key: /home/me/.ssh/id_ed25519 ED25519 SHA256:abc...
# debug1: Authentication succeeded (publickey).
To force a single, specific key and stop the agent trying everything, combine IdentityFile with IdentitiesOnly in ~/.ssh/config:
HostName host.example.com
User me
IdentityFile ~/.ssh/id_ed25519_myserver
IdentitiesOnly yes
Checking the SHA256 Fingerprint of a Key
A fingerprint is a short hash of a public key. Modern OpenSSH shows fingerprints as SHA256 (base64) by default; older tools showed an MD5 hex form. Fingerprints let you confirm that a key on a server, in the agent, or shown by a service such as GitHub, really is the key you think it is.
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# Force the older MD5 form
ssh-keygen -lf ~/.ssh/id_ed25519.pub -E md5
# Fingerprints of every key currently in the agent
ssh-add -l -E sha256
# Fingerprint of a key piped on stdin
ssh-keygen -lf - < ~/.ssh/id_ed25519.pub
A sample line from ssh-keygen -lf reads:
The fields are the key size (256), the SHA256 fingerprint, the comment, and the key type. When a service shows you a fingerprint — GitHub, for instance, lists the fingerprint of each account key — compare it character-for-character with this output to be certain you are looking at the same key.
Tracing the SSH Key Git Uses During Push and Pull
When a remote uses an SSH URL such as git@github.com:user/repo.git, Git shells out to ssh to make the connection, and SSH then selects a key exactly as described above. To see which key Git is using, make SSH verbose for that one command:
GIT_SSH_COMMAND="ssh -v" git push
# Test the GitHub connection directly (reports the account, never opens a shell)
ssh -vT git@github.com
In the output, look again for "Offering public key" and "Server accepts key" — that accepted key is the identity Git authenticated with. On GitHub the friendly line "Hi username! You've successfully authenticated..." also confirms which account the key maps to.
To pin a particular key to a host so push/pull is deterministic, add it to ~/.ssh/config:
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Or set it per-repository with Git's own option, which avoids touching the SSH config at all:
How Signing Works
Everything above rests on public-key (asymmetric) cryptography. A key pair has a mathematical relationship: anything signed with the private key can be verified with the matching public key, yet the private key cannot be derived from the public key. Signing never reveals the private key — it only produces a signature that proves the signer holds it.
Signing during SSH login (authentication)
Public-key authentication is a challenge-response handshake, not a transmission of the key:
- You connect, and the server looks up your public key in its ~/.ssh/authorized_keys.
- Client and server derive a unique session identifier from the key exchange, and the client signs a value bound to that session with your private key (the agent does this signing when the key is loaded).
- The server verifies the signature with the public key it has on file. A valid signature proves you hold the private key, so you are let in — the private key itself is never sent over the wire.
Because the signed value is tied to that one session, a captured signature cannot be replayed against a different connection.
Signing Git commits and tags with an SSH key
The same key pair can also sign your commits, so others can verify a commit genuinely came from you. Since OpenSSH 8.0 and Git 2.34 you can sign with an SSH key directly, with no GPG required:
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true # sign every commit
git commit -S -m "message" # sign one commit explicitly
git tag -s v1.0 -m "release" # sign a tag
To verify signatures locally, list the identities you trust in an allowed signers file and point Git at it:
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers
git log --show-signature # shows "Good signature" when verified
git verify-commit HEAD
When you commit, Git hashes the commit content and signs that hash with your private key; the signature travels with the commit. Anyone holding your public key — through the allowed-signers file, or via GitHub which then shows a green "Verified" badge — can recompute the hash and check the signature, confirming both who made the commit and that it has not been altered since.
Comments (0)
Be the first to comment.