diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java index 7fa6848..203970c 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/PasswordUtility.java @@ -20,6 +20,10 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import javax.annotation.Nullable; import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; @@ -44,6 +48,17 @@ public final class PasswordUtility { public static final int DEFAULT_ITERATIONS = 1000; /** + * An in-memory cache of password hashes done with N iterations + * to just one iteration. Used to speed up login attempts while still + * keeping the on-disk (or in-repository) password hashes protected + * with many hash iterations. We still use one hash iteration even + * in this cache to prevent an attacker from getting all active + * plain-text passwords from a heap dump. + */ + private static final Map PASSWORD_CACHE = + Collections.synchronizedMap(new HashMap()); + + /** * Avoid instantiation */ private PasswordUtility() {} @@ -158,9 +173,21 @@ public final class PasswordUtility { iterations = extractIterations(hashedPassword, startPos); } + String simpleHash = generateHash( + password, algorithm, salt, NO_ITERATIONS); + if (iterations == NO_ITERATIONS) { + return hashedPassword.equals(simpleHash); + } + if (simpleHash.equals(PASSWORD_CACHE.get(hashedPassword))) { + return true; + } + String hash = generateHash(password, algorithm, salt, iterations); - return hashedPassword.equals(hash); - } // hashedPassword is plaintext -> return false + if (hashedPassword.equals(hash)) { + PASSWORD_CACHE.put(hashedPassword, simpleHash); + return true; + } + } // hashedPassword is plaintext or doesn't match -> return false } catch (NoSuchAlgorithmException e) { log.warn(e.getMessage()); } catch (UnsupportedEncodingException e) {