Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,8 @@ Major changes from 1.9.0-jumbo-1 (May 2019) in this bleeding-edge version:
- Add support to keepass2john for the current format of KeePass XML keyfiles.
The formats needed no changes. [magnum; 2025]

- PuTTY format and putty2john: Add support for PPKv3 with Argon2 KDF. This
introduces tunable cost for the PuTTY format. [elipsion; 2026]

Major changes from 1.8.0-jumbo-1 (December 2014) to 1.9.0-jumbo-1 (May 2019):

Expand Down
4 changes: 2 additions & 2 deletions src/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,8 @@ poly1305-donna/poly1305-donna.a:
../run/hccap2john@EXE_EXT@: hccap2john.o common.o jumbo.o
$(LD) $(LDFLAGS) @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ common.o hccap2john.o jumbo.o @OPENMP_CFLAGS@ -o $@

../run/putty2john@EXE_EXT@: putty2john.o jumbo.o
$(LD) $(LDFLAGS) @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ putty2john.o jumbo.o @OPENMP_CFLAGS@ -o $@
../run/putty2john@EXE_EXT@: putty2john.o common.o jumbo.o
$(LD) $(LDFLAGS) @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ putty2john.o common.o jumbo.o @OPENMP_CFLAGS@ -o $@

# Note that this is NOT depending on PCAP lib. It is self-contained.
../run/wpapcap2john@EXE_EXT@: wpapcap2john.o jumbo.o
Expand Down
165 changes: 125 additions & 40 deletions src/putty2john.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
#endif

#include <stdint.h>
#include <stddef.h> /* for size_t */
#include <string.h> /* for memcpy() */
#include <stddef.h> /* for size_t */
#include <string.h> /* for memcpy() */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
Expand All @@ -31,6 +31,7 @@

#include "memory.h"
#include "jumbo.h"
#include "common.h"
#if _MSC_VER
#include <io.h>
#endif
Expand All @@ -44,8 +45,8 @@

struct ssh2_userkey {
const struct ssh_signkey *alg; /* the key algorithm */
void *data; /* the key data */
char *comment; /* the key comment */
void *data; /* the key data */
char *comment; /* the key comment */
};

enum {
Expand All @@ -63,18 +64,23 @@ static const char *putty_error = NULL;
static int i, is_mac, old_fmt;
static char alg[32];
static int cipher, cipherblk;
static int ppk_version;
static char *key_derivation;
static int argon2_memory, argon2_passes, argon2_parallelism;
static unsigned char argon2_salt[64];
static int argon2_salt_len;
static unsigned char *public_blob, *private_blob;
static int public_blob_len, private_blob_len;

static char *read_body(FILE * fp)
static char *read_body(FILE *fp)
{
char *text;
int len;
int size;
int c;

size = 128 * 1024;
text = (char*)malloc(size);
text = (char *)malloc(size);
if (!text) {
fprintf(stderr, "malloc failed in read_body, exiting!\n");
exit(-1);
Expand All @@ -101,7 +107,7 @@ static char *read_body(FILE * fp)
}
}

static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen)
static unsigned char *read_blob(FILE *fp, int nlines, int *bloblen)
{
unsigned char *blob;
char *line;
Expand All @@ -113,7 +119,7 @@ static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen)
return NULL;

/* We expect at most 64 base64 characters, ie 48 real bytes, per line. */
blob = (unsigned char*)malloc(48 * nlines);
blob = (unsigned char *)malloc(48 * nlines);
if (!blob) {
fprintf(stderr, "malloc failed in read_blob, exiting!\n");
exit(-1);
Expand Down Expand Up @@ -148,36 +154,43 @@ static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen)
return blob;
}

static int read_header(FILE * fp, char *header)
static int read_header(FILE *fp, char *header)
{
int len = 39;
int c;

while (len > 0) {
c = fgetc(fp);
if (c == '\n' || c == '\r' || c == EOF)
return 0; /* failure */
return 0; /* failure */
if (c == ':') {
c = fgetc(fp);
if (c != ' ')
return 0;
*header = '\0';
return 1; /* success! */
return 1; /* success! */
}
if (len == 0)
return 0; /* failure */
return 0; /* failure */
*header++ = c;
len--;
}
return 0; /* failure */
return 0; /* failure */
}


