Skip to content

Commit

Permalink
Added new pbkdf and hash schemes from OpenBSD
Browse files Browse the repository at this point in the history
Added some test vectors to verify correct function.
  • Loading branch information
kruton committed Jun 22, 2016
1 parent b5f4ae6 commit 37a5a77
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 1 deletion.
92 changes: 92 additions & 0 deletions jbcrypt/src/main/java/org/mindrot/jbcrypt/BCrypt.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package org.mindrot.jbcrypt;

import java.io.UnsupportedEncodingException;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
Expand Down Expand Up @@ -335,6 +338,12 @@ public class BCrypt {
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
};

// OpenBSD IV: "OxychromaticBlowfishSwatDynamite" in big endian
private static final int[] openbsd_iv = new int[] {
0x4f787963, 0x68726f6d, 0x61746963, 0x426c6f77,
0x66697368, 0x53776174, 0x44796e61, 0x6d697465,
};

// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
// this "ciphertext", but it is really plaintext or an IV. We keep
// the name to make code comparison easier.
Expand Down Expand Up @@ -596,6 +605,89 @@ private void ekskey(byte data[], byte key[]) {
}
}

/**
* Compatibility with new OpenBSD function.
*/
public void hash(byte[] hpass, byte[] hsalt, byte[] output) {
init_key();
ekskey(hsalt, hpass);
for (int i = 0; i < 64; i++) {
key(hsalt);
key(hpass);
}

int[] buf = new int[openbsd_iv.length];
System.arraycopy(openbsd_iv, 0, buf, 0, openbsd_iv.length);
for (int i = 0; i < 8; i += 2) {
for (int j = 0; j < 64; j++) {
encipher(buf, i);
}
}

for (int i = 0, j = 0; i < buf.length; i++) {
// Output of this is little endian
output[j++] = (byte)(buf[i] & 0xff);
output[j++] = (byte)((buf[i] >> 8) & 0xff);
output[j++] = (byte)((buf[i] >> 16) & 0xff);
output[j++] = (byte)((buf[i] >> 24) & 0xff);
}
}

