mirror of
https://github.com/Noratrieb/blog.git
synced 2026-01-14 20:35:02 +01:00
222 lines
9.6 KiB
Markdown
222 lines
9.6 KiB
Markdown
+++
|
|
title = "Having fun with OpenSSH private keys"
|
|
date = "2024-09-13"
|
|
author = "Noratrieb"
|
|
tags = ["ssh"]
|
|
keywords = ["SSH"]
|
|
description = "An interactive way to have fun with OpenSSH private keys"
|
|
showFullContent = false
|
|
readingTime = true
|
|
hideComments = false
|
|
draft = false
|
|
+++
|
|
|
|
you likely have an SSH private key.
|
|
and unless you're doing something *seriously* wrong, only you have this key.
|
|
that's the entire point, after all.
|
|
|
|
this private key is used for authenticating with an SSH server.
|
|
you sign a message with your private key and the server then verifies it with the public key.
|
|
this ensures that it's you who authenticated to the server, and not your friends or enemies.
|
|
or me. don't let me into your servers.
|
|
|
|
but how are these keys encoded and can we have fun with them?
|
|
let's use `ssh-keygen -t ed25519` to generate a test key and find out.
|
|
|
|
```
|
|
$ ssh-keygen -t ed25519
|
|
Generating public/private ed25519 key pair.
|
|
Enter file in which to save the key (/home/nora/.ssh/id_ed25519): testkey
|
|
Enter passphrase (empty for no passphrase):
|
|
Enter same passphrase again:
|
|
Your identification has been saved in testkey
|
|
Your public key has been saved in testkey.pub
|
|
The key fingerprint is:
|
|
SHA256:IPrdC+4S0ZIzwS1oYN3A78Q29yV6gpDgiEkPwJtj0Wc nora@nixos
|
|
The key's randomart image is:
|
|
+--[ED25519 256]--+
|
|
|=o++o. |
|
|
|.*o++E. |
|
|
|=oB B=. |
|
|
|+* =*B.o . . |
|
|
|. o ==+ S o |
|
|
| ..+ + o |
|
|
| ..o + |
|
|
| .. . . |
|
|
| oo . |
|
|
+----[SHA256]-----+
|
|
```
|
|
|
|
this command has created two files, `testkey` and `testkey.pub`.
|
|
|
|
`testkey.pub` contains the public key and looks like this. if you're following along at home it probably looks different.
|
|
hopefully. unless you have gotten very lucky, which would be terrible and the downfall of the entire cryptographic ecosystem.
|
|
|
|
```
|
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEc5o2i/B1bVs7X2dJjE48l7fqAyMdgrbAItrO8XWwP9 nora@nixos
|
|
```
|
|
|
|
the public key starts with the key type, ed25519.
|
|
Ed25519 is a signature algorithm that is commonly used for modern SSH keys.
|
|
it's also the default for modern OpenSSH versions, so passing that `-t` flag was unnecessary.
|
|
other common algorithms are ECDSA (`ecdsa-sha2-nistp256`) and RSA (`ssh-rsa`).
|
|
unless you need compatiblity with ancient servers or are bound by outdated regulation, you probably don't need either of those.
|
|
|
|
after the key type, we have a base64 encoded blob of the "wire encoding" of the key.
|
|
this encoding is [standardized](https://datatracker.ietf.org/doc/html/rfc8709) and is sent by the client to the server every time it wants to authenticate, to choose which key to use.
|
|
the exact details vary by key type but for Ed25519, it contains the following:
|
|
|
|
|bytes|meaning|
|
|
------|-------|
|
|
|`0000 000b` | name length, 11 |
|
|
|`7373 682d 6564 3235 3531 39` | ssh-ed25519 |
|
|
|`0000 0020` | encoded Ed25519 public key length, always 32 |
|
|
|`4739 a368 bf07 56d5 b3b5 f674 98c4 e3c9 7b7e a032 31d8 2b6c 022d acef 175b 03fd` | encoded Ed25519 public key |
|
|
|
|
the last part is the comment. it's automatically set to my username and my hostname (i use nixos btw) and can be set to anything with the `-C` parameter.
|
|
it's supposed to help us figure out what the key is.
|
|
|
|
the public key is fairly boring, so we're gonna take a look at the exciting private key instead.
|
|
|
|
```
|
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
|
QyNTUxOQAAACBHOaNovwdW1bO19nSYxOPJe36gMjHYK2wCLazvF1sD/QAAAJCFCe+ShQnv
|
|
kgAAAAtzc2gtZWQyNTUxOQAAACBHOaNovwdW1bO19nSYxOPJe36gMjHYK2wCLazvF1sD/Q
|
|
AAAEDmrbLtUasQVBfkJV0ILoxDox64ngUwOASQbc8N0oZzNEc5o2i/B1bVs7X2dJjE48l7
|
|
fqAyMdgrbAItrO8XWwP9AAAACm5vcmFAbml4b3MBAgM=
|
|
-----END OPENSSH PRIVATE KEY-----
|
|
```
|
|
|
|
it goes without saying but never share your private key on the internet and this is obviously just a test key!
|
|
|
|
the entire key is base64-encoded in the [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format.
|
|
this makes it easier to copy around compared to raw bytes. not that you're supposed to copy it to random places.
|
|
|
|
an OpenSSH private key consists of two areas:
|
|
- a plaintext area with the public key
|
|
- a potentially-encrypted area with the private key
|
|
|
|
most strings are length-prefixed, i'm not gonna mention the length explicitly for many of the cases here.
|
|
if it starts with 3 null bytes, the first 4 bytes are probably the length.
|
|
for an `ssh-ed25519` key, the format looks like this:
|
|
|
|
|bytes|meaning|
|
|
------|-------|
|
|
`6f70 656e 7373 682d 6b65 792d 7631 00` | openssh-key-v1 (null-terminated) |
|
|
`0000 0004 6e6f 6e65` | cipher, `none` in this case (`aes256-ctr` is common for encrypted keys) |
|
|
`0000 0004 6e6f 6e65` | key derivation function, `none` in this case (`bcrypt` is common for encrypted keys) |
|
|
`0000 0000` | key derivation options, empty here (contains the salt and cost for `bcrypt`) |
|
|
`0000 0001` | amount of keys, 1 (yes, it could contain multiple) |
|
|
a bunch of bytes | the full public key, as seen previously
|
|
`0000 0090` | the length of the encrypted part. the rest is encrypted with the previously mentioned cipher and a password |
|
|
`8509 ef92 8509 ef92` | two identical 4-byte sequences, to check if decryption was successful |
|
|
`0000 000b 7373 682d 6564 3235 3531 39` | ssh-ed25519, the algorithm of the first key (which might seem familiar) |
|
|
`0000 0020 4739 a368 bf07 56d5 b3b5 f674 98c4 e3c9 7b7e a0323 1d8 2b6c 022d acef 175b 03fd` | the raw encoded public key bytes |
|
|
`0000 0040` | the length of the next part, which contains the... |
|
|
`e6ad b2ed 51ab 1054 17e4 255d 082e 8c43 a31e b89e 0530 3804 906d cf0d d286 7334` | ...raw private key bytes... |
|
|
`4739 a368 bf07 56d5 b3b5 f674 98c4 e3c9 7b7e a0323 1d8 2b6c 022d acef 175b 03fd` | ...and the public key bytes. AGAIN. YES.
|
|
|
|
the unencrypted public area makes it easy to check which public key a private key belongs to without needing to enter a password to decrypt it.
|
|
the encrypted area makes sure that even if someone manages to steal your private key, they can't use it unless they know your password.
|
|
unless you haven't set a password of course. which is why you should set a password for your private key.
|
|
|
|
having the public key bytes in there THREE TIMES seems very silly. but the fact that the public key is in there at all is useful.
|
|
|
|
maybe you've been in a situation where you've needed to find the public key file of a private key you had around, and just couldn't find it.
|
|
but as I just mentioned, you don't actually need the `.pub` file for that, as the public key is contained in the private key.
|
|
`ssh-keygen` can even extract it for you with `-y`!
|
|
|
|
```
|
|
$ ssh-keygen -y -f testkey
|
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEc5o2i/B1bVs7X2dJjE48l7fqAyMdgrbAItrO8XWwP9 nora@nixos
|
|
```
|
|
|
|
i have a public key.
|
|
You can find it on <https://github.com/Noratrieb.keys> (this works for any GitHub user that has uploaded SSH keys!) and at the time of writing, it was
|
|
```
|
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG0n1ikUG9rYqobh7WpAyXrqZqxQoQ2zNJrFPj12gTpP
|
|
```
|
|
|
|
but you don't care about this, do you? you really want my private key. i know it.
|
|
well, here it is:
|
|
```
|
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
|
|
c2gtZWQyNTUxOQAAACBtJ9YpFBva2KqG4e1qQMl66masUKENszSaxT49doE6TwAA
|
|
AIgQ5LRcEOS0XAAAAAtzc2gtZWQyNTUxOQAAACBtJ9YpFBva2KqG4e1qQMl66mas
|
|
UKENszSaxT49doE6TwAAAEAoBWfFwPJSZQxTNETJRn40Y2XFP2GbW1aAGX+SzP/o
|
|
rG0n1ikUG9rYqobh7WpAyXrqZqxQoQ2zNJrFPj12gTpPAAAAAAECAwQF
|
|
-----END OPENSSH PRIVATE KEY-----
|
|
```
|
|
|
|
don't believe me? check it yourself!
|
|
|
|
```
|
|
$ ssh-keygen -y -f the-just-posted-public-key
|
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG0n1ikUG9rYqobh7WpAyXrqZqxQoQ2zNJrFPj12gTpP
|
|
```
|
|
|
|
it's true! you indeed have my private key! don't do bad things with it, please.
|
|
|
|
well, you probably won't believe me. you know how SSH private keys and `ssh-keygen -y` works,
|
|
and you know that the private key i posted above is just a random private key with my public key put into the public key part.
|
|
and you're right. good job!
|
|
|
|
but maybe your friends don't know that. or your enemies.
|
|
posting "your public key" may confuse them and is fun... and we're here for fun!
|
|
|
|
you can use the generator below to generate a fake private key for a public key.
|
|
it only supports `ssh-ed25519` and `ecdsa-sha2-nistp256`.
|
|
[no `ssh-rsa`, sorry](https://blog.trailofbits.com/2019/07/08/fuck-rsa/).
|
|
if you have an RSA key, get a better key first.
|
|
the implementation is based on [cluelessh](https://github.com/Noratrieb/cluelessh), my own SSH toolkit, compiled to WebAssembly.
|
|
|
|
## generator
|
|
|
|
<label for="public-key-input">public Key</label>
|
|
<br>
|
|
|
|
<textarea id="public-key-input" rows="10" cols="29"></textarea>
|
|
<button id="convert-button" style="margin-left: 10px;">Generate</button>
|
|
<div id="public-key-error"></div>
|
|
|
|
<label for="fake-key-output">fake private key</label>
|
|
<textarea id="fake-key-output" rows="10" disabled></textarea>
|
|
<style>
|
|
#fake-key-output { width: 90vw; }
|
|
@media (min-width: 600px) {
|
|
#fake-key-output { width: 30em; }
|
|
}
|
|
@media (min-width: 1000px) {
|
|
#fake-key-output { width: 50em; }
|
|
}
|
|
</style>
|
|
|
|
<script type="module">
|
|
import init, { generate_fake } from "./fake_openssh_key.js"
|
|
|
|
init({
|
|
module_or_path: new URL('fake_openssh_key_bg.wasm', import.meta.url)
|
|
});
|
|
|
|
const input = document.getElementById("public-key-input");
|
|
const output = document.getElementById("fake-key-output");
|
|
const button = document.getElementById("convert-button");
|
|
const error = document.getElementById("public-key-error");
|
|
|
|
button.addEventListener("click", () => {
|
|
const key = input.value;
|
|
error.innerText = "loading";
|
|
try {
|
|
const result = generate_fake(key);
|
|
output.value = result;
|
|
error.innerText = "";
|
|
} catch(e) {
|
|
console.log(key);
|
|
error.innerText = `error: ${e}`;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
what are these SSH keys actually used for? SSH of course. but how? oh do i have a blog post for you:
|