static int init_LAME(const char *filename) {
static int init_LAME(const char *filename)
{
FILE *fp;

common_init();

argon2_memory = argon2_passes = argon2_parallelism = 0;
argon2_salt_len = 0;
encryption = comment = mac = NULL;
key_derivation = NULL;
public_blob = private_blob = NULL;
ppk_version = 0;

fp = fopen(filename, "rb");
if (!fp) {
Expand All @@ -188,9 +201,14 @@ static int init_LAME(const char *filename) {
/* Read the first header line which contains the key type. */
if (!read_header(fp, header))
goto error;
if (0 == strcmp(header, "PuTTY-User-Key-File-2")) {
if (0 == strcmp(header, "PuTTY-User-Key-File-3")) {
ppk_version = 3;
old_fmt = 0;
} else if (0 == strcmp(header, "PuTTY-User-Key-File-2")) {
ppk_version = 2;
old_fmt = 0;
} else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) {
ppk_version = 1;
/* this is an old key file; warn and then continue */
// old_keyfile_warning();
old_fmt = 1;
Expand Down Expand Up @@ -244,6 +262,57 @@ static int init_LAME(const char *filename) {
if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
goto error;

if (ppk_version >= 3 && cipher) {
/* Parse Argon2 KDF section used in PPK v3 encrypted keys. */
if (!read_header(fp, header) || 0 != strcmp(header, "Key-Derivation"))
goto error;
if ((key_derivation = read_body(fp)) == NULL)
goto error;
if (strcmp(key_derivation, "Argon2d") && strcmp(key_derivation, "Argon2i") && strcmp(key_derivation, "Argon2id"))
goto error;

if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Memory"))
goto error;
if ((b = read_body(fp)) == NULL)
goto error;
argon2_memory = atoi(b);
MEM_FREE(b);
if (argon2_memory <= 0)
goto error;

if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Passes"))
goto error;
if ((b = read_body(fp)) == NULL)
goto error;
argon2_passes = atoi(b);
MEM_FREE(b);
if (argon2_passes <= 0)
goto error;

if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Parallelism"))
goto error;
if ((b = read_body(fp)) == NULL)
goto error;
argon2_parallelism = atoi(b);
MEM_FREE(b);
if (argon2_parallelism <= 0)
goto error;

if (!read_header(fp, header) || 0 != strcmp(header, "Argon2-Salt"))
goto error;
if ((b = read_body(fp)) == NULL)
goto error;
int extra;
argon2_salt_len = hexlenl(b, &extra);
if (extra || argon2_salt_len > (int)sizeof(argon2_salt) * 2)
goto error;
for (i = 0; i < argon2_salt_len; i++)
argon2_salt[i] = atoi16[ARCH_INDEX(b[i * 2])] * 16 + atoi16[ARCH_INDEX(b[i * 2 + 1])];
MEM_FREE(b);
if (argon2_salt_len <= 0)
goto error;
}

/* Read the Private-Lines header line and the Private blob. */
if (!read_header(fp, header) || 0 != strcmp(header, "Private-Lines"))
goto error;
Expand Down Expand Up @@ -277,6 +346,7 @@ static int init_LAME(const char *filename) {
fclose(fp);
MEM_FREE(comment);
MEM_FREE(encryption);
MEM_FREE(key_derivation);
MEM_FREE(mac);
MEM_FREE(public_blob);
MEM_FREE(private_blob);
Expand All @@ -286,41 +356,56 @@ static int init_LAME(const char *filename) {
static void print_hex(unsigned char *str, int len)
{
int i;

for (i = 0; i < len; ++i)
printf("%02x", str[i]);
}

static void LAME_ssh2_load_userkey(const char *path, const char **errorstr)
{
const char *ext[] = {".ppk"};
const char *ext[] = { ".ppk" };
char *fname;

/*
* Decrypt the private blob.
*/
* Decrypt the private blob.
*/
if (cipher) {
if (private_blob_len % cipherblk)
goto error;
}

{
fname = strip_suffixes(basename(path), ext, 1);
printf("%s:$putty$%d*%d*%d*%d*%s*%d*", fname, cipher, cipherblk, is_mac, old_fmt, mac, public_blob_len);
print_hex(public_blob, public_blob_len);
printf("*%d*", private_blob_len);
print_hex(private_blob, private_blob_len);
if (!old_fmt) {
if (ppk_version >= 3) {
printf("%s:$putty$3*%d*%d*%d*%d*%s*%d*%d*%d*", fname,
cipher, cipherblk, is_mac, old_fmt,
key_derivation ? key_derivation : "none", argon2_memory, argon2_passes, argon2_parallelism);
print_hex(argon2_salt, argon2_salt_len);
printf("*%s*%d*", mac, public_blob_len);
print_hex(public_blob, public_blob_len);
printf("*%d*", private_blob_len);
print_hex(private_blob, private_blob_len);
printf("*%s*%s*%s\n", alg, encryption, comment);
}
else {
printf("\n");
} else {
printf("%s:$putty$%d*%d*%d*%d*%s*%d*", fname, cipher, cipherblk, is_mac, old_fmt, mac, public_blob_len);
print_hex(public_blob, public_blob_len);
printf("*%d*", private_blob_len);
print_hex(private_blob, private_blob_len);
if (!old_fmt) {
printf("*%s*%s*%s\n", alg, encryption, comment);
} else {
printf("\n");
}
}
MEM_FREE(comment);
MEM_FREE(key_derivation);
return;
}
error:
fprintf(stderr, "Something failed!\n");
MEM_FREE(comment);
MEM_FREE(encryption);
MEM_FREE(key_derivation);
MEM_FREE(mac);
MEM_FREE(public_blob);
MEM_FREE(private_blob);
Expand Down Expand Up @@ -350,15 +435,15 @@ static int key_type(const char *filename)
return SSH_KEYTYPE_UNOPENABLE;
if (i < 32)
return SSH_KEYTYPE_UNKNOWN;
if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1))
if (!memcmp(buf, rsa_signature, sizeof(rsa_signature) - 1))
return SSH_KEYTYPE_SSH1;
if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1))
if (!memcmp(buf, putty2_sig, sizeof(putty2_sig) - 1))
return SSH_KEYTYPE_SSH2;
if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1))
if (!memcmp(buf, openssh_sig, sizeof(openssh_sig) - 1))
return SSH_KEYTYPE_OPENSSH;
if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1))
if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig) - 1))
return SSH_KEYTYPE_SSHCOM;
return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */
return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */
}

