{"slug": "ssh-actually-explained-handshakes-keys-and-the-tunnel-trick", "title": "SSH, Actually Explained: Handshakes, Keys, and the Tunnel Trick", "summary": "Maneshwar, a developer building LiveReview, explains the inner workings of SSH, detailing how Diffie-Hellman key exchange establishes a secure session and why encryption precedes authentication. The article highlights SSH's use of asymmetric crypto for the handshake and symmetric crypto for the session, emphasizing forward secrecy.", "body_md": "*Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. [Star us](https://github.com/HexmosTech/LiveReview/) to help devs discover the project, give it a try, and share your feedback to help improve the product.*\n\nYou type `ssh ubuntu@1.2.3.4`, you get a prompt, and a server in another continent starts obeying your keyboard.\n\nMost of us learned that incantation on day one and never looked under it again.\n\nWhich is fair. It works. It has worked for thirty years. Nothing about it demands your attention.\n\nBut SSH is doing something genuinely clever in the half second before that prompt appears, and once you have seen it, a whole category of confusing errors stops being confusing.\n\nSo let's open it up.\n\nBefore SSH there was telnet, and telnet had exactly one flaw.\n\nIt sent everything in plain text. Your username, your password, every command, every byte of output.\n\nOn a shared network that is not a subtle problem. Anyone sitting between you and the server was reading your session like a group chat they had been added to.\n\nTelnet is a postcard. SSH is a locked briefcase.\n\nThe postman is the same postman in both cases. That is the entire point.\n\nTelnet is still installed on your machine, by the way. It survives as a debugging tool, because `telnet host 443` is a quick way to ask \"is this port even open\". \n\nAs a login protocol it is dead, and it deserved it.\n\nHere is the problem SSH has to solve in its first few milliseconds.\n\nTwo machines that have never met need to agree on a secret encryption key, while shouting at each other across a network where everything they say is being recorded.\n\nThat sounds impossible. It is not, and the reason is the neatest trick in applied cryptography.\n\nSSH uses two kinds of encryption, not one, and people conflate them constantly.\n\n**Asymmetric crypto sets up the conversation.** It is slow, it involves public and private key pairs, and it is used only for the opening handshake.\n\n**Symmetric crypto carries the conversation.** One shared key, fast, and it does all the actual work of encrypting your keystrokes.\n\nThe handshake exists purely to get both sides holding the same symmetric key.\n\nThe move is called a [Diffie-Hellman key exchange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange), and it goes like this.\n\nEach side generates a private value and derives a public value from it.\n\nThey swap public values in the clear, where anyone can see them.\n\nThen each side combines its own private value with the other side's public value, and the maths works out such that both arrive at the same number.\n\nAn observer who saw both public values cannot get there. They are missing either private half, and going backwards from a public value to a private one is the hard problem the whole thing rests on.\n\nSo the session key is never transmitted. It is independently derived, twice, in two different countries.\n\nThat key is also **ephemeral**. It exists for this session and is thrown away when you disconnect. Record the traffic today, steal the server's private key next year, and you still cannot decrypt what you captured. That property is called [forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy) and it is worth knowing the name of.\n\nWant to actually watch this happen? SSH will narrate it.\n\n``` bash\n$ ssh -vv ubuntu@example.com\ndebug1: SSH2_MSG_KEXINIT sent\ndebug1: kex: algorithm: curve25519-sha256\ndebug1: kex: host key algorithm: ssh-ed25519\ndebug1: Server host key: ssh-ed25519 SHA256:qN8Xb2...\ndebug1: SSH2_MSG_NEWKEYS sent\ndebug1: Authenticating to example.com:22 as 'ubuntu'\n```\n\n`NEWKEYS` is the moment the tunnel goes live. Notice that authentication happens on the line *after* it.\n\nThat ordering matters more than it looks.\n\nThe encryption is set up first, and only then does SSH ask who you are.\n\nYour password, if you use one, is already inside the tunnel by the time you type it.\n\nThere is a gap in the story above.\n\nKey exchange gives you a secure channel to *somebody*. It does not prove that somebody is the server you meant to reach.\n\nThat is what the server's host key is for, and it is why the first connection asks you this:\n\n```\nThe authenticity of host 'example.com' can't be established.\nED25519 key fingerprint is SHA256:qN8Xb2fV+3Kx9...\nAre you sure you want to continue connecting (yes/no)?\n```\n\nYou have typed `yes` there a thousand times without reading it. I have too.\n\nWhat you are being asked is whether that fingerprint belongs to the machine you think you are talking to. SSH cannot know. There is no certificate authority here, unlike the web.\n\nSo it does the next best thing. It writes the fingerprint into `~/.ssh/known_hosts` and screams if it ever changes.\n\nThat scream is the `REMOTE HOST IDENTIFICATION HAS CHANGED` block, and it is not being dramatic for fun.\n\nEither the server was genuinely rebuilt, or someone is sitting in the middle pretending to be it.\n\nNine times out of ten it is a rebuilt server. The tenth time is the reason for the warning.\n\nOnce the tunnel is up, SSH still needs to know who you are.\n\nYou can use a password. You should not.\n\nThe better way is a key pair you generate once, on your own machine.\n\n```\nssh-keygen -t ed25519 -C \"laptop\"\n# ~/.ssh/id_ed25519       <- private. never leaves this machine.\n# ~/.ssh/id_ed25519.pub   <- public. paste it anywhere.\n\nssh-copy-id ubuntu@example.com   # appends the .pub to the server's authorized_keys\n```\n\nUse `ed25519`, not RSA. It is smaller, faster, and has fewer ways to configure it badly. RSA is still fine at 4096 bits, but there is no reason to pick it for a new key.\n\nThe login is a challenge and response.\n\nYou tell the server which public key you are claiming. The server checks `authorized_keys`, finds it, and sends back a random chunk of data.\n\nYour client signs that data with the private key. The server verifies the signature against the public key it already had.\n\nNothing secret ever crosses the network. Not on the first login, not on the thousandth.\n\nCompare that to a password, which crosses the wire on every single login, lives in someone's head, is probably reused, and is short enough to guess. A 256-bit key is not short enough to guess. That is not a marginal improvement, it is a different category.\n\nThis is also why keys are the only sane option for automation. A CI pipeline cannot type a password, but it can hold a key.\n\nTwo places where this is already in your muscle memory.\n\n**EC2.** When you launch an instance, AWS puts your public key into the image and hands you the `.pem`, which is the private half. That is why `ssh -i mykey.pem ec2-user@1.2.3.4` works with no password. AWS never had your private key and cannot recover it for you, which is [stated plainly in their docs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html).\n\n**GitHub.** `git push` over SSH is the exact same challenge and response. You put your public key in your account settings, and every push signs a challenge. GitHub [removed password authentication for Git entirely in 2021](https://github.blog/security/application-security/removing-support-for-password-authentication/), so it is keys or a token, nothing else.\n\nA private key in a git repo is a server someone else owns now. GitHub scans public repos for exactly this, and bots scan them faster.\n\n`chmod 600` your keys, keep them out of repos, and put a passphrase on them so a stolen laptop is not a stolen server.\n\nHere is the SSH feature that feels like cheating the first time it works.\n\nYou need to reach a MySQL database in a private subnet. It has no public IP. It has no route from the internet, on purpose, because it is a database.\n\nBut there is a **bastion host** in front of that network. One small public machine whose entire job is to be the single door, with port 22 open and nothing else.\n\nYou can SSH into the bastion. So you can reach the database.\n\n```\nssh -L 3306:db.internal:3306 ec2-user@bastion.example.com\n```\n\nRead the `-L` argument as three parts: the local port you want to open, then the host and port to reach *from the bastion's point of view*.\n\nNow `mysql -h 127.0.0.1 -P 3306` on your laptop talks to that private database.\n\nYour MySQL client believes it is connected to something local. It has no idea a tunnel exists. Every byte goes through the encrypted SSH session and comes out on the far side of the firewall.\n\nThe same trick works for an internal dashboard, a Redis instance, an admin panel that should never be public, or a staging service someone forgot to expose.\n\nTwo directions worth knowing, since the flags are easy to mix up:\n\n`-L` brings a `-R` pushes a If you find yourself typing long tunnel commands daily, put them in `~/.ssh/config` and forget them:\n\n```\nHost db-tunnel\n    HostName bastion.example.com\n    User ec2-user\n    IdentityFile ~/.ssh/prod.pem\n    LocalForward 3306 db.internal:3306\n```\n\nThen it is just `ssh db-tunnel`. Your fingers will thank you.\n\n`Permission denied (publickey)` is the least helpful error message in networking, because it covers about six different mistakes.\n\nHere is the order I actually check them in:\n\n``` php\nflowchart TD\n    A[Permission denied publickey] --> B{Did you pass the right key?}\n    B -->|No| K[ssh -i mykey.pem or add to ~/.ssh/config]\n    B -->|Yes| C{chmod 600 on the key file?}\n    C -->|No| P[Fix permissions, SSH ignores world-readable keys]\n    C -->|Yes| D{Public key in authorized_keys on the server?}\n    D -->|No| U[ssh-copy-id, or paste it in]\n    D -->|Yes| E{Right username for the image?}\n    E -->|No| N[ec2-user, ubuntu, admin, not root]\n    E -->|Yes| F{Home dir or .ssh perms too open?}\n    F -->|Yes| H[chmod 700 ~/.ssh, 755 the home dir]\n    F -->|No| L[Read the server log: journalctl -u sshd]\n\n    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a\n    classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a\n    classDef fix fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a\n    classDef last fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a\n\n    class B,C,D,E,F decision\n    class A start\n    class K,P,U,N,H fix\n    class L last\n```\n\nThe permissions one catches everybody at least once. SSH will silently ignore a private key that other users can read, which is protective and infuriating in equal measure.\n\nThe wrong-username one is the other classic. Amazon Linux wants `ec2-user`, Ubuntu images want `ubuntu`, and almost nothing wants `root` anymore.\n\nWhen you have exhausted the client side, `ssh -vv` tells you which keys were offered, and `journalctl -u sshd` on the server tells you why each one was rejected. Between those two you will find it.\n\nA short list, because most SSH hardening advice is longer than it needs to be.\n\nTurn off password authentication once your key works. `PasswordAuthentication no` in `sshd_config` deletes the entire brute-force attack surface in one line.\n\nTurn off direct root login. `PermitRootLogin no`, then `sudo` from a normal user, so the audit log has a name in it.\n\nDo not bother moving off port 22. It stops log noise from untargeted scanners, and nothing else. A real attacker runs a port scan.\n\nTest config changes in a second terminal while your first session is still open. Locking yourself out of a box by reloading a broken `sshd_config` is a rite of passage best skipped.\n\nSSH is not one idea, it is three stacked on each other.\n\n**A key exchange** that lets two strangers agree on a secret in public, then hands the session to fast symmetric encryption.\n\n**An authentication step** that proves who you are by signing a challenge, so nothing worth stealing ever crosses the network.\n\n**A tunnel** that, once you have all that, will carry whatever other protocol you point at it.\n\nThat third layer is the one most people never touch, and it is the one that turns SSH from a login tool into a general-purpose way to reach things that are deliberately unreachable.\n\nGo generate an ed25519 key, delete a password login, and forward a port. It is a good afternoon.\n\nYour team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.\n\nI'm building **LiveReview**, a blast-radius aware AI code review built for your business-critical systems.\n\nInstead of presenting every diff with equal emphasis, **LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.**\n\nSpend code review effort where business risk is highest — not spread evenly across every diff.\n\n⭐ Star it on GitHub:\n\nLiveReview is an AI code reviewer that scores every hunk of a diff by **blast radius**: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.\n\n*LiveReview's Blast Radius & Review Priority scoring, live in the diff viewer.*\n\n| The exact math, not a black box | Visualize blast radius at a glance | Every factor that feeds the score | \n|---|---|---|\n\n**Here's the goal:**\n\n**Click below to try LiveReview with your codebase:**", "url": "https://wpnews.pro/news/ssh-actually-explained-handshakes-keys-and-the-tunnel-trick", "canonical_source": "https://dev.to/lovestaco/ssh-actually-explained-handshakes-keys-and-the-tunnel-trick-48bf", "published_at": "2026-09-07 17:17:38+00:00", "updated_at": "2026-09-07 17:32:32.458861+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["SSH", "Diffie-Hellman", "LiveReview", "Maneshwar"], "alternates": {"html": "https://wpnews.pro/news/ssh-actually-explained-handshakes-keys-and-the-tunnel-trick", "markdown": "https://wpnews.pro/news/ssh-actually-explained-handshakes-keys-and-the-tunnel-trick.md", "text": "https://wpnews.pro/news/ssh-actually-explained-handshakes-keys-and-the-tunnel-trick.txt", "jsonld": "https://wpnews.pro/news/ssh-actually-explained-handshakes-keys-and-the-tunnel-trick.jsonld"}}