]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Implementation of OpenSSH's bcrypt.
authorSimon Tatham <anakin@pobox.com>
Mon, 27 Apr 2015 19:48:29 +0000 (20:48 +0100)
committerSimon Tatham <anakin@pobox.com>
Mon, 27 Apr 2015 19:48:29 +0000 (20:48 +0100)
This isn't the same as the standard bcrypt; it's OpenSSH's
modification that they use for their new-style key format.

In order to implement this, I've broken up blowfish_setkey() into two
subfunctions, and provided one of them with an extra optional salt
parameter, which is NULL in ordinary Blowfish but used by bcrypt.
Also, I've exposed some of sshblowf.c's internal machinery for the new
sshbcrypt.c to use.

ssh.h
sshbcrypt.c [new file with mode: 0644]
sshblowf.c
sshblowf.h [new file with mode: 0644]

diff --git a/ssh.h b/ssh.h
index b6365c5bf2c8c537c4dcb3495dd82ded77c8a549..c958ddd1623d67ae2093eafffec75b72df7ca764 100644 (file)
--- 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 (file)
index 0000000..a6c163c
--- /dev/null
@@ -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 <stddef.h>
+#include <string.h>
+#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));
+}
index e3b4f509c13d1297ef7405c29a263d607144029a..c155b25e836154d40fa58a57b990d47d58f51ab5 100644 (file)
@@ -7,11 +7,12 @@
 #include <assert.h>
 #include <stdio.h>
 #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 (file)
index 0000000..eb6a48c
--- /dev/null
@@ -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);