mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 15:20:54 +03:00
Removed unused bcrypt password hashing methods (#852)
Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
@@ -14,11 +14,9 @@
|
|||||||
|
|
||||||
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
|
package com.hierynomus.sshj.userauth.keyprovider.bcrypt;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.DigestException;
|
import java.security.DigestException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BCrypt implements OpenBSD-style Blowfish password hashing using
|
* BCrypt implements OpenBSD-style Blowfish password hashing using
|
||||||
@@ -30,32 +28,6 @@ import java.security.SecureRandom;
|
|||||||
* based on Bruce Schneier's Blowfish cipher. The work factor of
|
* based on Bruce Schneier's Blowfish cipher. The work factor of
|
||||||
* the algorithm is parameterised, so it can be increased as
|
* the algorithm is parameterised, so it can be increased as
|
||||||
* computers get faster.
|
* computers get faster.
|
||||||
* <p>
|
|
||||||
* Usage is really simple. To hash a password for the first time,
|
|
||||||
* call the hashpw method with a random salt, like this:
|
|
||||||
* <p>
|
|
||||||
* <code>
|
|
||||||
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br>
|
|
||||||
* </code>
|
|
||||||
* <p>
|
|
||||||
* To check whether a plaintext password matches one that has been
|
|
||||||
* hashed previously, use the checkpw method:
|
|
||||||
* <p>
|
|
||||||
* <code>
|
|
||||||
* if (BCrypt.checkpw(candidate_password, stored_hash))<br>
|
|
||||||
* System.out.println("It matches");<br>
|
|
||||||
* else<br>
|
|
||||||
* System.out.println("It does not match");<br>
|
|
||||||
* </code>
|
|
||||||
* <p>
|
|
||||||
* The gensalt() method takes an optional parameter (log_rounds)
|
|
||||||
* that determines the computational complexity of the hashing:
|
|
||||||
* <p>
|
|
||||||
* <code>
|
|
||||||
* String strong_salt = BCrypt.gensalt(10)<br>
|
|
||||||
* String stronger_salt = BCrypt.gensalt(12)<br>
|
|
||||||
* </code>
|
|
||||||
* <p>
|
|
||||||
* The amount of work increases exponentially (2**log_rounds), so
|
* The amount of work increases exponentially (2**log_rounds), so
|
||||||
* each increment is twice as much work. The default log_rounds is
|
* each increment is twice as much work. The default log_rounds is
|
||||||
* 10, and the valid range is 4 to 30.
|
* 10, and the valid range is 4 to 30.
|
||||||
@@ -64,22 +36,18 @@ import java.security.SecureRandom;
|
|||||||
* @version 0.2
|
* @version 0.2
|
||||||
*/
|
*/
|
||||||
public class BCrypt {
|
public class BCrypt {
|
||||||
// BCrypt parameters
|
|
||||||
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
|
|
||||||
private static final int BCRYPT_SALT_LEN = 16;
|
|
||||||
|
|
||||||
// Blowfish parameters
|
// Blowfish parameters
|
||||||
private static final int BLOWFISH_NUM_ROUNDS = 16;
|
private static final int BLOWFISH_NUM_ROUNDS = 16;
|
||||||
|
|
||||||
// Initial contents of key schedule
|
// Initial contents of key schedule
|
||||||
private static final int P_orig[] = {
|
private static final int[] P_orig = {
|
||||||
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
|
||||||
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
||||||
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
|
||||||
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
||||||
0x9216d5d9, 0x8979fb1b
|
0x9216d5d9, 0x8979fb1b
|
||||||
};
|
};
|
||||||
private static final int S_orig[] = {
|
private static final int[] S_orig = {
|
||||||
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
|
||||||
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
|
||||||
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
|
||||||
@@ -344,149 +312,9 @@ public class BCrypt {
|
|||||||
0x66697368, 0x53776174, 0x44796e61, 0x6d697465,
|
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.
|
|
||||||
static private final int bf_crypt_ciphertext[] = {
|
|
||||||
0x4f727068, 0x65616e42, 0x65686f6c,
|
|
||||||
0x64657253, 0x63727944, 0x6f756274
|
|
||||||
};
|
|
||||||
|
|
||||||
// Table for Base64 encoding
|
|
||||||
static private final char base64_code[] = {
|
|
||||||
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
|
||||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
|
|
||||||
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
|
||||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
|
||||||
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
|
|
||||||
'6', '7', '8', '9'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Table for Base64 decoding
|
|
||||||
static private final byte index_64[] = {
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
|
|
||||||
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
|
|
||||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
||||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
|
||||||
-1, -1, -1, -1, -1, -1, 28, 29, 30,
|
|
||||||
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
|
||||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
|
||||||
51, 52, 53, -1, -1, -1, -1, -1
|
|
||||||
};
|
|
||||||
|
|
||||||
// Expanded Blowfish key
|
// Expanded Blowfish key
|
||||||
private int P[];
|
private int[] P;
|
||||||
private int S[];
|
private int[] S;
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode a byte array using bcrypt's slightly-modified base64
|
|
||||||
* encoding scheme. Note that this is *not* compatible with
|
|
||||||
* the standard MIME-base64 encoding.
|
|
||||||
*
|
|
||||||
* @param d the byte array to encode
|
|
||||||
* @param len the number of bytes to encode
|
|
||||||
* @return base64-encoded string
|
|
||||||
* @exception IllegalArgumentException if the length is invalid
|
|
||||||
*/
|
|
||||||
private static String encode_base64(byte d[], int len)
|
|
||||||
throws IllegalArgumentException {
|
|
||||||
int off = 0;
|
|
||||||
StringBuffer rs = new StringBuffer();
|
|
||||||
int c1, c2;
|
|
||||||
|
|
||||||
if (len <= 0 || len > d.length)
|
|
||||||
throw new IllegalArgumentException ("Invalid len");
|
|
||||||
|
|
||||||
while (off < len) {
|
|
||||||
c1 = d[off++] & 0xff;
|
|
||||||
rs.append(base64_code[(c1 >> 2) & 0x3f]);
|
|
||||||
c1 = (c1 & 0x03) << 4;
|
|
||||||
if (off >= len) {
|
|
||||||
rs.append(base64_code[c1 & 0x3f]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
c2 = d[off++] & 0xff;
|
|
||||||
c1 |= (c2 >> 4) & 0x0f;
|
|
||||||
rs.append(base64_code[c1 & 0x3f]);
|
|
||||||
c1 = (c2 & 0x0f) << 2;
|
|
||||||
if (off >= len) {
|
|
||||||
rs.append(base64_code[c1 & 0x3f]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
c2 = d[off++] & 0xff;
|
|
||||||
c1 |= (c2 >> 6) & 0x03;
|
|
||||||
rs.append(base64_code[c1 & 0x3f]);
|
|
||||||
rs.append(base64_code[c2 & 0x3f]);
|
|
||||||
}
|
|
||||||
return rs.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up the 3 bits base64-encoded by the specified character,
|
|
||||||
* range-checking againt conversion table
|
|
||||||
* @param x the base64-encoded value
|
|
||||||
* @return the decoded value of x
|
|
||||||
*/
|
|
||||||
private static byte char64(char x) {
|
|
||||||
if ((int)x < 0 || (int)x > index_64.length)
|
|
||||||
return -1;
|
|
||||||
return index_64[(int)x];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a string encoded using bcrypt's base64 scheme to a
|
|
||||||
* byte array. Note that this is *not* compatible with
|
|
||||||
* the standard MIME-base64 encoding.
|
|
||||||
* @param s the string to decode
|
|
||||||
* @param maxolen the maximum number of bytes to decode
|
|
||||||
* @return an array containing the decoded bytes
|
|
||||||
* @throws IllegalArgumentException if maxolen is invalid
|
|
||||||
*/
|
|
||||||
private static byte[] decode_base64(String s, int maxolen)
|
|
||||||
throws IllegalArgumentException {
|
|
||||||
StringBuffer rs = new StringBuffer();
|
|
||||||
int off = 0, slen = s.length(), olen = 0;
|
|
||||||
byte ret[];
|
|
||||||
byte c1, c2, c3, c4, o;
|
|
||||||
|
|
||||||
if (maxolen <= 0)
|
|
||||||
throw new IllegalArgumentException ("Invalid maxolen");
|
|
||||||
|
|
||||||
while (off < slen - 1 && olen < maxolen) {
|
|
||||||
c1 = char64(s.charAt(off++));
|
|
||||||
c2 = char64(s.charAt(off++));
|
|
||||||
if (c1 == -1 || c2 == -1)
|
|
||||||
break;
|
|
||||||
o = (byte)(c1 << 2);
|
|
||||||
o |= (c2 & 0x30) >> 4;
|
|
||||||
rs.append((char)o);
|
|
||||||
if (++olen >= maxolen || off >= slen)
|
|
||||||
break;
|
|
||||||
c3 = char64(s.charAt(off++));
|
|
||||||
if (c3 == -1)
|
|
||||||
break;
|
|
||||||
o = (byte)((c2 & 0x0f) << 4);
|
|
||||||
o |= (c3 & 0x3c) >> 2;
|
|
||||||
rs.append((char)o);
|
|
||||||
if (++olen >= maxolen || off >= slen)
|
|
||||||
break;
|
|
||||||
c4 = char64(s.charAt(off++));
|
|
||||||
o = (byte)((c3 & 0x03) << 6);
|
|
||||||
o |= c4;
|
|
||||||
rs.append((char)o);
|
|
||||||
++olen;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = new byte[olen];
|
|
||||||
for (off = 0; off < olen; off++)
|
|
||||||
ret[off] = (byte)rs.charAt(off);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blowfish encipher a single 64-bit block encoded as
|
* Blowfish encipher a single 64-bit block encoded as
|
||||||
@@ -494,7 +322,7 @@ public class BCrypt {
|
|||||||
* @param lr an array containing the two 32-bit half blocks
|
* @param lr an array containing the two 32-bit half blocks
|
||||||
* @param off the position in the array of the blocks
|
* @param off the position in the array of the blocks
|
||||||
*/
|
*/
|
||||||
private final void encipher(int lr[], int off) {
|
private void encipher(int[] lr, int off) {
|
||||||
int i, n, l = lr[off], r = lr[off + 1];
|
int i, n, l = lr[off], r = lr[off + 1];
|
||||||
|
|
||||||
l ^= P[0];
|
l ^= P[0];
|
||||||
@@ -524,7 +352,7 @@ public class BCrypt {
|
|||||||
* current offset into data
|
* current offset into data
|
||||||
* @return the next word of material from data
|
* @return the next word of material from data
|
||||||
*/
|
*/
|
||||||
private static int streamtoword(byte data[], int offp[]) {
|
private static int streamtoword(byte[] data, int[] offp) {
|
||||||
int i;
|
int i;
|
||||||
int word = 0;
|
int word = 0;
|
||||||
int off = offp[0];
|
int off = offp[0];
|
||||||
@@ -542,18 +370,18 @@ public class BCrypt {
|
|||||||
* Initialise the Blowfish key schedule
|
* Initialise the Blowfish key schedule
|
||||||
*/
|
*/
|
||||||
private void init_key() {
|
private void init_key() {
|
||||||
P = (int[])P_orig.clone();
|
P = P_orig.clone();
|
||||||
S = (int[])S_orig.clone();
|
S = S_orig.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key the Blowfish cipher
|
* Key the Blowfish cipher
|
||||||
* @param key an array containing the key
|
* @param key an array containing the key
|
||||||
*/
|
*/
|
||||||
private void key(byte key[]) {
|
private void key(byte[] key) {
|
||||||
int i;
|
int i;
|
||||||
int koffp[] = { 0 };
|
int[] koffp = { 0 };
|
||||||
int lr[] = { 0, 0 };
|
int[] lr = { 0, 0 };
|
||||||
int plen = P.length, slen = S.length;
|
int plen = P.length, slen = S.length;
|
||||||
|
|
||||||
for (i = 0; i < plen; i++)
|
for (i = 0; i < plen; i++)
|
||||||
@@ -579,10 +407,10 @@ public class BCrypt {
|
|||||||
* @param data salt information
|
* @param data salt information
|
||||||
* @param key password information
|
* @param key password information
|
||||||
*/
|
*/
|
||||||
private void ekskey(byte data[], byte key[]) {
|
private void ekskey(byte[] data, byte[] key) {
|
||||||
int i;
|
int i;
|
||||||
int koffp[] = { 0 }, doffp[] = { 0 };
|
int[] koffp = { 0 }, doffp = { 0 };
|
||||||
int lr[] = { 0, 0 };
|
int[] lr = { 0, 0 };
|
||||||
int plen = P.length, slen = S.length;
|
int plen = P.length, slen = S.length;
|
||||||
|
|
||||||
for (i = 0; i < plen; i++)
|
for (i = 0; i < plen; i++)
|
||||||
@@ -687,183 +515,4 @@ public class BCrypt {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform the central password hashing step in the
|
|
||||||
* bcrypt scheme
|
|
||||||
* @param password the password to hash
|
|
||||||
* @param salt the binary salt to hash with the password
|
|
||||||
* @param log_rounds the binary logarithm of the number
|
|
||||||
* of rounds of hashing to apply
|
|
||||||
* @param cdata the plaintext to encrypt
|
|
||||||
* @return an array containing the binary hashed password
|
|
||||||
*/
|
|
||||||
public byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
|
|
||||||
int cdata[]) {
|
|
||||||
int rounds, i, j;
|
|
||||||
int clen = cdata.length;
|
|
||||||
byte ret[];
|
|
||||||
|
|
||||||
if (log_rounds < 4 || log_rounds > 30)
|
|
||||||
throw new IllegalArgumentException ("Bad number of rounds");
|
|
||||||
rounds = 1 << log_rounds;
|
|
||||||
if (salt.length != BCRYPT_SALT_LEN)
|
|
||||||
throw new IllegalArgumentException ("Bad salt length");
|
|
||||||
|
|
||||||
init_key();
|
|
||||||
ekskey(salt, password);
|
|
||||||
for (i = 0; i != rounds; i++) {
|
|
||||||
key(password);
|
|
||||||
key(salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < 64; i++) {
|
|
||||||
for (j = 0; j < (clen >> 1); j++)
|
|
||||||
encipher(cdata, j << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = new byte[clen * 4];
|
|
||||||
for (i = 0, j = 0; i < clen; i++) {
|
|
||||||
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
|
|
||||||
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
|
|
||||||
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
|
|
||||||
ret[j++] = (byte)(cdata[i] & 0xff);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash a password using the OpenBSD bcrypt scheme
|
|
||||||
* @param password the password to hash
|
|
||||||
* @param salt the salt to hash with (perhaps generated
|
|
||||||
* using BCrypt.gensalt)
|
|
||||||
* @return the hashed password
|
|
||||||
*/
|
|
||||||
public static String hashpw(String password, String salt) {
|
|
||||||
BCrypt B;
|
|
||||||
String real_salt;
|
|
||||||
byte passwordb[], saltb[], hashed[];
|
|
||||||
char minor = (char)0;
|
|
||||||
int rounds, off = 0;
|
|
||||||
StringBuffer rs = new StringBuffer();
|
|
||||||
|
|
||||||
if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
|
|
||||||
throw new IllegalArgumentException ("Invalid salt version");
|
|
||||||
if (salt.charAt(2) == '$')
|
|
||||||
off = 3;
|
|
||||||
else {
|
|
||||||
minor = salt.charAt(2);
|
|
||||||
if (minor != 'a' || salt.charAt(3) != '$')
|
|
||||||
throw new IllegalArgumentException ("Invalid salt revision");
|
|
||||||
off = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract number of rounds
|
|
||||||
if (salt.charAt(off + 2) > '$')
|
|
||||||
throw new IllegalArgumentException ("Missing salt rounds");
|
|
||||||
rounds = Integer.parseInt(salt.substring(off, off + 2));
|
|
||||||
|
|
||||||
real_salt = salt.substring(off + 3, off + 25);
|
|
||||||
try {
|
|
||||||
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException uee) {
|
|
||||||
throw new AssertionError("UTF-8 is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
|
|
||||||
|
|
||||||
B = new BCrypt();
|
|
||||||
hashed = B.crypt_raw(passwordb, saltb, rounds,
|
|
||||||
(int[])bf_crypt_ciphertext.clone());
|
|
||||||
|
|
||||||
rs.append("$2");
|
|
||||||
if (minor >= 'a')
|
|
||||||
rs.append(minor);
|
|
||||||
rs.append("$");
|
|
||||||
if (rounds < 10)
|
|
||||||
rs.append("0");
|
|
||||||
if (rounds > 30) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"rounds exceeds maximum (30)");
|
|
||||||
}
|
|
||||||
rs.append(Integer.toString(rounds));
|
|
||||||
rs.append("$");
|
|
||||||
rs.append(encode_base64(saltb, saltb.length));
|
|
||||||
rs.append(encode_base64(hashed,
|
|
||||||
bf_crypt_ciphertext.length * 4 - 1));
|
|
||||||
return rs.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a salt for use with the BCrypt.hashpw() method
|
|
||||||
* @param log_rounds the log2 of the number of rounds of
|
|
||||||
* hashing to apply - the work factor therefore increases as
|
|
||||||
* 2**log_rounds.
|
|
||||||
* @param random an instance of SecureRandom to use
|
|
||||||
* @return an encoded salt value
|
|
||||||
*/
|
|
||||||
public static String gensalt(int log_rounds, SecureRandom random) {
|
|
||||||
StringBuffer rs = new StringBuffer();
|
|
||||||
byte rnd[] = new byte[BCRYPT_SALT_LEN];
|
|
||||||
|
|
||||||
random.nextBytes(rnd);
|
|
||||||
|
|
||||||
rs.append("$2a$");
|
|
||||||
if (log_rounds < 10)
|
|
||||||
rs.append("0");
|
|
||||||
if (log_rounds > 30) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"log_rounds exceeds maximum (30)");
|
|
||||||
}
|
|
||||||
rs.append(Integer.toString(log_rounds));
|
|
||||||
rs.append("$");
|
|
||||||
rs.append(encode_base64(rnd, rnd.length));
|
|
||||||
return rs.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a salt for use with the BCrypt.hashpw() method
|
|
||||||
* @param log_rounds the log2 of the number of rounds of
|
|
||||||
* hashing to apply - the work factor therefore increases as
|
|
||||||
* 2**log_rounds.
|
|
||||||
* @return an encoded salt value
|
|
||||||
*/
|
|
||||||
public static String gensalt(int log_rounds) {
|
|
||||||
return gensalt(log_rounds, new SecureRandom());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a salt for use with the BCrypt.hashpw() method,
|
|
||||||
* selecting a reasonable default for the number of hashing
|
|
||||||
* rounds to apply
|
|
||||||
* @return an encoded salt value
|
|
||||||
*/
|
|
||||||
public static String gensalt() {
|
|
||||||
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that a plaintext password matches a previously hashed
|
|
||||||
* one
|
|
||||||
* @param plaintext the plaintext password to verify
|
|
||||||
* @param hashed the previously-hashed password
|
|
||||||
* @return true if the passwords match, false otherwise
|
|
||||||
*/
|
|
||||||
public static boolean checkpw(String plaintext, String hashed) {
|
|
||||||
byte hashed_bytes[];
|
|
||||||
byte try_bytes[];
|
|
||||||
try {
|
|
||||||
String try_pw = hashpw(plaintext, hashed);
|
|
||||||
hashed_bytes = hashed.getBytes("UTF-8");
|
|
||||||
try_bytes = try_pw.getBytes("UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException uee) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (hashed_bytes.length != try_bytes.length)
|
|
||||||
return false;
|
|
||||||
byte ret = 0;
|
|
||||||
for (int i = 0; i < try_bytes.length; i++)
|
|
||||||
ret |= hashed_bytes[i] ^ try_bytes[i];
|
|
||||||
return ret == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import org.junit.Test;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JUnit unit tests for BCrypt routines
|
* JUnit unit tests for BCrypt routines
|
||||||
@@ -28,168 +26,6 @@ import static org.junit.Assert.assertTrue;
|
|||||||
* @version 0.2
|
* @version 0.2
|
||||||
*/
|
*/
|
||||||
public class BCryptTest {
|
public class BCryptTest {
|
||||||
String[][] test_vectors = {
|
|
||||||
{ "",
|
|
||||||
"$2a$06$DCq7YPn5Rq63x1Lad4cll.",
|
|
||||||
"$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." },
|
|
||||||
{ "",
|
|
||||||
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.",
|
|
||||||
"$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" },
|
|
||||||
{ "",
|
|
||||||
"$2a$10$k1wbIrmNyFAPwPVPSVa/ze",
|
|
||||||
"$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" },
|
|
||||||
{ "",
|
|
||||||
"$2a$12$k42ZFHFWqBp3vWli.nIn8u",
|
|
||||||
"$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" },
|
|
||||||
{ "a",
|
|
||||||
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO",
|
|
||||||
"$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" },
|
|
||||||
{ "a",
|
|
||||||
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfe",
|
|
||||||
"$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." },
|
|
||||||
{ "a",
|
|
||||||
"$2a$10$k87L/MF28Q673VKh8/cPi.",
|
|
||||||
"$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" },
|
|
||||||
{ "a",
|
|
||||||
"$2a$12$8NJH3LsPrANStV6XtBakCe",
|
|
||||||
"$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" },
|
|
||||||
{ "abc",
|
|
||||||
"$2a$06$If6bvum7DFjUnE9p2uDeDu",
|
|
||||||
"$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" },
|
|
||||||
{ "abc",
|
|
||||||
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7O",
|
|
||||||
"$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" },
|
|
||||||
{ "abc",
|
|
||||||
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.",
|
|
||||||
"$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" },
|
|
||||||
{ "abc",
|
|
||||||
"$2a$12$EXRkfkdmXn2gzds2SSitu.",
|
|
||||||
"$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" },
|
|
||||||
{ "abcdefghijklmnopqrstuvwxyz",
|
|
||||||
"$2a$06$.rCVZVOThsIa97pEDOxvGu",
|
|
||||||
"$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" },
|
|
||||||
{ "abcdefghijklmnopqrstuvwxyz",
|
|
||||||
"$2a$08$aTsUwsyowQuzRrDqFflhge",
|
|
||||||
"$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." },
|
|
||||||
{ "abcdefghijklmnopqrstuvwxyz",
|
|
||||||
"$2a$10$fVH8e28OQRj9tqiDXs1e1u",
|
|
||||||
"$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" },
|
|
||||||
{ "abcdefghijklmnopqrstuvwxyz",
|
|
||||||
"$2a$12$D4G5f18o7aMMfwasBL7Gpu",
|
|
||||||
"$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" },
|
|
||||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
|
||||||
"$2a$06$fPIsBO8qRqkjj273rfaOI.",
|
|
||||||
"$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" },
|
|
||||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
|
||||||
"$2a$08$Eq2r4G/76Wv39MzSX262hu",
|
|
||||||
"$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" },
|
|
||||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
|
||||||
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe",
|
|
||||||
"$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" },
|
|
||||||
{ "~!@#$%^&*() ~!@#$%^&*()PNBFRD",
|
|
||||||
"$2a$12$WApznUOJfkEGSmYRfnkrPO",
|
|
||||||
"$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" },
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method for 'BCrypt.hashpw(String, String)'
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testHashpw() {
|
|
||||||
System.out.print("BCrypt.hashpw(): ");
|
|
||||||
for (int i = 0; i < test_vectors.length; i++) {
|
|
||||||
String plain = test_vectors[i][0];
|
|
||||||
String salt = test_vectors[i][1];
|
|
||||||
String expected = test_vectors[i][2];
|
|
||||||
String hashed = BCrypt.hashpw(plain, salt);
|
|
||||||
assertEquals(hashed, expected);
|
|
||||||
System.out.print(".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method for 'BCrypt.gensalt(int)'
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGensaltInt() {
|
|
||||||
System.out.print("BCrypt.gensalt(log_rounds):");
|
|
||||||
for (int i = 4; i <= 12; i++) {
|
|
||||||
System.out.print(" " + i + ":");
|
|
||||||
for (int j = 0; j < test_vectors.length; j += 4) {
|
|
||||||
String plain = test_vectors[j][0];
|
|
||||||
String salt = BCrypt.gensalt(i);
|
|
||||||
String hashed1 = BCrypt.hashpw(plain, salt);
|
|
||||||
String hashed2 = BCrypt.hashpw(plain, hashed1);
|
|
||||||
assertEquals(hashed1, hashed2);
|
|
||||||
System.out.print(".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method for 'BCrypt.gensalt()'
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGensalt() {
|
|
||||||
System.out.print("BCrypt.gensalt(): ");
|
|
||||||
for (int i = 0; i < test_vectors.length; i += 4) {
|
|
||||||
String plain = test_vectors[i][0];
|
|
||||||
String salt = BCrypt.gensalt();
|
|
||||||
String hashed1 = BCrypt.hashpw(plain, salt);
|
|
||||||
String hashed2 = BCrypt.hashpw(plain, hashed1);
|
|
||||||
assertEquals(hashed1, hashed2);
|
|
||||||
System.out.print(".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method for 'BCrypt.checkpw(String, String)'
|
|
||||||
* expecting success
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testCheckpw_success() {
|
|
||||||
System.out.print("BCrypt.checkpw w/ good passwords: ");
|
|
||||||
for (int i = 0; i < test_vectors.length; i++) {
|
|
||||||
String plain = test_vectors[i][0];
|
|
||||||
String expected = test_vectors[i][2];
|
|
||||||
assertTrue(BCrypt.checkpw(plain, expected));
|
|
||||||
System.out.print(".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test method for 'BCrypt.checkpw(String, String)'
|
|
||||||
* expecting failure
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testCheckpw_failure() {
|
|
||||||
System.out.print("BCrypt.checkpw w/ bad passwords: ");
|
|
||||||
for (int i = 0; i < test_vectors.length; i++) {
|
|
||||||
int broken_index = (i + 4) % test_vectors.length;
|
|
||||||
String plain = test_vectors[i][0];
|
|
||||||
String expected = test_vectors[broken_index][2];
|
|
||||||
assertFalse(BCrypt.checkpw(plain, expected));
|
|
||||||
System.out.print(".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test for correct hashing of non-US-ASCII passwords
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testInternationalChars() {
|
|
||||||
System.out.print("BCrypt.hashpw w/ international chars: ");
|
|
||||||
String pw1 = "\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605";
|
|
||||||
String pw2 = "????????";
|
|
||||||
|
|
||||||
String h1 = BCrypt.hashpw(pw1, BCrypt.gensalt());
|
|
||||||
assertFalse(BCrypt.checkpw(pw2, h1));
|
|
||||||
System.out.print(".");
|
|
||||||
|
|
||||||
String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt());
|
|
||||||
assertFalse(BCrypt.checkpw(pw1, h2));
|
|
||||||
System.out.print(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BCryptHashTV {
|
private static class BCryptHashTV {
|
||||||
private final byte[] pass;
|
private final byte[] pass;
|
||||||
|
|||||||
Reference in New Issue
Block a user