Update and Fix PHP bcrypt to login server bcrypt compatibility (#462)
Update jBCrypt from version 0.2 to 0.4 which resolves a security bug with character encoding and corrects an integer overflow bug.
Modify jBCrypt library to handle $2y$ hashes, which are produced by PHP's password_hash bcrypt function. The original jBCrypt library only recognizes $2a$ hashes which makes it harder for a website CMS (content management system) to interface with the server's login and databases.
Credit to: Ariel Salomon for compatibility support of 2b, 2y, and 2x hashes fe14360010 (diff-61d1d5b7b3c721fac60702cf559d70aa)
This commit is contained in:
@@ -11,12 +11,14 @@
|
|||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package tools;
|
package tools;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BCrypt implements OpenBSD-style Blowfish password hashing using
|
* BCrypt implements OpenBSD-style Blowfish password hashing using
|
||||||
* the scheme described in "A Future-Adaptable Password Scheme" by
|
* the scheme described in "A Future-Adaptable Password Scheme" by
|
||||||
@@ -58,16 +60,15 @@ import java.security.SecureRandom;
|
|||||||
* 10, and the valid range is 4 to 30.
|
* 10, and the valid range is 4 to 30.
|
||||||
*
|
*
|
||||||
* @author Damien Miller
|
* @author Damien Miller
|
||||||
* @version 0.2
|
* @version 0.4
|
||||||
*/
|
*/
|
||||||
public class BCrypt {
|
public class BCrypt {
|
||||||
// BCrypt parameters
|
// BCrypt parameters
|
||||||
|
|
||||||
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
|
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
|
||||||
private static final int BCRYPT_SALT_LEN = 16;
|
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,
|
||||||
@@ -334,7 +335,6 @@ public class BCrypt {
|
|||||||
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
|
||||||
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
|
||||||
};
|
};
|
||||||
|
|
||||||
// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
|
// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
|
||||||
// this "ciphertext", but it is really plaintext or an IV. We keep
|
// this "ciphertext", but it is really plaintext or an IV. We keep
|
||||||
// the name to make code comparison easier.
|
// the name to make code comparison easier.
|
||||||
@@ -342,7 +342,6 @@ public class BCrypt {
|
|||||||
0x4f727068, 0x65616e42, 0x65686f6c,
|
0x4f727068, 0x65616e42, 0x65686f6c,
|
||||||
0x64657253, 0x63727944, 0x6f756274
|
0x64657253, 0x63727944, 0x6f756274
|
||||||
};
|
};
|
||||||
|
|
||||||
// Table for Base64 encoding
|
// Table for Base64 encoding
|
||||||
static private final char base64_code[] = {
|
static private final char base64_code[] = {
|
||||||
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||||
@@ -352,7 +351,6 @@ public class BCrypt {
|
|||||||
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
|
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
|
||||||
'6', '7', '8', '9'
|
'6', '7', '8', '9'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Table for Base64 decoding
|
// Table for Base64 decoding
|
||||||
static private final byte index_64[] = {
|
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,
|
||||||
@@ -369,7 +367,6 @@ public class BCrypt {
|
|||||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
||||||
51, 52, 53, -1, -1, -1, -1, -1
|
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[];
|
||||||
@@ -387,11 +384,12 @@ public class BCrypt {
|
|||||||
private static String encode_base64(byte d[], int len)
|
private static String encode_base64(byte d[], int len)
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
int off = 0;
|
int off = 0;
|
||||||
StringBuffer rs = new StringBuffer();
|
StringBuilder rs = new StringBuilder();
|
||||||
int c1, c2;
|
int c1, c2;
|
||||||
|
|
||||||
if (len <= 0 || len > d.length)
|
if (len <= 0 || len > d.length) {
|
||||||
throw new IllegalArgumentException("Invalid len");
|
throw new IllegalArgumentException("Invalid len");
|
||||||
|
}
|
||||||
|
|
||||||
while (off < len) {
|
while (off < len) {
|
||||||
c1 = d[off++] & 0xff;
|
c1 = d[off++] & 0xff;
|
||||||
@@ -424,8 +422,9 @@ public class BCrypt {
|
|||||||
* @return the decoded value of x
|
* @return the decoded value of x
|
||||||
*/
|
*/
|
||||||
private static byte char64(char x) {
|
private static byte char64(char x) {
|
||||||
if ((int)x < 0 || (int)x > index_64.length)
|
if ((int) x < 0 || (int) x > index_64.length) {
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
return index_64[(int) x];
|
return index_64[(int) x];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,32 +439,37 @@ public class BCrypt {
|
|||||||
*/
|
*/
|
||||||
private static byte[] decode_base64(String s, int maxolen)
|
private static byte[] decode_base64(String s, int maxolen)
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
StringBuffer rs = new StringBuffer();
|
StringBuilder rs = new StringBuilder();
|
||||||
int off = 0, slen = s.length(), olen = 0;
|
int off = 0, slen = s.length(), olen = 0;
|
||||||
byte ret[];
|
byte ret[];
|
||||||
byte c1, c2, c3, c4, o;
|
byte c1, c2, c3, c4, o;
|
||||||
|
|
||||||
if (maxolen <= 0)
|
if (maxolen <= 0) {
|
||||||
throw new IllegalArgumentException("Invalid maxolen");
|
throw new IllegalArgumentException("Invalid maxolen");
|
||||||
|
}
|
||||||
|
|
||||||
while (off < slen - 1 && olen < maxolen) {
|
while (off < slen - 1 && olen < maxolen) {
|
||||||
c1 = char64(s.charAt(off++));
|
c1 = char64(s.charAt(off++));
|
||||||
c2 = char64(s.charAt(off++));
|
c2 = char64(s.charAt(off++));
|
||||||
if (c1 == -1 || c2 == -1)
|
if (c1 == -1 || c2 == -1) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
o = (byte) (c1 << 2);
|
o = (byte) (c1 << 2);
|
||||||
o |= (c2 & 0x30) >> 4;
|
o |= (c2 & 0x30) >> 4;
|
||||||
rs.append((char) o);
|
rs.append((char) o);
|
||||||
if (++olen >= maxolen || off >= slen)
|
if (++olen >= maxolen || off >= slen) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
c3 = char64(s.charAt(off++));
|
c3 = char64(s.charAt(off++));
|
||||||
if (c3 == -1)
|
if (c3 == -1) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
o = (byte) ((c2 & 0x0f) << 4);
|
o = (byte) ((c2 & 0x0f) << 4);
|
||||||
o |= (c3 & 0x3c) >> 2;
|
o |= (c3 & 0x3c) >> 2;
|
||||||
rs.append((char) o);
|
rs.append((char) o);
|
||||||
if (++olen >= maxolen || off >= slen)
|
if (++olen >= maxolen || off >= slen) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
c4 = char64(s.charAt(off++));
|
c4 = char64(s.charAt(off++));
|
||||||
o = (byte) ((c3 & 0x03) << 6);
|
o = (byte) ((c3 & 0x03) << 6);
|
||||||
o |= c4;
|
o |= c4;
|
||||||
@@ -474,8 +478,9 @@ public class BCrypt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = new byte[olen];
|
ret = new byte[olen];
|
||||||
for (off = 0; off < olen; off++)
|
for (off = 0; off < olen; off++) {
|
||||||
ret[off] = (byte) rs.charAt(off);
|
ret[off] = (byte) rs.charAt(off);
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,20 +518,50 @@ public class BCrypt {
|
|||||||
* @param data the string to extract the data from
|
* @param data the string to extract the data from
|
||||||
* @param offp a "pointer" (as a one-entry array) to the
|
* @param offp a "pointer" (as a one-entry array) to the
|
||||||
* current offset into data
|
* current offset into data
|
||||||
* @return the next word of material from data
|
* @param signp a "pointer" (as a one-entry array) to the
|
||||||
|
* cumulative flag for non-benign sign extension
|
||||||
|
* @return correct and buggy next word of material from data as int[2]
|
||||||
*/
|
*/
|
||||||
private static int streamtoword(byte data[], int offp[]) {
|
private static int[] streamtowords(byte data[], int offp[], int signp[]) {
|
||||||
int i;
|
int i;
|
||||||
int word = 0;
|
int words[] = { 0, 0 };
|
||||||
int off = offp[0];
|
int off = offp[0];
|
||||||
|
int sign = signp[0];
|
||||||
|
|
||||||
for (i = 0; i < 4; i++) {
|
for (i = 0; i < 4; i++) {
|
||||||
word = (word << 8) | (data[off] & 0xff);
|
words[0] = (words[0] << 8) | (data[off] & 0xff);
|
||||||
|
words[1] = (words[1] << 8) | (int)data[off];
|
||||||
|
if (i > 0) sign |= words[1] & 0x80;
|
||||||
off = (off + 1) % data.length;
|
off = (off + 1) % data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
offp[0] = off;
|
offp[0] = off;
|
||||||
return word;
|
signp[0] = sign;
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cycically extract a word of key material
|
||||||
|
* @param data the string to extract the data from
|
||||||
|
* @param offp a "pointer" (as a one-entry array) to the
|
||||||
|
* current offset into data
|
||||||
|
* @return the next word of material from data
|
||||||
|
*/
|
||||||
|
private static int streamtoword(byte data[], int offp[]) {
|
||||||
|
int signp[] = { 0 };
|
||||||
|
return streamtowords(data, offp, signp)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cycically extract a word of key material, with sign-extension bug
|
||||||
|
* @param data the string to extract the data from
|
||||||
|
* @param offp a "pointer" (as a one-entry array) to the
|
||||||
|
* current offset into data
|
||||||
|
* @return the next word of material from data
|
||||||
|
*/
|
||||||
|
private static int streamtoword_bug(byte data[], int offp[]) {
|
||||||
|
int signp[] = { 0 };
|
||||||
|
return streamtowords(data, offp, signp)[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -540,15 +575,20 @@ public class BCrypt {
|
|||||||
/**
|
/**
|
||||||
* Key the Blowfish cipher
|
* Key the Blowfish cipher
|
||||||
* @param key an array containing the key
|
* @param key an array containing the key
|
||||||
|
* @param sign_ext_bug true to implement the 2x bug
|
||||||
*/
|
*/
|
||||||
private void key(byte key[]) {
|
private void key(byte key[], boolean sign_ext_bug) {
|
||||||
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++) {
|
||||||
|
if (!sign_ext_bug)
|
||||||
P[i] = P[i] ^ streamtoword(key, koffp);
|
P[i] = P[i] ^ streamtoword(key, koffp);
|
||||||
|
else
|
||||||
|
P[i] = P[i] ^ streamtoword_bug(key, koffp);
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < plen; i += 2) {
|
for (i = 0; i < plen; i += 2) {
|
||||||
encipher(lr, 0);
|
encipher(lr, 0);
|
||||||
@@ -569,15 +609,53 @@ public class BCrypt {
|
|||||||
* http://www.openbsd.org/papers/bcrypt-paper.ps
|
* http://www.openbsd.org/papers/bcrypt-paper.ps
|
||||||
* @param data salt information
|
* @param data salt information
|
||||||
* @param key password information
|
* @param key password information
|
||||||
|
* @param sign_ext_bug true to implement the 2x bug
|
||||||
|
* @param safety bit 16 is set when the safety measure is requested
|
||||||
*/
|
*/
|
||||||
private void ekskey(byte data[], byte key[]) {
|
private void ekskey(byte data[], byte key[],
|
||||||
|
boolean sign_ext_bug, int safety) {
|
||||||
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;
|
||||||
|
int signp[] = { 0 }; // non-benign sign-extension flag
|
||||||
|
int diff = 0; // zero iff correct and buggy are same
|
||||||
|
|
||||||
for (i = 0; i < plen; i++)
|
for (i = 0; i < plen; i++) {
|
||||||
P[i] = P[i] ^ streamtoword(key, koffp);
|
int words[] = streamtowords(key, koffp, signp);
|
||||||
|
diff |= words[0] ^ words[1];
|
||||||
|
P[i] = P[i] ^ words[sign_ext_bug ? 1 : 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
int sign = signp[0];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point, "diff" is zero iff the correct and buggy algorithms produced
|
||||||
|
* exactly the same result. If so and if "sign" is non-zero, which indicates
|
||||||
|
* that there was a non-benign sign extension, this means that we have a
|
||||||
|
* collision between the correctly computed hash for this password and a set of
|
||||||
|
* passwords that could be supplied to the buggy algorithm. Our safety measure
|
||||||
|
* is meant to protect from such many-buggy to one-correct collisions, by
|
||||||
|
* deviating from the correct algorithm in such cases. Let's check for this.
|
||||||
|
*/
|
||||||
|
diff |= diff >> 16; /* still zero iff exact match */
|
||||||
|
diff &= 0xffff; /* ditto */
|
||||||
|
diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */
|
||||||
|
sign <<= 9; /* move the non-benign sign extension flag to bit 16 */
|
||||||
|
sign &= ~diff & safety; /* action needed? */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have determined that we need to deviate from the correct algorithm,
|
||||||
|
* flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but
|
||||||
|
* let's stick to it now. It came out of the approach we used above, and it's
|
||||||
|
* not any worse than any other choice we could make.)
|
||||||
|
*
|
||||||
|
* It is crucial that we don't do the same to the expanded key used in the main
|
||||||
|
* Eksblowfish loop. By doing it to only one of these two, we deviate from a
|
||||||
|
* state that could be directly specified by a password to the buggy algorithm
|
||||||
|
* (and to the fully correct one as well, but that's a side-effect).
|
||||||
|
*/
|
||||||
|
P[0] ^= sign;
|
||||||
|
|
||||||
for (i = 0; i < plen; i += 2) {
|
for (i = 0; i < plen; i += 2) {
|
||||||
lr[0] ^= streamtoword(data, doffp);
|
lr[0] ^= streamtoword(data, doffp);
|
||||||
@@ -603,32 +681,37 @@ public class BCrypt {
|
|||||||
* @param salt the binary salt to hash with the password
|
* @param salt the binary salt to hash with the password
|
||||||
* @param log_rounds the binary logarithm of the number
|
* @param log_rounds the binary logarithm of the number
|
||||||
* of rounds of hashing to apply
|
* of rounds of hashing to apply
|
||||||
|
* @param sign_ext_bug true to implement the 2x bug
|
||||||
|
* @param safety bit 16 is set when the safety measure is requested
|
||||||
* @param cdata the plaintext to encrypt
|
* @param cdata the plaintext to encrypt
|
||||||
* @return an array containing the binary hashed password
|
* @return an array containing the binary hashed password
|
||||||
*/
|
*/
|
||||||
public byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
|
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
|
||||||
int cdata[]) {
|
boolean sign_ext_bug, int safety, int cdata[]) {
|
||||||
int rounds, i, j;
|
int rounds, i, j;
|
||||||
int clen = cdata.length;
|
int clen = cdata.length;
|
||||||
byte ret[];
|
byte ret[];
|
||||||
|
|
||||||
if (log_rounds < 4 || log_rounds > 30)
|
if (log_rounds < 4 || log_rounds > 30) {
|
||||||
throw new IllegalArgumentException("Bad number of rounds");
|
throw new IllegalArgumentException("Bad number of rounds");
|
||||||
|
}
|
||||||
rounds = 1 << log_rounds;
|
rounds = 1 << log_rounds;
|
||||||
if (salt.length != BCRYPT_SALT_LEN)
|
if (salt.length != BCRYPT_SALT_LEN) {
|
||||||
throw new IllegalArgumentException("Bad salt length");
|
throw new IllegalArgumentException("Bad salt length");
|
||||||
|
}
|
||||||
|
|
||||||
init_key();
|
init_key();
|
||||||
ekskey(salt, password);
|
ekskey(salt, password, sign_ext_bug, safety);
|
||||||
for (i = 0; i != rounds; i++) {
|
for (i = 0; i != rounds; i++) {
|
||||||
key(password);
|
key(password, sign_ext_bug);
|
||||||
key(salt);
|
key(salt, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < 64; i++) {
|
for (i = 0; i < 64; i++) {
|
||||||
for (j = 0; j < (clen >> 1); j++)
|
for (j = 0; j < (clen >> 1); j++) {
|
||||||
encipher(cdata, j << 1);
|
encipher(cdata, j << 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret = new byte[clen * 4];
|
ret = new byte[clen * 4];
|
||||||
for (i = 0, j = 0; i < clen; i++) {
|
for (i = 0, j = 0; i < clen; i++) {
|
||||||
@@ -640,6 +723,23 @@ public class BCrypt {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts given plaintext to byte representation.
|
||||||
|
* @param plaintext the plaintext password to convert
|
||||||
|
* @return Byte representation of given plaintext.
|
||||||
|
*/
|
||||||
|
private static byte[] stringToBytes(String plaintext) {
|
||||||
|
byte plaintextb[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
plaintextb = plaintext.getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException uee) {
|
||||||
|
throw new AssertionError("UTF-8 is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintextb;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash a password using the OpenBSD bcrypt scheme
|
* Hash a password using the OpenBSD bcrypt scheme
|
||||||
* @param password the password to hash
|
* @param password the password to hash
|
||||||
@@ -648,48 +748,67 @@ public class BCrypt {
|
|||||||
* @return the hashed password
|
* @return the hashed password
|
||||||
*/
|
*/
|
||||||
public static String hashpw(String password, String salt) {
|
public static String hashpw(String password, String salt) {
|
||||||
|
byte passwordb[] = stringToBytes(password);
|
||||||
|
|
||||||
|
return hashpw(passwordb, salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash a password using the OpenBSD bcrypt scheme
|
||||||
|
* @param passwordb the password to hash, as a byte array
|
||||||
|
* @param salt the salt to hash with (perhaps generated
|
||||||
|
* using BCrypt.gensalt)
|
||||||
|
* @return the hashed password
|
||||||
|
*/
|
||||||
|
public static String hashpw(byte passwordb[], String salt) {
|
||||||
BCrypt B;
|
BCrypt B;
|
||||||
String real_salt;
|
String real_salt;
|
||||||
byte passwordb[], saltb[], hashed[];
|
byte saltb[], hashed[];
|
||||||
char minor = (char) 0;
|
char minor = (char) 0;
|
||||||
int rounds, off = 0;
|
int rounds, off = 0;
|
||||||
StringBuffer rs = new StringBuffer();
|
StringBuilder rs = new StringBuilder();
|
||||||
|
|
||||||
if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
|
if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
|
||||||
throw new IllegalArgumentException("Invalid salt version");
|
throw new IllegalArgumentException("Invalid salt version");
|
||||||
if (salt.charAt(2) == '$')
|
}
|
||||||
|
if (salt.charAt(2) == '$') {
|
||||||
off = 3;
|
off = 3;
|
||||||
else {
|
} else {
|
||||||
minor = salt.charAt(2);
|
minor = salt.charAt(2);
|
||||||
if (minor != 'a' || salt.charAt(3) != '$')
|
if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b')
|
||||||
|
|| salt.charAt(3) != '$') {
|
||||||
throw new IllegalArgumentException("Invalid salt revision");
|
throw new IllegalArgumentException("Invalid salt revision");
|
||||||
|
}
|
||||||
off = 4;
|
off = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract number of rounds
|
// Extract number of rounds
|
||||||
if (salt.charAt(off + 2) > '$')
|
if (salt.charAt(off + 2) > '$') {
|
||||||
throw new IllegalArgumentException("Missing salt rounds");
|
throw new IllegalArgumentException("Missing salt rounds");
|
||||||
|
}
|
||||||
rounds = Integer.parseInt(salt.substring(off, off + 2));
|
rounds = Integer.parseInt(salt.substring(off, off + 2));
|
||||||
|
|
||||||
real_salt = salt.substring(off + 3, off + 25);
|
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);
|
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
|
||||||
|
|
||||||
|
if (minor >= 'a') // add null terminator
|
||||||
|
passwordb = Arrays.copyOf(passwordb, passwordb.length + 1);
|
||||||
|
|
||||||
B = new BCrypt();
|
B = new BCrypt();
|
||||||
hashed = B.crypt_raw(passwordb, saltb, rounds,
|
hashed = B.crypt_raw(passwordb, saltb, rounds,
|
||||||
|
minor == 'x', // true for sign extension bug ('2x')
|
||||||
|
minor == 'a' ? 0x10000 : 0, // safety factor for '2a'
|
||||||
(int[])bf_crypt_ciphertext.clone());
|
(int[])bf_crypt_ciphertext.clone());
|
||||||
|
|
||||||
rs.append("$2");
|
rs.append("$2");
|
||||||
if (minor >= 'a')
|
if (minor >= 'a') {
|
||||||
rs.append(minor);
|
rs.append(minor);
|
||||||
|
}
|
||||||
rs.append("$");
|
rs.append("$");
|
||||||
if (rounds < 10)
|
if (rounds < 10) {
|
||||||
rs.append("0");
|
rs.append("0");
|
||||||
|
}
|
||||||
if (rounds > 30) {
|
if (rounds > 30) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"rounds exceeds maximum (30)");
|
"rounds exceeds maximum (30)");
|
||||||
@@ -704,21 +823,36 @@ public class BCrypt {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a salt for use with the BCrypt.hashpw() method
|
* Generate a salt for use with the BCrypt.hashpw() method
|
||||||
|
* @param prefix the prefix value (default $2y)
|
||||||
* @param log_rounds the log2 of the number of rounds of
|
* @param log_rounds the log2 of the number of rounds of
|
||||||
* hashing to apply - the work factor therefore increases as
|
* hashing to apply - the work factor therefore increases as
|
||||||
* 2**log_rounds.
|
* 2**log_rounds.
|
||||||
* @param random an instance of SecureRandom to use
|
* @param random an instance of SecureRandom to use
|
||||||
* @return an encoded salt value
|
* @return an encoded salt value
|
||||||
|
* @exception IllegalArgumentException if prefix or log_rounds is invalid
|
||||||
*/
|
*/
|
||||||
public static String gensalt(int log_rounds, SecureRandom random) {
|
public static String gensalt(String prefix, int log_rounds, SecureRandom random)
|
||||||
StringBuffer rs = new StringBuffer();
|
throws IllegalArgumentException {
|
||||||
|
StringBuilder rs = new StringBuilder();
|
||||||
byte rnd[] = new byte[BCRYPT_SALT_LEN];
|
byte rnd[] = new byte[BCRYPT_SALT_LEN];
|
||||||
|
|
||||||
|
if (!prefix.startsWith("$2") ||
|
||||||
|
(prefix.charAt(2) != 'a' && prefix.charAt(2) != 'y') &&
|
||||||
|
prefix.charAt(2) != 'b') {
|
||||||
|
throw new IllegalArgumentException ("Invalid prefix");
|
||||||
|
}
|
||||||
|
if (log_rounds < 4 || log_rounds > 31) {
|
||||||
|
throw new IllegalArgumentException ("Invalid log_rounds");
|
||||||
|
}
|
||||||
|
|
||||||
random.nextBytes(rnd);
|
random.nextBytes(rnd);
|
||||||
|
|
||||||
rs.append("$2a$");
|
rs.append("$2");
|
||||||
if (log_rounds < 10)
|
rs.append(prefix.charAt(2));
|
||||||
|
rs.append("$");
|
||||||
|
if (log_rounds < 10) {
|
||||||
rs.append("0");
|
rs.append("0");
|
||||||
|
}
|
||||||
if (log_rounds > 30) {
|
if (log_rounds > 30) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"log_rounds exceeds maximum (30)");
|
"log_rounds exceeds maximum (30)");
|
||||||
@@ -731,12 +865,42 @@ public class BCrypt {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a salt for use with the BCrypt.hashpw() method
|
* Generate a salt for use with the BCrypt.hashpw() method
|
||||||
|
* @param prefix the prefix value (default $2y)
|
||||||
* @param log_rounds the log2 of the number of rounds of
|
* @param log_rounds the log2 of the number of rounds of
|
||||||
* hashing to apply - the work factor therefore increases as
|
* hashing to apply - the work factor therefore increases as
|
||||||
* 2**log_rounds.
|
* 2**log_rounds.
|
||||||
* @return an encoded salt value
|
* @return an encoded salt value
|
||||||
|
* @exception IllegalArgumentException if prefix or log_rounds is invalid
|
||||||
*/
|
*/
|
||||||
public static String gensalt(int log_rounds) {
|
public static String gensalt(String prefix, int log_rounds)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
return gensalt(prefix, log_rounds, new SecureRandom());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @exception IllegalArgumentException if prefix or log_rounds is invalid
|
||||||
|
*/
|
||||||
|
public static String gensalt(int log_rounds, SecureRandom random)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
return gensalt("$2y", log_rounds, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @exception IllegalArgumentException if prefix or log_rounds is invalid
|
||||||
|
*/
|
||||||
|
public static String gensalt(int log_rounds)
|
||||||
|
throws IllegalArgumentException {
|
||||||
return gensalt(log_rounds, new SecureRandom());
|
return gensalt(log_rounds, new SecureRandom());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,8 +909,10 @@ public class BCrypt {
|
|||||||
* selecting a reasonable default for the number of hashing
|
* selecting a reasonable default for the number of hashing
|
||||||
* rounds to apply
|
* rounds to apply
|
||||||
* @return an encoded salt value
|
* @return an encoded salt value
|
||||||
|
* @exception IllegalArgumentException if prefix or log_rounds is invalid
|
||||||
*/
|
*/
|
||||||
public static String gensalt() {
|
public static String gensalt()
|
||||||
|
throws IllegalArgumentException {
|
||||||
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
|
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,6 +924,18 @@ public class BCrypt {
|
|||||||
* @return true if the passwords match, false otherwise
|
* @return true if the passwords match, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean checkpw(String plaintext, String hashed) {
|
public static boolean checkpw(String plaintext, String hashed) {
|
||||||
|
byte plaintextb[] = stringToBytes(plaintext);
|
||||||
|
return checkpw(plaintextb, hashed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a plaintext byte[] 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(byte[] plaintext, String hashed) {
|
||||||
byte hashed_bytes[];
|
byte hashed_bytes[];
|
||||||
byte try_bytes[];
|
byte try_bytes[];
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user