/**
* Compatibility with new OpenBSD function.
*/
public void pbkdf(byte[] password, byte[] salt, int rounds, byte[] output) {
try {
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");

int nblocks = (output.length + 31) / 32;
byte[] hpass = sha512.digest(password);

byte[] hsalt = new byte[64];
byte[] block_b = new byte[4];
byte[] out = new byte[32];
byte[] tmp = new byte[32];
for (int block = 1; block <= nblocks; block++) {
// Block count is in big endian
block_b[0] = (byte) ((block >> 24) & 0xFF);
block_b[1] = (byte) ((block >> 16) & 0xFF);
block_b[2] = (byte) ((block >> 8) & 0xFF);
block_b[3] = (byte) (block & 0xFF);

sha512.reset();
sha512.update(salt);
sha512.update(block_b);
sha512.digest(hsalt, 0, hsalt.length);

hash(hpass, hsalt, out);
System.arraycopy(out, 0, tmp, 0, out.length);

for (int round = 1; round < rounds; round++) {
sha512.reset();
sha512.update(tmp);
sha512.digest(hsalt, 0, hsalt.length);

hash(hpass, hsalt, tmp);

for (int i = 0; i < tmp.length; i++) {
out[i] ^= tmp[i];
}
}

for (int i = 0; i < out.length; i++) {
int idx = i * nblocks + (block - 1);
if (idx < output.length) {
output[idx] = out[i];
}
}
}
} catch (DigestException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

/**
* Perform the central password hashing step in the
* bcrypt scheme
Expand Down
99 changes: 98 additions & 1 deletion jbcrypt/src/test/java/org/mindrot/jbcrypt/TestBCrypt.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@

package org.mindrot.jbcrypt;

import org.mindrot.jbcrypt.BCrypt;
import junit.framework.TestCase;

import java.util.Arrays;

/**
* JUnit unit tests for BCrypt routines
* @author Damien Miller
Expand Down Expand Up @@ -194,4 +195,100 @@ public void testInternationalChars() {
System.out.println("");
}

private static class BCryptHashTV {
private final byte[] pass;
private final byte[] salt;
private final byte[] out;

public BCryptHashTV(byte[] pass, byte[] salt, byte[] out) {
this.pass = pass;
this.salt = salt;
this.out = out;
}
}

BCryptHashTV[] bcrypt_hash_test_vectors = new BCryptHashTV[]{
new BCryptHashTV(
new byte[]{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
new byte[]{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
new byte[]{
(byte) 0x46, (byte) 0x02, (byte) 0x86, (byte) 0xe9, (byte) 0x72, (byte) 0xfa, (byte) 0x83, (byte) 0x3f, (byte) 0x8b, (byte) 0x12, (byte) 0x83, (byte) 0xad, (byte) 0x8f, (byte) 0xa9, (byte) 0x19, (byte) 0xfa,
(byte) 0x29, (byte) 0xbd, (byte) 0xe2, (byte) 0x0e, (byte) 0x23, (byte) 0x32, (byte) 0x9e, (byte) 0x77, (byte) 0x4d, (byte) 0x84, (byte) 0x22, (byte) 0xba, (byte) 0xc0, (byte) 0xa7, (byte) 0x92, (byte) 0x6c,
}),
new BCryptHashTV(
new byte[] {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, },
new byte[] {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, },
new byte[] {
(byte) 0xc6, (byte) 0xa9, (byte) 0x5f, (byte) 0xe6, (byte) 0x41, (byte) 0x31, (byte) 0x15, (byte) 0xfb, (byte) 0x57, (byte) 0xe9, (byte) 0x9f, (byte) 0x75, (byte) 0x74, (byte) 0x98, (byte) 0xe8, (byte) 0x5d,
(byte) 0xa3, (byte) 0xc6, (byte) 0xe1, (byte) 0xdf, (byte) 0x0c, (byte) 0x3c, (byte) 0x93, (byte) 0xaa, (byte) 0x97, (byte) 0x5c, (byte) 0x54, (byte) 0x8a, (byte) 0x34, (byte) 0x43, (byte) 0x26, (byte) 0xf8,
}),
};

public void testBCryptHashTestVectors() throws Exception {
System.out.print("BCrypt.hash w/ known vectors: ");
for (BCryptHashTV tv : bcrypt_hash_test_vectors) {
byte[] output = new byte[tv.out.length];
new BCrypt().hash(tv.pass, tv.salt, output);
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}

private static class BCryptPbkdfTV {
private final byte[] pass;
private final byte[] salt;
private final int rounds;
private final byte[] out;

public BCryptPbkdfTV(byte[] pass, byte[] salt, int rounds, byte[] out) {
this.pass = pass;
this.salt = salt;
this.rounds = rounds;
this.out = out;
}
}

BCryptPbkdfTV[] bcrypt_pbkdf_test_vectors = new BCryptPbkdfTV[]{
new BCryptPbkdfTV("password".getBytes(), "salt".getBytes(), 4, new byte[]{
(byte) 0x5b, (byte) 0xbf, (byte) 0x0c, (byte) 0xc2, (byte) 0x93, (byte) 0x58, (byte) 0x7f, (byte) 0x1c, (byte) 0x36, (byte) 0x35, (byte) 0x55, (byte) 0x5c, (byte) 0x27, (byte) 0x79, (byte) 0x65, (byte) 0x98,
(byte) 0xd4, (byte) 0x7e, (byte) 0x57, (byte) 0x90, (byte) 0x71, (byte) 0xbf, (byte) 0x42, (byte) 0x7e, (byte) 0x9d, (byte) 0x8f, (byte) 0xbe, (byte) 0x84, (byte) 0x2a, (byte) 0xba, (byte) 0x34, (byte) 0xd9,
}),
new BCryptPbkdfTV("password".getBytes(), "salt".getBytes(), 8, new byte[]{
(byte) 0xe1, (byte) 0x36, (byte) 0x7e, (byte) 0xc5, (byte) 0x15, (byte) 0x1a, (byte) 0x33, (byte) 0xfa, (byte) 0xac, (byte) 0x4c, (byte) 0xc1, (byte) 0xc1, (byte) 0x44, (byte) 0xcd, (byte) 0x23, (byte) 0xfa,
(byte) 0x15, (byte) 0xd5, (byte) 0x54, (byte) 0x84, (byte) 0x93, (byte) 0xec, (byte) 0xc9, (byte) 0x9b, (byte) 0x9b, (byte) 0x5d, (byte) 0x9c, (byte) 0x0d, (byte) 0x3b, (byte) 0x27, (byte) 0xbe, (byte) 0xc7,
(byte) 0x62, (byte) 0x27, (byte) 0xea, (byte) 0x66, (byte) 0x08, (byte) 0x8b, (byte) 0x84, (byte) 0x9b, (byte) 0x20, (byte) 0xab, (byte) 0x7a, (byte) 0xa4, (byte) 0x78, (byte) 0x01, (byte) 0x02, (byte) 0x46,
(byte) 0xe7, (byte) 0x4b, (byte) 0xba, (byte) 0x51, (byte) 0x72, (byte) 0x3f, (byte) 0xef, (byte) 0xa9, (byte) 0xf9, (byte) 0x47, (byte) 0x4d, (byte) 0x65, (byte) 0x08, (byte) 0x84, (byte) 0x5e, (byte) 0x8d}),
new BCryptPbkdfTV("password".getBytes(), "salt".getBytes(), 42, new byte[]{
(byte) 0x83, (byte) 0x3c, (byte) 0xf0, (byte) 0xdc, (byte) 0xf5, (byte) 0x6d, (byte) 0xb6, (byte) 0x56, (byte) 0x08, (byte) 0xe8, (byte) 0xf0, (byte) 0xdc, (byte) 0x0c, (byte) 0xe8, (byte) 0x82, (byte) 0xbd}),
};

public void testBCryptPbkdfTestVectors() throws Exception {
System.out.print("BCrypt.pbkdf w/ known vectors: ");
for (BCryptPbkdfTV tv : bcrypt_pbkdf_test_vectors) {
byte[] output = new byte[tv.out.length];
new BCrypt().pbkdf(tv.pass, tv.salt, tv.rounds, output);
assertEquals(Arrays.toString(tv.out), Arrays.toString(output));
System.out.print(".");
}
System.out.println("");
}
}

0 comments on commit 37a5a77

Please sign in to comment.