static int ssh2_userkey_encrypted(const char *filename, char **commentptr)
Expand All @@ -374,16 +459,16 @@ static int ssh2_userkey_encrypted(const char *filename, char **commentptr)
if (!fp)
return 0;
if (!read_header(fp, header)
|| (0 != strcmp(header, "PuTTY-User-Key-File-2") &&
0 != strcmp(header, "PuTTY-User-Key-File-1"))) {
|| (0 != strcmp(header, "PuTTY-User-Key-File-3") &&
0 != strcmp(header, "PuTTY-User-Key-File-2") && 0 != strcmp(header, "PuTTY-User-Key-File-1"))) {
fclose(fp);
return 0;
}
if ((b = read_body(fp)) == NULL) {
fclose(fp);
return 0;
}
MEM_FREE(b); /* we don't care about key type here */
MEM_FREE(b); /* we don't care about key type here */
/* Read the Encryption header line. */
if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) {
fclose(fp);
Expand Down Expand Up @@ -440,7 +525,7 @@ static int base64_decode_atom(char *atom, unsigned char *out)
else if (c == '=')
v = -1;
else
return 0; /* invalid atom */
return 0; /* invalid atom */
vals[i] = v;
}

Expand All @@ -456,8 +541,7 @@ static int base64_decode_atom(char *atom, unsigned char *out)
else
len = 1;

word = ((vals[0] << 18) |
(vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F));
word = ((vals[0] << 18) | (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F));
out[0] = (word >> 16) & 0xFF;
if (len > 1)
out[1] = (word >> 8) & 0xFF;
Expand Down Expand Up @@ -512,7 +596,7 @@ static void process_file(const char *filename)
if (type == SSH_KEYTYPE_SSH1) {
fprintf(stderr, "SSH1 key type not supported!\n");
goto out;
} else { // SSH_KEYTYPE_SSH2
} else { // SSH_KEYTYPE_SSH2
if (realtype == type) {
LAME_ssh2_load_userkey(filename, &errmsg);
}
Expand All @@ -522,6 +606,7 @@ static void process_file(const char *filename)

MEM_FREE(comment);
MEM_FREE(encryption);
MEM_FREE(key_derivation);
MEM_FREE(mac);
MEM_FREE(public_blob);
MEM_FREE(private_blob);
Expand Down Expand Up @@ -551,8 +636,8 @@ int main(int argc, char **argv)
int i;

if (argc < 2) {
printf( "Usage: putty2john [.ppk PuTTY-Private-Key-File(s)]\n");
printf( "\nKey types supported: RSA, DSA, ECDSA, ED25519\n");
printf("Usage: putty2john [.ppk PuTTY-Private-Key-File(s)]\n");
printf("\nKey types supported: RSA, DSA, ECDSA, ED25519\n");
exit(1);
}

Expand Down
Loading