@Test public void testBuildPasswordHashNoIterations() throws Exception { String hash = PasswordUtil.buildPasswordHash("pw", PasswordUtil.DEFAULT_ALGORITHM, PasswordUtil.DEFAULT_SALT_SIZE, 1); assertTrue(PasswordUtil.isSame(hash, "pw")); }
@Override public Object apply(Object context, Options options, HelperContext pluginContext) throws IOException { if (context == null) { return null; } String password = context.toString(); // if password is already encrypted skip further processing if (!PasswordUtil.isPlainTextPassword(password)) { return password; } ConfigurationParameters config = ConfigurationParameters.of(options.hash); try { return PasswordUtil.buildPasswordHash(password, config); } catch (NoSuchAlgorithmException ex) { throw new IOException("Unable build password hash.", ex); } }
String algorithm = extractAlgorithm(hashedPassword); if (algorithm != null) { int startPos = algorithm.length()+2; String salt = extractSalt(hashedPassword, startPos); int iterations = NO_ITERATIONS; if (salt != null) { startPos += salt.length()+1; iterations = extractIterations(hashedPassword, startPos); String hash = generateHash(password, algorithm, salt, iterations); return compareSecure(hashedPassword, hash);
@NotNull private static String generatePBKDF2(@NotNull String pwd, @NotNull String salt, @NotNull String algorithm, int iterations, int keyLength) throws NoSuchAlgorithmException { // for example PBKDF2WithHmacSHA1 SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm); byte[] saltBytes = convertHexToBytes(salt); KeySpec keyspec = new PBEKeySpec(pwd.toCharArray(), saltBytes, iterations, keyLength); try { Key key = factory.generateSecret(keyspec); byte[] bytes = key.getEncoded(); return convertBytesToHex(bytes); } catch (InvalidKeySpecException e) { throw new NoSuchAlgorithmException(algorithm, e); } }
/** * Generates a hash of the specified password with the default values * for algorithm, salt-size and number of iterations. * * @param password The password to be hashed. * @return The password hash. * @throws NoSuchAlgorithmException If {@link #DEFAULT_ALGORITHM} is not supported. * @throws UnsupportedEncodingException If utf-8 is not supported. */ public static String buildPasswordHash(@NotNull String password) throws NoSuchAlgorithmException, UnsupportedEncodingException { return buildPasswordHash(password, DEFAULT_ALGORITHM, DEFAULT_SALT_SIZE, DEFAULT_ITERATIONS); }
/** * Returns {@code true} if hash of the specified {@code password} equals the * given hashed password. * * @param hashedPassword Password hash. * @param password The password to compare. * @return If the hash created from the specified {@code password} equals * the given {@code hashedPassword} string. */ public static boolean isSame(@Nullable String hashedPassword, @NotNull char[] password) { return isSame(hashedPassword, String.valueOf(password)); }
@Test public void testPBKDF2With() throws Exception { // https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html String algo = "PBKDF2WithHmacSHA512"; // test vector from http://tools.ietf.org/html/rfc6070 String pw = "pass\0word"; int iterations = 4096; String hash = PasswordUtil.buildPasswordHash(pw, algo, 5, iterations); assertTrue(hash.startsWith("{" + algo + "}")); int cntOctets = hash.substring(hash.lastIndexOf('-') + 1).length() / 2; assertEquals(16, cntOctets); assertFalse(PasswordUtil.isPlainTextPassword(hash)); assertTrue(PasswordUtil.isSame(hash, pw)); } }
/** * Validate the specified password. * * @param password The password to be validated * @param forceMatch If true the specified password is always validated; * otherwise only if it is a plain text password. * @throws RepositoryException If the specified password is too short or * doesn't match the specified password pattern. */ private void validatePassword(@Nullable String password, boolean forceMatch) throws RepositoryException { if (password != null && (forceMatch || PasswordUtil.isPlainTextPassword(password))) { if (pattern != null && !pattern.matcher(password).matches()) { throw new ConstraintViolationException("Password violates password constraint (" + pattern.pattern() + ")."); } } } }
@NotNull private static String generateSalt(int saltSize) { SecureRandom random = new SecureRandom(); byte[] salt = new byte[saltSize]; random.nextBytes(salt); return convertBytesToHex(salt); }
/** * Returns {@code true} if the specified string doesn't start with a * valid algorithm name in curly brackets. * * @param password The string to be tested. * @return {@code true} if the specified string doesn't start with a * valid algorithm name in curly brackets. */ public static boolean isPlainTextPassword(@Nullable String password) { return extractAlgorithm(password) == null; }
/** * Generates a hash of the specified password with the default values * for algorithm, salt-size and number of iterations. * * @param password The password to be hashed. * @return The password hash. * @throws NoSuchAlgorithmException If {@link #DEFAULT_ALGORITHM} is not supported. * @throws UnsupportedEncodingException If utf-8 is not supported. */ public static String buildPasswordHash(@Nonnull String password) throws NoSuchAlgorithmException, UnsupportedEncodingException { return buildPasswordHash(password, DEFAULT_ALGORITHM, DEFAULT_SALT_SIZE, DEFAULT_ITERATIONS); }
/** * Returns {@code true} if hash of the specified {@code password} equals the * given hashed password. * * @param hashedPassword Password hash. * @param password The password to compare. * @return If the hash created from the specified {@code password} equals * the given {@code hashedPassword} string. */ public static boolean isSame(@Nullable String hashedPassword, @Nonnull char[] password) { return isSame(hashedPassword, String.valueOf(password)); }
@Test public void testPasswordValidationActionOnCreate() throws Exception { String hashed = PasswordUtil.buildPasswordHash("DWkej32H"); user = getUserManager(root).createUser("testuser", hashed); root.commit(); String pwValue = root.getTree(user.getPath()).getProperty(UserConstants.REP_PASSWORD).getValue(Type.STRING); assertFalse(PasswordUtil.isPlainTextPassword(pwValue)); assertTrue(PasswordUtil.isSame(pwValue, hashed)); }
/** * Validate the specified password. * * @param password The password to be validated * @param forceMatch If true the specified password is always validated; * otherwise only if it is a plain text password. * @throws RepositoryException If the specified password is too short or * doesn't match the specified password pattern. */ private void validatePassword(@Nullable String password, boolean forceMatch) throws RepositoryException { if (password != null && (forceMatch || PasswordUtil.isPlainTextPassword(password))) { if (pattern != null && !pattern.matcher(password).matches()) { throw new ConstraintViolationException("Password violates password constraint (" + pattern.pattern() + ")."); } } } }
@Nonnull private static String generatePBKDF2(@Nonnull String pwd, @Nonnull String salt, @Nonnull String algorithm, int iterations, int keyLength) throws NoSuchAlgorithmException { // for example PBKDF2WithHmacSHA1 SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm); byte[] saltBytes = convertHexToBytes(salt); KeySpec keyspec = new PBEKeySpec(pwd.toCharArray(), saltBytes, iterations, keyLength); try { Key key = factory.generateSecret(keyspec); byte[] bytes = key.getEncoded(); return convertBytesToHex(bytes); } catch (InvalidKeySpecException e) { throw new NoSuchAlgorithmException(algorithm, e); } }
@Nonnull private static String generateSalt(int saltSize) { SecureRandom random = new SecureRandom(); byte[] salt = new byte[saltSize]; random.nextBytes(salt); return convertBytesToHex(salt); }
/** * Returns {@code true} if the specified string doesn't start with a * valid algorithm name in curly brackets. * * @param password The string to be tested. * @return {@code true} if the specified string doesn't start with a * valid algorithm name in curly brackets. */ public static boolean isPlainTextPassword(@Nullable String password) { return extractAlgorithm(password) == null; }
@Test public void testIsSameNullPw() throws Exception { assertFalse(PasswordUtil.isSame(PasswordUtil.buildPasswordHash("pw"), (String) null)); }
/** * Same as {@link #buildPasswordHash(String, String, int, int)} but retrieving * the parameters for hash generation from the specified configuration. * * @param password The password to be hashed. * @param config The configuration defining the details of the hash generation. * @return The password hash. * @throws NoSuchAlgorithmException If the specified algorithm is not supported. * @throws UnsupportedEncodingException If utf-8 is not supported. */ public static String buildPasswordHash(@Nonnull String password, @Nonnull ConfigurationParameters config) throws NoSuchAlgorithmException, UnsupportedEncodingException { checkNotNull(config); String algorithm = config.getConfigValue(UserConstants.PARAM_PASSWORD_HASH_ALGORITHM, DEFAULT_ALGORITHM); int iterations = config.getConfigValue(UserConstants.PARAM_PASSWORD_HASH_ITERATIONS, DEFAULT_ITERATIONS); int saltSize = config.getConfigValue(UserConstants.PARAM_PASSWORD_SALT_SIZE, DEFAULT_SALT_SIZE); return buildPasswordHash(password, algorithm, saltSize, iterations); }
@Override public void onPasswordChange(@Nonnull User user, String newPassword, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper) throws RepositoryException { if (newPassword == null) { throw new ConstraintViolationException("Expected a new password that is not null."); } String pwHash = getPasswordHash(root, user); if (PasswordUtil.isSame(pwHash, newPassword)) { throw new ConstraintViolationException("New password is identical to the old password."); } }