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:
jongames
2019-05-04 10:36:12 -04:00
committed by Ronan Lana
parent 618d53c707
commit e70215eb9d

View File

@@ -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 {