Linux and developer tools
Bash Basics for Security Work
Learn pipes, redirection, variables, loops, exit codes and set -euo pipefail, then use them to count failed logins and hash a folder.
How SSH host and user keys work, why Ed25519 with a passphrase is the default to use, and how to set up and harden a key login on a lab VM you own.
SSH (Secure Shell) is the protocol most people use to log into Linux servers, lab virtual machines and Git hosts. It opens a shell over an encrypted channel, and the same channel can copy files or forward ports. It has three jobs. It encrypts the traffic so nobody on the network path can read it. It proves to the server who you are. It proves to you who the server is.
Keys do the second job without sending a password across the network. A password can be guessed or phished. A key pair works differently. The server stores only the public half. To log in, your client signs a challenge with the private half, and the server checks the signature. The private key never leaves your machine.
SSH does not protect you in some cases, and those cases matter as much as the protocol itself. If your own computer is compromised, malware running as your user can read the private key file or ask your agent to sign things. If the private key is stolen and its passphrase is weak, an attacker can try to guess it offline. If you accept a server's identity without checking it, you may be talking to someone who is not the server at all.
SSH uses two different kinds of keys, and people mix them up often.
Host keys identify the server. Each SSH server has one or more, usually stored as /etc/ssh/ssh_host_*_key. The private part stays on the server. The public part is presented to every client that connects. The first time your client sees it, the client saves it in known_hosts. On later connections, a changed host key triggers a warning.
User keys identify you. You generate the pair on your own machine. The private key stays with you. The public key goes on the server, in ~/.ssh/authorized_keys for the account you want to use.
The difference has practical consequences. Copying your private key to a server gives that server, and anyone who can read it, the material needed to act as you everywhere the key is trusted. Ignoring a host key warning means you may send your password or your commands to an impostor.
OpenSSH supports several key types. RSA still works, but new RSA keys should be at least 3072 bits, and the public key lines get long. Ed25519 keys are short, fast to verify and standardized for SSH in RFC 8709. OpenSSH 9.5 and later make Ed25519 the default for ssh-keygen. Ed25519 is the right default for a lab key unless a system you need to reach only accepts an older type.
A passphrase encrypts the private key file on disk. Without one, anyone who copies the file can use the key. With one, they need the file and the passphrase. That protects against the most common accidents: a backup sent to the wrong place, a key left in a shared folder, a file synced to a cloud drive by mistake. A passphrase does not help once the key is loaded into an agent and malware runs as you. Keep the passphrase long, store it in a password manager, and keep a backup of the key file somewhere safe.
RFC 4253 describes the transport layer. At a high level, a connection goes through these steps:
known_hosts. If the host is new, the client shows the fingerprint and asks you to confirm it.authorized_keys, it sends a challenge. Your client signs the challenge with the private key, and the server checks the signature.Notice where trust comes from. Step 3 is about the server. Step 4 is about you. A fingerprint check that you skip in step 3 leaves the rest of the handshake working for whoever answered the call.
A fingerprint is a short hash of a public key. OpenSSH shows SHA256 fingerprints by default. Two people can compare a fingerprint to confirm they hold the same public key, without reading the whole key aloud.
The commands below were run on a Windows 11 machine with OpenSSH 9.9p2 (ssh -V), in Git Bash. The key was a scratch copy created for this observation, with an empty passphrase, so the output could be shown here. Your real key will have a different fingerprint.
ssh-keygen -t ed25519 -C 'lab-key' -f ./lab_ed25519 -N ''
ssh-keygen -lf ./lab_ed25519.pubObserved output:
256 SHA256:2BAyZTrHADdiG1FNwfCwb/fC5vSVZPwfGnZjRaaExc4 lab-key (ED25519)ssh-keygen -lvf prints the same fingerprint with a random-art picture. The picture is easier to notice when something changes, but the fingerprint text is what you compare.
You need a virtual machine or cloud instance that you own and control, with an OpenSSH server running and an account that can use sudo. A local VM in VirtualBox or Hyper-V works. So does a small cloud instance you pay for and can delete. Do not run these steps against a machine that belongs to someone else.
ssh-keygen -t ed25519 -C 'lab-key' -f ~/.ssh/lab_ed25519When prompted, enter a long passphrase. The command creates two files: lab_ed25519 (private) and lab_ed25519.pub (public). Only the .pub file is meant to leave your machine.
ssh-keygen -lf ~/.ssh/lab_ed25519.pubWrite this fingerprint down, or note the first few characters. You will compare it again later.
chmod 600 ~/.ssh/lab_ed25519On Linux and macOS, ssh refuses a private key that other users can read. On Windows, OpenSSH checks file ACLs rather than the Unix mode bits. On this Windows machine, chmod 600 ran without an error, but ls -l still showed -rw-r--r-- afterward. Do not assume the mode bits changed there. If ssh complains about permissions on Windows, inspect the file with icacls and remove access for other accounts.
ssh-copy-id -i ~/.ssh/lab_ed25519.pub youruser@your-lab-vmThis is the one time you type the account password on purpose. The command appends the public key to ~/.ssh/authorized_keys on the VM.
The first connection to the VM also shows you a host key fingerprint. Compare it with the real one. On the VM, run:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pubIf the two fingerprints match, type yes. If they do not match, stop. Find out why before you continue. A mismatch can mean you reinstalled the VM, or that someone sits between you and it.
This step was not run here, because no VM was available on this machine. Commands in steps 4 to 6 are shown without output for that reason. You can check each one on your own VM.
ssh -i ~/.ssh/lab_ed25519 youruser@your-lab-vmYour client asks for the key passphrase, not the account password. If you get a shell, the key works. Keep this session open for the next step.
On the VM:
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sshd -t && sudo systemctl restart sshThe sed line replaces the PasswordAuthentication line, whether or not it is commented out. sshd -t checks the configuration syntax and stops the chain if it fails. systemctl restart ssh applies the change. On Fedora and other RHEL-family systems the service is called sshd, so use that name.
Three things commonly go wrong here:
/etc/ssh/sshd_config includes files from /etc/ssh/sshd_config.d/, and the first value read for a keyword wins. Check the value that sshd actually uses:sudo sshd -T | grep -i '^passwordauthentication'After the change, the output should be passwordauthentication no. If it still says yes, find the file that sets it and fix it there.
sed only edits a line that exists. If there is no PasswordAuthentication line, the default applies, and the sshd_config(5) man page says that default is yes. Add the line yourself:echo 'PasswordAuthentication no' | sudo tee -a /etc/ssh/sshd_configThen test from your own machine that password login is refused:
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password youruser@your-lab-vmThe connection should fail with a permission denied message. Run this check yourself on the VM you own. It was not run on this machine.
Typing the passphrase on every connection gets tiring. ssh-agent keeps the decrypted key in memory for your session, so you type the passphrase once.
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/lab_ed25519
ssh-add -lssh-add -l lists the fingerprints the agent holds. On this Windows machine, ssh-add -l ran without an agent running and printed Could not open a connection to your authentication agent. with exit code 2. Windows uses the OpenSSH Authentication Agent service for this. Start that service from the Services console or from an administrator PowerShell. That is a change to your system, so do it yourself and only if you want the agent running.
An agent is convenient, and it is also a target. Anyone who can talk to the agent socket while you are logged in can ask it to sign things. For that reason, avoid agent forwarding (ssh -A) to hosts you do not fully trust. A root user on the remote host can use your forwarded agent. If you need to reach a private host through a bastion, use a jump host instead:
ssh -J youruser@bastion youruser@private-vmThe client stores host keys in ~/.ssh/known_hosts, one line per host and key type. Lines can be hashed if you set HashKnownHosts yes, which hides host names from someone who reads the file.
To look up a host:
ssh-keygen -F your-lab-vmTo remove an entry after you rebuild a VM and get a new host key:
ssh-keygen -R your-lab-vmDo this only when you know why the key changed. A changed host key is a warning, not a nuisance. Removing the old entry without checking the new fingerprint teaches you to ignore the next warning, and that next one may be real.
.pub file goes to servers, to Git hosts or to other people.authorized_keys..pub line in authorized_keys.~/.ssh should be 700 and authorized_keys should be 600. Looser modes can make sshd ignore the key. Check your sshd logs when a key login fails.lab_ed25519 for the lab and another key for GitHub.yes.ssh-copy-id twice is harmless, but appending a key by hand several times makes the file harder to audit. Remove keys you no longer use.Once the lab works, a few steps make daily use safer and tidier.
~/.ssh/config. Give each host an alias, set the IdentityFile, and add IdentitiesOnly yes so the client offers only the key you chose. This stops the client from trying every key in your agent against a server.ssh-keygen -t ed25519-sk. The private part then cannot be copied off the device. Check your OpenSSH version and your key's support before you rely on this.ssh-keygen and sshd_config man pages describe every option you will meet. RFC 4253 explains the transport layer, and RFC 8709 covers Ed25519 in SSH.If file modes and ownership are still new to you, read the earlier article on Linux files and permissions first. The next article covers isolated Docker networks for labs, which you can use to keep lab traffic away from your everyday network.
ssh-keygen(1): https://man.openbsd.org/ssh-keygensshd_config(5): https://man.openbsd.org/sshd_configLinux and developer tools
Learn pipes, redirection, variables, loops, exit codes and set -euo pipefail, then use them to count failed logins and hash a folder.
Linux and developer tools
Linux owners, groups, rwx bits, chmod, umask and setuid, with a lab that tests access as a second user and finds risky file modes.
Linux and developer tools
GitHub explained for beginners: what Git and GitHub do, the terms you need, a local Git practice run, and how to check a project before you run it.