Handle very long passwords
Bcrypt only uses 72 bytes of input, therefore hashing input first is safer.
This commit is contained in:
parent
8e7d86916b
commit
e19ac767b4
|
@ -4,6 +4,8 @@ import javax.crypto.Cipher
|
|||
import javax.crypto.spec.{ IvParameterSpec, SecretKeySpec }
|
||||
import java.util.Base64
|
||||
|
||||
import com.roundeights.hasher.Implicits._
|
||||
|
||||
/**
|
||||
* Encryption for bcrypt hashes.
|
||||
*
|
||||
|
@ -34,12 +36,12 @@ private[user] final class DumbAes(secret: String) {
|
|||
}
|
||||
|
||||
final class PasswordHasher(secret: String, logRounds: Int,
|
||||
hashTimer: (=> Array[Byte]) => Array[Byte] = x => x) {
|
||||
hashTimer: (=> Array[Byte]) => Array[Byte] = x => x) {
|
||||
import org.mindrot.BCrypt
|
||||
|
||||
private val aes = new DumbAes(secret)
|
||||
private def bHash(pass: String, salt: Array[Byte]) =
|
||||
hashTimer(BCrypt.hashpwRaw(pass, 'a', logRounds, salt))
|
||||
hashTimer(BCrypt.hashpwRaw(pass.sha512, 'a', logRounds, salt))
|
||||
|
||||
def hash(pass: String) = {
|
||||
val salt = BCrypt.gensaltRaw
|
||||
|
|
|
@ -262,7 +262,7 @@ final class Authenticator(passHasher: PasswordHasher, onShaLogin: => Unit) {
|
|||
import com.roundeights.hasher.Implicits._
|
||||
|
||||
private def salted(p: String, salt: String) = s"$p{$salt}"
|
||||
def passEnc(id: String, pass: String) = passHasher.hash(salted(pass, id))
|
||||
def passEnc(id: String, pass: String) = passHasher.hash(salted(id, pass))
|
||||
|
||||
case class AuthData(
|
||||
_id: String,
|
||||
|
@ -283,7 +283,7 @@ final class Authenticator(passHasher: PasswordHasher, onShaLogin: => Unit) {
|
|||
bpass match {
|
||||
// Deprecated fallback. Log & fail after DB migration.
|
||||
case None => password ?? { onShaLogin; _ == newP }
|
||||
case Some(bHash) => passHasher.check(bHash, salted(newP, _id))
|
||||
case Some(bHash) => passHasher.check(bHash, salted(_id, newP))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.mindrot;
|
|||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* BCrypt implements OpenBSD-style Blowfish password hashing using
|
||||
|
@ -57,7 +58,7 @@ import java.security.SecureRandom;
|
|||
* 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
|
||||
* 10, and the valid range is 4 to 30.
|
||||
*
|
||||
|
@ -655,16 +656,9 @@ public final class BCrypt {
|
|||
return ret;
|
||||
}
|
||||
|
||||
public static byte[] hashpwRaw(String password, char minorV, int rounds, byte[] salt) {
|
||||
byte[] passwordb;
|
||||
if (minorV >= 'a') password += '\0';
|
||||
try {
|
||||
passwordb = password.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new AssertionError("UTF-8 is not supported");
|
||||
}
|
||||
|
||||
return new BCrypt().crypt_raw(passwordb, salt, rounds);
|
||||
public static byte[] hashpwRaw(byte[] password, char minorV, int rounds, byte[] salt) {
|
||||
if (minorV >= 'a') password = Arrays.copyOf(password, password.length + 1);
|
||||
return new BCrypt().crypt_raw(password, salt, rounds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -676,7 +670,7 @@ public final class BCrypt {
|
|||
*/
|
||||
public static String hashpw(String password, String salt) {
|
||||
String real_salt;
|
||||
byte saltb[], hashed[];
|
||||
byte passwordb[], saltb[], hashed[];
|
||||
char minor = (char)0;
|
||||
int rounds, off = 0;
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
@ -698,9 +692,15 @@ public final class BCrypt {
|
|||
rounds = Integer.parseInt(salt.substring(off, off + 2));
|
||||
|
||||
real_salt = salt.substring(off + 3, off + 25);
|
||||
try {
|
||||
passwordb = password.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new AssertionError("UTF-8 is not supported");
|
||||
}
|
||||
|
||||
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
|
||||
|
||||
hashed = hashpwRaw(password, minor, rounds, saltb);
|
||||
hashed = hashpwRaw(passwordb, minor, rounds, saltb);
|
||||
|
||||
rs.append("$2");
|
||||
if (minor >= 'a')
|
||||
|
|
|
@ -31,7 +31,7 @@ class AuthTest extends Specification {
|
|||
val bCryptUser = AuthData(
|
||||
_id = "foo",
|
||||
bpass = Some(Base64.getDecoder.decode(
|
||||
"nMY6Pi45178YiOMcWncklizO3Z2enZfiFF5RDBkFZFYEOP7rZ2F0"
|
||||
"qRDaT9KiCL4WlssyZuqezCb/3E0ddU6WX7bTknnNWBu8uv/yqR+F"
|
||||
))
|
||||
)
|
||||
"correct" >> bCryptUser.compare("password")
|
||||
|
@ -48,6 +48,22 @@ class AuthTest extends Specification {
|
|||
bpass = bCryptUser.bpass
|
||||
).compare("password")
|
||||
}
|
||||
|
||||
"very long password" in {
|
||||
val longPass = "a" * 100
|
||||
val user = AuthData("foo", bpass = Some(passEnc("foo", longPass)))
|
||||
"correct" >> user.compare(longPass)
|
||||
"wrong fails" >> !user.compare("a" * 99)
|
||||
}
|
||||
|
||||
"handle crazy passwords" in {
|
||||
val abcUser = AuthData("foo", bpass = Some(passEnc("foo", "abc")))
|
||||
|
||||
"test eq" >> abcUser.compare("abc")
|
||||
"vs null bytes" >> !abcUser.compare("abc\u0000")
|
||||
"vs unicode" >> !abcUser.compare("abc\uD83D\uDE01")
|
||||
"vs empty" >> !abcUser.compare("")
|
||||
}
|
||||
}
|
||||
|
||||
"migrated user" in {
|
||||
|
|
|
@ -16,7 +16,7 @@ class BCryptTest extends Specification {
|
|||
|
||||
val salt = BCrypt.gensaltRaw
|
||||
"with raw bytes" in {
|
||||
val rawHash = BCrypt.hashpwRaw(pass, 'a', 6, salt)
|
||||
val rawHash = BCrypt.hashpwRaw(pass.getBytes("UTF-8"), 'a', 6, salt)
|
||||
|
||||
salt.size must_== 16
|
||||
rawHash.size must_== 23
|
||||
|
@ -27,15 +27,5 @@ class BCryptTest extends Specification {
|
|||
"reject bad" >> !BCrypt.checkpw("", bString)
|
||||
"uniq salts" >> { salt !== BCrypt.gensaltRaw }
|
||||
}
|
||||
|
||||
"handle crazy passwords" in {
|
||||
val hashIt = (p: String) => BCrypt.hashpwRaw(p, 'a', 2, salt)
|
||||
val abcHash = hashIt("abc")
|
||||
|
||||
"test eq" >> bcryptEq(abcHash, hashIt("abc"))
|
||||
"vs null bytes" >> !bcryptEq(abcHash, hashIt("abc\u0000"))
|
||||
"vs unicode" >> !bcryptEq(abcHash, hashIt("abc\uD83D\uDE01"))
|
||||
"vs empty" >> !bcryptEq(abcHash, hashIt(""))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue