mirror of
https://github.com/libtom/libtomcrypt
synced 2026-08-25 20:26:07 -04:00
add OpenSSH Private Key decryption demo
Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
This commit is contained in:
parent
f27a312a6c
commit
bee3ad2a2a
3 changed files with 279 additions and 4 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -34,6 +34,8 @@ multi
|
|||
multi.exe
|
||||
openssl-enc
|
||||
openssl-enc.exe
|
||||
openssh-privkey
|
||||
openssh-privkey.exe
|
||||
sizes
|
||||
sizes.exe
|
||||
small
|
||||
|
|
|
|||
272
demos/openssh-privkey.c
Normal file
272
demos/openssh-privkey.c
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/* LibTomCrypt, modular cryptographic library -- Tom St Denis
|
||||
*
|
||||
* LibTomCrypt is a library that provides various cryptographic
|
||||
* algorithms in a highly modular and flexible manner.
|
||||
*
|
||||
* The library is free for all purposes without any express
|
||||
* guarantee it works.
|
||||
*/
|
||||
|
||||
/**
|
||||
@file openssh-privkey.c
|
||||
OpenSSH Private Key decryption demo, Steffen Jaeckel
|
||||
*/
|
||||
|
||||
#include <bsd/string.h>
|
||||
#include <tomcrypt.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
static int verbose = 1;
|
||||
|
||||
static void print_hex(const char* what, const void* v, const unsigned long l)
|
||||
{
|
||||
const unsigned char* p = v;
|
||||
unsigned long x, y = 0, z;
|
||||
|
||||
if (!verbose) return;
|
||||
|
||||
fprintf(stderr, "%s contents: \n", what);
|
||||
for (x = 0; x < l; ) {
|
||||
fprintf(stderr, "%02X ", p[x]);
|
||||
if (!(++x % 16) || x == l) {
|
||||
if((x % 16) != 0) {
|
||||
z = 16 - (x % 16);
|
||||
if(z >= 8)
|
||||
fprintf(stderr, " ");
|
||||
for (; z != 0; --z) {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
}
|
||||
fprintf(stderr, " | ");
|
||||
for(; y < x; y++) {
|
||||
if((y % 8) == 0)
|
||||
fprintf(stderr, " ");
|
||||
if(isgraph(p[y]))
|
||||
fprintf(stderr, "%c", p[y]);
|
||||
else
|
||||
fprintf(stderr, ".");
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
else if((x % 8) == 0) {
|
||||
fprintf(stderr, " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void print_err(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
if (!verbose) return;
|
||||
|
||||
va_start(args, fmt);
|
||||
vfprintf(stderr, fmt, args);
|
||||
}
|
||||
|
||||
static void die_(int err, int line)
|
||||
{
|
||||
print_err("%3d: LTC sez %s\n", line, error_to_string(err));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#define die(i) do { die_(i, __LINE__); } while(0)
|
||||
#define DIE(s) do { print_err("%3d: " s "\n", __LINE__); exit(EXIT_FAILURE); } while(0)
|
||||
|
||||
|
||||
enum blockcipher_mode {
|
||||
none, cbc, ctr, stream, gcm
|
||||
};
|
||||
struct ssh_blockcipher {
|
||||
const char *name;
|
||||
const char *cipher;
|
||||
int len;
|
||||
enum blockcipher_mode mode;
|
||||
};
|
||||
|
||||
/* Table as of
|
||||
* https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml#ssh-parameters-17
|
||||
*/
|
||||
const struct ssh_blockcipher ssh_ciphers[] =
|
||||
{
|
||||
{ "aes256-cbc", "aes", 256 / 8, cbc },
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
|
||||
struct ssh_decode_ctx {
|
||||
unsigned char *orig;
|
||||
size_t len, rd;
|
||||
void *cur;
|
||||
};
|
||||
|
||||
ulong32 read_length_and_advance(int is_length, struct ssh_decode_ctx *ctx) {
|
||||
ulong32 r;
|
||||
ctx->cur = &ctx->orig[ctx->rd];
|
||||
|
||||
LOAD32H(r, ctx->cur);
|
||||
print_err("%u\n", r);
|
||||
if (is_length && ctx->rd + r > ctx->len) DIE("too long");
|
||||
ctx->rd += 4;
|
||||
|
||||
ctx->cur = &ctx->orig[ctx->rd];
|
||||
if (is_length) print_hex("next", ctx->cur, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* The basic format of the key is described here:
|
||||
* https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
||||
*/
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) return EXIT_FAILURE;
|
||||
|
||||
int err;
|
||||
if ((err = register_all_ciphers()) != CRYPT_OK) {
|
||||
die(err);
|
||||
}
|
||||
if ((err = register_all_hashes()) != CRYPT_OK) {
|
||||
die(err);
|
||||
}
|
||||
|
||||
char pem[100 * 72];
|
||||
size_t w = 0;
|
||||
const char *b64_start = "-----BEGIN OPENSSH PRIVATE KEY-----";
|
||||
const char *b64_end = "-----END OPENSSH PRIVATE KEY-----";
|
||||
char buf[72];
|
||||
FILE *f = fopen(argv[1], "r");
|
||||
if (f == NULL) DIE("fopen sez no");
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
const char *start = strstr(buf, b64_start);
|
||||
if (start == buf) break;
|
||||
}
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
char *end = strstr(buf, b64_end);
|
||||
if (end != NULL) {
|
||||
if (end == buf) break;
|
||||
*end = '\0';
|
||||
}
|
||||
size_t l = strlcpy(pem + w, buf, sizeof(pem) - w);
|
||||
if (l == 0) {
|
||||
DIE("strlcpy sez no");
|
||||
}
|
||||
w += l;
|
||||
}
|
||||
|
||||
unsigned char b64_decoded[sizeof(pem)];
|
||||
unsigned long b64_decoded_len = sizeof(pem);
|
||||
if ((err = base64_sane_decode(pem, w, b64_decoded, &b64_decoded_len)) != CRYPT_OK) {
|
||||
die(err);
|
||||
}
|
||||
print_hex("decoded", b64_decoded, b64_decoded_len);
|
||||
|
||||
void *magic = strstr((const char*)b64_decoded, "openssh-key-v1");
|
||||
if (magic == NULL) DIE("magic not found");
|
||||
if (magic != b64_decoded) DIE("magic not at the beginning");
|
||||
struct ssh_decode_ctx dec_ctx;
|
||||
dec_ctx.orig = b64_decoded;
|
||||
dec_ctx.len = b64_decoded_len;
|
||||
dec_ctx.rd = strlen("openssh-key-v1") + 1;
|
||||
|
||||
ulong32 l = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
const struct ssh_blockcipher *c = NULL;
|
||||
for (size_t i = 0; i < sizeof(ssh_ciphers)/sizeof(ssh_ciphers[0]); ++i) {
|
||||
if (memcmp(dec_ctx.cur, ssh_ciphers[i].name, l) == 0) {
|
||||
c = &ssh_ciphers[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c == NULL) DIE("can't find cipher");
|
||||
dec_ctx.rd += l;
|
||||
|
||||
l = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
if (memcmp(dec_ctx.cur, "bcrypt", l) != 0) DIE("unsupported kdf");
|
||||
dec_ctx.rd += l;
|
||||
|
||||
l = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
unsigned char salt[32];
|
||||
unsigned long salt_len = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
memcpy(salt, dec_ctx.cur, salt_len);
|
||||
dec_ctx.rd += salt_len;
|
||||
|
||||
ulong32 rounds = read_length_and_advance(0, &dec_ctx);
|
||||
|
||||
ulong32 num_pubkeys = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
l = read_length_and_advance(1, &dec_ctx);
|
||||
l = read_length_and_advance(1, &dec_ctx);
|
||||
if (memcmp(dec_ctx.cur, "ssh-ed25519", l) != 0) DIE("unsupported pka");
|
||||
dec_ctx.rd += l;
|
||||
|
||||
unsigned char pubkey[32];
|
||||
unsigned long pubkey_len = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
memcpy(pubkey, dec_ctx.cur, pubkey_len);
|
||||
dec_ctx.rd += pubkey_len;
|
||||
|
||||
l = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
unsigned char decrypted_privkey[sizeof(pem)];
|
||||
unsigned long decrypted_privkey_len = sizeof(decrypted_privkey);
|
||||
|
||||
int cipher = find_cipher(c->cipher);
|
||||
|
||||
unsigned char symkey[128];
|
||||
unsigned long symkey_len = c->len + cipher_descriptor[cipher].block_length;
|
||||
|
||||
if (sizeof(symkey) < symkey_len) DIE("too small");
|
||||
|
||||
if ((err = bcrypt("abc123", 6, salt, salt_len, rounds, find_hash("sha512"), symkey, &symkey_len)) != CRYPT_OK) {
|
||||
die(err);
|
||||
}
|
||||
|
||||
symmetric_CBC cbc_ctx;
|
||||
if ((err = cbc_start(cipher, symkey + c->len, symkey, c->len, 0, &cbc_ctx)) != CRYPT_OK) {
|
||||
die(err);
|
||||
}
|
||||
if ((err = cbc_decrypt(dec_ctx.cur, decrypted_privkey, l, &cbc_ctx)) != CRYPT_OK) {
|
||||
die(err);
|
||||
}
|
||||
decrypted_privkey_len = l;
|
||||
print_hex("decrypted", decrypted_privkey, decrypted_privkey_len);
|
||||
|
||||
dec_ctx.orig = decrypted_privkey;
|
||||
dec_ctx.len = decrypted_privkey_len;
|
||||
dec_ctx.rd = 0;
|
||||
|
||||
ulong32 check1 = read_length_and_advance(0, &dec_ctx);
|
||||
ulong32 check2 = read_length_and_advance(0, &dec_ctx);
|
||||
|
||||
if (check1 != check2) DIE("decrypt failed");
|
||||
|
||||
l = read_length_and_advance(1, &dec_ctx);
|
||||
if (memcmp(dec_ctx.cur, "ssh-ed25519", l) != 0) DIE("unsupported pka");
|
||||
dec_ctx.rd += l;
|
||||
|
||||
l = read_length_and_advance(1, &dec_ctx);
|
||||
if (memcmp(dec_ctx.cur, pubkey, l) != 0) DIE("pubkey's don't match");
|
||||
dec_ctx.rd += l;
|
||||
|
||||
unsigned char privkey[64];
|
||||
unsigned long privkey_len = read_length_and_advance(1, &dec_ctx);
|
||||
|
||||
memcpy(privkey, dec_ctx.cur, privkey_len);
|
||||
|
||||
if (memcmp(&privkey[32], pubkey, 32) != 0) DIE("pubkey in privkey doesn't match");
|
||||
|
||||
curve25519_key k;
|
||||
if ((err = ed25519_set_key(privkey, 32, &privkey[32], 32, &k)) != CRYPT_OK) {
|
||||
die(err);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* ref: $Format:%D$ */
|
||||
/* git commit: $Format:%H$ */
|
||||
/* commit time: $Format:%ai$ */
|
||||
|
|
@ -174,10 +174,11 @@ USEABLE_DEMOS = ltcrypt sizes constants
|
|||
TEST_DEMOS = small tv_gen
|
||||
|
||||
# Demos that are in one config broken
|
||||
# aesgcm - can't be built with LTC_EASY
|
||||
# openssl-enc - can't be built with LTC_EASY
|
||||
# timing - not really broken, but older gcc builds spit warnings
|
||||
BROKEN_DEMOS = aesgcm openssl-enc timing
|
||||
# aesgcm - can't be built with LTC_EASY
|
||||
# openssl-enc - can't be built with LTC_EASY
|
||||
# openssh-privkey - can't be built with LTC_EASY
|
||||
# timing - not really broken, but older gcc builds spit warnings
|
||||
BROKEN_DEMOS = aesgcm openssl-enc openssh-privkey timing
|
||||
|
||||
# Combine demos in groups
|
||||
UNBROKEN_DEMOS = $(TEST_DEMOS) $(USEABLE_DEMOS) $(USEFUL_DEMOS)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue