mirror of
https://github.com/Noratrieb/blog.git
synced 2026-01-14 20:35:02 +01:00
openssh private key fun
This commit is contained in:
parent
3d4c1e10d9
commit
d0593cbd0e
8 changed files with 1434 additions and 0 deletions
370
content/posts/fake-openssh-keys/fake_openssh_key.js
Normal file
370
content/posts/fake-openssh-keys/fake_openssh_key.js
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
let wasm;
|
||||
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
|
||||
function getUint8ArrayMemory0() {
|
||||
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
const heap = new Array(128).fill(undefined);
|
||||
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
function getObject(idx) { return heap[idx]; }
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 132) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
|
||||
|
||||
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
||||
? function (arg, view) {
|
||||
return cachedTextEncoder.encodeInto(arg, view);
|
||||
}
|
||||
: function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length
|
||||
};
|
||||
});
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8ArrayMemory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7F) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = encodeString(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachedDataViewMemory0 = null;
|
||||
|
||||
function getDataViewMemory0() {
|
||||
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
||||
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
||||
}
|
||||
return cachedDataViewMemory0;
|
||||
}
|
||||
/**
|
||||
* @param {string} public_key
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generate_fake(public_key) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passStringToWasm0(public_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.generate_fake(retptr, ptr0, len0);
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
||||
var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
|
||||
var ptr2 = r0;
|
||||
var len2 = r1;
|
||||
if (r3) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeObject(r2);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||
}
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
|
||||
} catch (e) {
|
||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||
const ret = getObject(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_crypto_1d1f22824a6a080c = function(arg0) {
|
||||
const ret = getObject(arg0).crypto;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_object = function(arg0) {
|
||||
const val = getObject(arg0);
|
||||
const ret = typeof(val) === 'object' && val !== null;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_process_4a72847cc503995b = function(arg0) {
|
||||
const ret = getObject(arg0).process;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_versions_f686565e586dd935 = function(arg0) {
|
||||
const ret = getObject(arg0).versions;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_node_104a2ff8d6ea03a2 = function(arg0) {
|
||||
const ret = getObject(arg0).node;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_string = function(arg0) {
|
||||
const ret = typeof(getObject(arg0)) === 'string';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_require_cca90b1a94a0255b = function() { return handleError(function () {
|
||||
const ret = module.require;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_is_function = function(arg0) {
|
||||
const ret = typeof(getObject(arg0)) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_msCrypto_eb05e62b530a1508 = function(arg0) {
|
||||
const ret = getObject(arg0).msCrypto;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_newwithlength_ec548f448387c968 = function(arg0) {
|
||||
const ret = new Uint8Array(arg0 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_call_89af060b4e1523f2 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_self_3093d5d1f7bcb682 = function() { return handleError(function () {
|
||||
const ret = self.self;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_window_3bcfc4d31bc012f8 = function() { return handleError(function () {
|
||||
const ret = window.window;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_globalThis_86b222e13bdf32ed = function() { return handleError(function () {
|
||||
const ret = globalThis.globalThis;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_global_e5a3fe56f8be9485 = function() { return handleError(function () {
|
||||
const ret = global.global;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = getObject(arg0) === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newnoargs_76313bd6ff35d0f2 = function(arg0, arg1) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_call_1084a111329e68ce = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).call(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_subarray_7c2e3576afe181d1 = function(arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_getRandomValues_3aa56aa6edec874c = function() { return handleError(function (arg0, arg1) {
|
||||
getObject(arg0).getRandomValues(getObject(arg1));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_memory = function() {
|
||||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_buffer_b7b08af79b0b0974 = function(arg0) {
|
||||
const ret = getObject(arg0).buffer;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_newwithbyteoffsetandlength_8a2cb9ca96b27ec9 = function(arg0, arg1, arg2) {
|
||||
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_randomFillSync_5c9c955aa56b6049 = function() { return handleError(function (arg0, arg1) {
|
||||
getObject(arg0).randomFillSync(takeObject(arg1));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_new_ea1883e1e5e86686 = function(arg0) {
|
||||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_set_d1e79e2388520f18 = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
function __wbg_init_memory(imports, memory) {
|
||||
|
||||
}
|
||||
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
__wbg_init.__wbindgen_wasm_module = module;
|
||||
cachedDataViewMemory0 = null;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
|
||||
|
||||
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module !== 'undefined' && Object.getPrototypeOf(module) === Object.prototype)
|
||||
({module} = module)
|
||||
else
|
||||
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
async function __wbg_init(module_or_path) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module_or_path !== 'undefined' && Object.getPrototypeOf(module_or_path) === Object.prototype)
|
||||
({module_or_path} = module_or_path)
|
||||
else
|
||||
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
||||
|
||||
if (typeof module_or_path === 'undefined') {
|
||||
module_or_path = new URL('fake_openssh_key_bg.wasm', import.meta.url);
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
||||
module_or_path = fetch(module_or_path);
|
||||
}
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
export { initSync };
|
||||
export default __wbg_init;
|
||||
BIN
content/posts/fake-openssh-keys/fake_openssh_key_bg.wasm
Normal file
BIN
content/posts/fake-openssh-keys/fake_openssh_key_bg.wasm
Normal file
Binary file not shown.
223
content/posts/fake-openssh-keys/index.md
Normal file
223
content/posts/fake-openssh-keys/index.md
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
+++
|
||||
title = "Having fun with OpenSSH private keys"
|
||||
date = "2024-09-13"
|
||||
author = "Noratrieb"
|
||||
authorTwitter = "@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 obivously 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 private 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:
|
||||
Loading…
Add table
Add a link
Reference in a new issue