diff --git a/ssh.h b/ssh.h index b6365c5b..c958ddd1 100644 --- a/ssh.h +++ b/ssh.h @@ -663,6 +663,10 @@ void des_encrypt_xdmauth(const unsigned char *key, void des_decrypt_xdmauth(const unsigned char *key, unsigned char *blk, int len); +void openssh_bcrypt(const char *passphrase, + const unsigned char *salt, int saltbytes, + int rounds, unsigned char *out, int outbytes); + /* * For progress updates in the key generation utility. */ diff --git a/sshbcrypt.c b/sshbcrypt.c new file mode 100644 index 00000000..a6c163c6 --- /dev/null +++ b/sshbcrypt.c @@ -0,0 +1,124 @@ +/* + * 'bcrypt' password hash function, for PuTTY's import/export of + * OpenSSH encrypted private key files. + * + * This is not really the same as the original bcrypt; OpenSSH has + * modified it in various ways, and of course we have to do the same. + */ + +#include +#include +#include "ssh.h" +#include "sshblowf.h" + +BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes) +{ + int i; + BlowfishContext *ctx; + + ctx = blowfish_make_context(); + blowfish_initkey(ctx); + blowfish_expandkey(ctx, key, keybytes, salt, saltbytes); + + /* Original bcrypt replaces this fixed loop count with the + * variable cost. OpenSSH instead iterates the whole thing more + * than once if it wants extra rounds. */ + for (i = 0; i < 64; i++) { + blowfish_expandkey(ctx, salt, saltbytes, NULL, 0); + blowfish_expandkey(ctx, key, keybytes, NULL, 0); + } + + return ctx; +} + +void bcrypt_hash(const unsigned char *key, int keybytes, + const unsigned char *salt, int saltbytes, + unsigned char output[32]) +{ + BlowfishContext *ctx; + int i; + + ctx = bcrypt_setup(key, keybytes, salt, saltbytes); + /* This was quite a nice starting string until it ran into + * little-endian Blowfish :-/ */ + memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32); + for (i = 0; i < 64; i++) { + blowfish_lsb_encrypt_ecb(output, 32, ctx); + } + blowfish_free_context(ctx); +} + +void bcrypt_genblock(int counter, + const unsigned char hashed_passphrase[64], + const unsigned char *salt, int saltbytes, + unsigned char output[32]) +{ + SHA512_State shastate; + unsigned char hashed_salt[64]; + unsigned char countbuf[4]; + + /* Hash the input salt with the counter value optionally suffixed + * to get our real 32-byte salt */ + SHA512_Init(&shastate); + SHA512_Bytes(&shastate, salt, saltbytes); + if (counter) { + PUT_32BIT_MSB_FIRST(countbuf, counter); + SHA512_Bytes(&shastate, countbuf, 4); + } + SHA512_Final(&shastate, hashed_salt); + + bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output); + + smemclr(&shastate, sizeof(shastate)); + smemclr(&hashed_salt, sizeof(hashed_salt)); +} + +void openssh_bcrypt(const char *passphrase, + const unsigned char *salt, int saltbytes, + int rounds, unsigned char *out, int outbytes) +{ + unsigned char hashed_passphrase[64]; + unsigned char block[32], outblock[32]; + const unsigned char *thissalt; + int thissaltbytes; + int modulus, residue, i, j, round; + + /* Hash the passphrase to get the bcrypt key material */ + SHA512_Simple(passphrase, strlen(passphrase), hashed_passphrase); + + /* We output key bytes in a scattered fashion to meld all output + * key blocks into all parts of the output. To do this, we pick a + * modulus, and we output the key bytes to indices of out[] in the + * following order: first the indices that are multiples of the + * modulus, then the ones congruent to 1 mod modulus, etc. Each of + * those passes consumes exactly one block output from + * bcrypt_genblock, so we must pick a modulus large enough that at + * most 32 bytes are used in the pass. */ + modulus = (outbytes + 31) / 32; + + for (residue = 0; residue < modulus; residue++) { + /* Our output block of data is the XOR of all blocks generated + * by bcrypt in the following loop */ + memset(outblock, 0, sizeof(outblock)); + + thissalt = salt; + thissaltbytes = saltbytes; + for (round = 0; round < rounds; round++) { + bcrypt_genblock(round == 0 ? residue+1 : 0, + hashed_passphrase, + thissalt, thissaltbytes, block); + /* Each subsequent bcrypt call reuses the previous one's + * output as its salt */ + thissalt = block; + thissaltbytes = 32; + + for (i = 0; i < 32; i++) + outblock[i] ^= block[i]; + } + + for (i = residue, j = 0; i < outbytes; i += modulus, j++) + out[i] = outblock[j]; + } + smemclr(&hashed_passphrase, sizeof(hashed_passphrase)); +} diff --git a/sshblowf.c b/sshblowf.c index e3b4f509..c155b25e 100644 --- a/sshblowf.c +++ b/sshblowf.c @@ -7,11 +7,12 @@ #include #include #include "ssh.h" +#include "sshblowf.h" -typedef struct { +struct BlowfishContext { word32 S0[256], S1[256], S2[256], S3[256], P[18]; word32 iv0, iv1; /* for CBC mode */ -} BlowfishContext; +}; /* * The Blowfish init data: hex digits of the fractional part of pi. @@ -305,6 +306,24 @@ static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, ctx->iv1 = iv1; } +void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len, + BlowfishContext * ctx) +{ + word32 xL, xR, out[2]; + + assert((len & 7) == 0); + + while (len > 0) { + xL = GET_32BIT_LSB_FIRST(blk); + xR = GET_32BIT_LSB_FIRST(blk + 4); + blowfish_encrypt(xL, xR, out, ctx); + PUT_32BIT_LSB_FIRST(blk, out[0]); + PUT_32BIT_LSB_FIRST(blk + 4, out[1]); + blk += 8; + len -= 8; + } +} + static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, BlowfishContext * ctx) { @@ -415,8 +434,25 @@ static void blowfish_msb_sdctr(unsigned char *blk, int len, ctx->iv1 = iv1; } -static void blowfish_setkey(BlowfishContext * ctx, - const unsigned char *key, short keybytes) +void blowfish_initkey(BlowfishContext *ctx) +{ + int i; + + for (i = 0; i < 18; i++) { + ctx->P[i] = parray[i]; + } + + for (i = 0; i < 256; i++) { + ctx->S0[i] = sbox0[i]; + ctx->S1[i] = sbox1[i]; + ctx->S2[i] = sbox2[i]; + ctx->S3[i] = sbox3[i]; + } +} + +void blowfish_expandkey(BlowfishContext * ctx, + const unsigned char *key, short keybytes, + const unsigned char *salt, short saltbytes) { word32 *S0 = ctx->S0; word32 *S1 = ctx->S1; @@ -424,10 +460,18 @@ static void blowfish_setkey(BlowfishContext * ctx, word32 *S3 = ctx->S3; word32 *P = ctx->P; word32 str[2]; - int i; + int i, j; + int saltpos; + unsigned char dummysalt[1]; + + saltpos = 0; + if (!salt) { + saltbytes = 1; + salt = dummysalt; + dummysalt[0] = 0; + } for (i = 0; i < 18; i++) { - P[i] = parray[i]; P[i] ^= ((word32) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; P[i] ^= @@ -437,48 +481,59 @@ static void blowfish_setkey(BlowfishContext * ctx, P[i] ^= ((word32) (unsigned char) (key[(i * 4 + 3) % keybytes])); } - for (i = 0; i < 256; i++) { - S0[i] = sbox0[i]; - S1[i] = sbox1[i]; - S2[i] = sbox2[i]; - S3[i] = sbox3[i]; - } - str[0] = str[1] = 0; for (i = 0; i < 18; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + blowfish_encrypt(str[0], str[1], str, ctx); P[i] = str[0]; P[i + 1] = str[1]; } for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); blowfish_encrypt(str[0], str[1], str, ctx); S0[i] = str[0]; S0[i + 1] = str[1]; } for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); blowfish_encrypt(str[0], str[1], str, ctx); S1[i] = str[0]; S1[i + 1] = str[1]; } for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); blowfish_encrypt(str[0], str[1], str, ctx); S2[i] = str[0]; S2[i + 1] = str[1]; } for (i = 0; i < 256; i += 2) { + for (j = 0; j < 8; j++) + str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); blowfish_encrypt(str[0], str[1], str, ctx); S3[i] = str[0]; S3[i + 1] = str[1]; } } +static void blowfish_setkey(BlowfishContext *ctx, + const unsigned char *key, short keybytes) +{ + blowfish_initkey(ctx); + blowfish_expandkey(ctx, key, keybytes, NULL, 0); +} + /* -- Interface with PuTTY -- */ #define SSH_SESSION_KEY_LENGTH 32 -static void *blowfish_make_context(void) +void *blowfish_make_context(void) { return snew(BlowfishContext); } @@ -489,7 +544,7 @@ static void *blowfish_ssh1_make_context(void) return snewn(2, BlowfishContext); } -static void blowfish_free_context(void *handle) +void blowfish_free_context(void *handle) { sfree(handle); } diff --git a/sshblowf.h b/sshblowf.h new file mode 100644 index 00000000..eb6a48c1 --- /dev/null +++ b/sshblowf.h @@ -0,0 +1,15 @@ +/* + * Header file shared between sshblowf.c and sshbcrypt.c. Exposes the + * internal Blowfish routines needed by bcrypt. + */ + +typedef struct BlowfishContext BlowfishContext; + +void *blowfish_make_context(void); +void blowfish_free_context(void *handle); +void blowfish_initkey(BlowfishContext *ctx); +void blowfish_expandkey(BlowfishContext *ctx, + const unsigned char *key, short keybytes, + const unsigned char *salt, short saltbytes); +void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len, + BlowfishContext *ctx);