opendj-sdk/opendj-server-legacy/src/main/java/org/opends/server/extensions/PBKDF2PasswordStorageScheme.java
@@ -68,25 +68,14 @@ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The fully-qualified name of this class. */ private static final String CLASS_NAME = "org.opends.server.extensions.PBKDF2PasswordStorageScheme"; private static final String CLASS_NAME = "org.opends.server.extensions.PBKDF2PasswordStorageScheme"; /** * The number of bytes of random data to use as the salt when generating the * hashes. */ /** The number of bytes of random data to use as the salt when generating the hashes. */ private static final int NUM_SALT_BYTES = 8; /** The number of bytes the SHA-1 algorithm produces. */ private static final int SHA1_LENGTH = 20; /** The factory used to generate the PBKDF2 hashes. */ private SecretKeyFactory factory; /** The lock used to provide thread-safe access to the message digest. */ private final Object factoryLock = new Object(); /** The secure random number generator to use to generate the salt values. */ private SecureRandom random; @@ -105,14 +94,14 @@ /** {@inheritDoc} */ @Override public void initializePasswordStorageScheme( PBKDF2PasswordStorageSchemeCfg configuration) public void initializePasswordStorageScheme(PBKDF2PasswordStorageSchemeCfg configuration) throws ConfigException, InitializationException { try { random = SecureRandom.getInstance(SECURE_PRNG_SHA1); factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); // Just try to verify if the algorithm is supported. SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); } catch (NoSuchAlgorithmException e) { @@ -125,17 +114,15 @@ /** {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( PBKDF2PasswordStorageSchemeCfg configuration, List<LocalizableMessage> unacceptableReasons) public boolean isConfigurationChangeAcceptable(PBKDF2PasswordStorageSchemeCfg configuration, List<LocalizableMessage> unacceptableReasons) { return true; } /** {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( PBKDF2PasswordStorageSchemeCfg configuration) public ConfigChangeResult applyConfigurationChange(PBKDF2PasswordStorageSchemeCfg configuration) { this.config = configuration; return new ConfigChangeResult(); @@ -156,7 +143,7 @@ byte[] saltBytes = new byte[NUM_SALT_BYTES]; int iterations = config.getPBKDF2Iterations(); byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations); byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations,random); byte[] hashPlusSalt = concatenateHashPlusSalt(saltBytes, digestBytes); return ByteString.valueOf(iterations + ":" + Base64.encode(hashPlusSalt)); @@ -167,15 +154,12 @@ public ByteString encodePasswordWithScheme(ByteSequence plaintext) throws DirectoryException { return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + encodePassword(plaintext)); return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + encodePassword(plaintext)); } /** {@inheritDoc} */ @Override public boolean passwordMatches(ByteSequence plaintextPassword, ByteSequence storedPassword) { public boolean passwordMatches(ByteSequence plaintextPassword, ByteSequence storedPassword) { // Split the iterations from the stored value (separated by a ':') // Base64-decode the remaining value and take the last 8 bytes as the salt. try @@ -232,18 +216,16 @@ { byte[] saltBytes = new byte[NUM_SALT_BYTES]; int iterations = config.getPBKDF2Iterations(); byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations); byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations,random); // Encode and return the value. return ByteString.valueOf(AUTH_PASSWORD_SCHEME_NAME_PBKDF2 + '$' + iterations + ':' + Base64.encode(saltBytes) + '$' + Base64.encode(digestBytes)); + iterations + ':' + Base64.encode(saltBytes) + '$' + Base64.encode(digestBytes)); } /** {@inheritDoc} */ @Override public boolean authPasswordMatches(ByteSequence plaintextPassword, String authInfo, String authValue) public boolean authPasswordMatches(ByteSequence plaintextPassword, String authInfo, String authValue) { try { @@ -276,19 +258,16 @@ public ByteString getPlaintextValue(ByteSequence storedPassword) throws DirectoryException { LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PBKDF2); LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PBKDF2); throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); } /** {@inheritDoc} */ @Override public ByteString getAuthPasswordPlaintextValue(String authInfo, String authValue) public ByteString getAuthPasswordPlaintextValue(String authInfo, String authValue) throws DirectoryException { LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PBKDF2); LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PBKDF2); throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); } @@ -300,16 +279,13 @@ } /** * Generates an encoded password string from the given clear-text password. * This method is primarily intended for use when it is necessary to generate * a password with the server offline (e.g., when setting the initial root * user password). * This method is primarily intended for use when it is necessary to generate a password with the server * offline (e.g., when setting the initial root user password). * * @param passwordBytes The bytes that make up the clear-text password. * @return The encoded password string, including the scheme name in curly * braces. * @return The encoded password string, including the scheme name in curly braces. * @throws DirectoryException If a problem occurs during processing. */ public static String encodeOffline(byte[] passwordBytes) @@ -322,18 +298,16 @@ byte[] digestBytes = encodeWithRandomSalt(password, saltBytes, iterations); byte[] hashPlusSalt = concatenateHashPlusSalt(saltBytes, digestBytes); return '{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + iterations + ':' + Base64.encode(hashPlusSalt); return '{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + iterations + ':' + Base64.encode(hashPlusSalt); } private static byte[] encodeWithRandomSalt(ByteString plaintext, byte[] saltBytes, int iterations) throws DirectoryException private static byte[] encodeWithRandomSalt(ByteString plaintext, byte[] saltBytes, int iterations) throws DirectoryException { try { final SecureRandom random = SecureRandom.getInstance(SECURE_PRNG_SHA1); final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); return encodeWithRandomSalt(plaintext, saltBytes, iterations, random, factory); return encodeWithRandomSalt(plaintext, saltBytes, iterations, random); } catch (DirectoryException e) { @@ -345,14 +319,14 @@ } } private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations, final SecretKeyFactory factory) throws DirectoryException private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations) throws DirectoryException { final char[] plaintextChars = plaintext.toString().toCharArray(); try { KeySpec spec = new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8); final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); KeySpec spec = new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8); return factory.generateSecret(spec).getEncoded(); } catch (Exception e) @@ -361,45 +335,29 @@ } finally { if (plaintextChars != null) { Arrays.fill(plaintextChars, '0'); } Arrays.fill(plaintextChars, '0'); } } private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes, byte[] digestBytes, int iterations) private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes, byte[] digestBytes, int iterations) { synchronized (factoryLock) try { try { final byte[] userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations, factory); return Arrays.equals(digestBytes, userDigestBytes); } catch (Exception e) { return false; } final byte[] userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations); return Arrays.equals(digestBytes, userDigestBytes); } } private byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes, int iterations) throws DirectoryException { synchronized (factoryLock) catch (Exception e) { return encodeWithRandomSalt(plaintext, saltBytes, iterations, random, factory); return false; } } private static byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes, int iterations, SecureRandom random, final SecretKeyFactory factory) throws DirectoryException int iterations, SecureRandom random) throws DirectoryException { random.nextBytes(saltBytes); return encodeWithSalt(plaintext, saltBytes, iterations, factory); return encodeWithSalt(plaintext, saltBytes, iterations); } private static DirectoryException cannotEncodePassword(Exception e) opendj-sdk/opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
@@ -21,7 +21,7 @@ * CDDL HEADER END * * * Copyright 2014 ForgeRock AS. * Copyright 2014-2015 ForgeRock AS. * Portions Copyright Emidio Stani & Andrea Stani */ package org.opends.server.extensions; @@ -65,14 +65,9 @@ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The fully-qualified name of this class. */ private static final String CLASS_NAME = "org.opends.server.extensions.PKCS5S2PasswordStorageScheme"; private static final String CLASS_NAME = "org.opends.server.extensions.PKCS5S2PasswordStorageScheme"; /** * The number of bytes of random data to use as the salt when generating the * hashes. */ /** The number of bytes of random data to use as the salt when generating the hashes. */ private static final int NUM_SALT_BYTES = 16; /** The number of bytes the SHA-1 algorithm produces. */ @@ -81,12 +76,6 @@ /** Atlassian hardcoded the number of iterations to 10000. */ private static final int iterations = 10000; /** The factory used to generate the PKCS5S2 hashes. */ private SecretKeyFactory factory; /** The lock used to provide thread-safe access to the message digest. */ private final Object factoryLock = new Object(); /** The secure random number generator to use to generate the salt values. */ private SecureRandom random; @@ -102,14 +91,14 @@ /** {@inheritDoc} */ @Override public void initializePasswordStorageScheme( PKCS5S2PasswordStorageSchemeCfg configuration) public void initializePasswordStorageScheme(PKCS5S2PasswordStorageSchemeCfg configuration) throws InitializationException { try { random = SecureRandom.getInstance(SECURE_PRNG_SHA1); factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); // Just try to verify if the algorithm is supported SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); } catch (NoSuchAlgorithmException e) { @@ -130,7 +119,7 @@ throws DirectoryException { byte[] saltBytes = new byte[NUM_SALT_BYTES]; byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes); byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes,random); byte[] hashPlusSalt = concatenateSaltPlusHash(saltBytes, digestBytes); return ByteString.valueOf(Base64.encode(hashPlusSalt)); @@ -141,14 +130,12 @@ public ByteString encodePasswordWithScheme(ByteSequence plaintext) throws DirectoryException { return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' + encodePassword(plaintext)); return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' + encodePassword(plaintext)); } /** {@inheritDoc} */ @Override public boolean passwordMatches(ByteSequence plaintextPassword, ByteSequence storedPassword) public boolean passwordMatches(ByteSequence plaintextPassword, ByteSequence storedPassword) { // Base64-decode the value and take the first 16 bytes as the salt. try @@ -158,8 +145,7 @@ if (decodedBytes.length != NUM_SALT_BYTES + SHA1_LENGTH) { logger.error(ERR_PWSCHEME_INVALID_BASE64_DECODED_STORED_PASSWORD.get( storedPassword.toString())); logger.error(ERR_PWSCHEME_INVALID_BASE64_DECODED_STORED_PASSWORD.get(storedPassword.toString())); return false; } @@ -173,8 +159,7 @@ catch (Exception e) { logger.traceException(e); logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD.get( storedPassword.toString(), String.valueOf(e))); logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD.get(storedPassword.toString(), String.valueOf(e))); return false; } } @@ -199,17 +184,15 @@ throws DirectoryException { byte[] saltBytes = new byte[NUM_SALT_BYTES]; byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes); byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes,random); // Encode and return the value. return ByteString.valueOf(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2 + '$' + iterations + ':' + Base64.encode(saltBytes) + '$' + Base64.encode(digestBytes)); return ByteString.valueOf(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2 + '$' + iterations + ':' + Base64.encode(saltBytes) + '$' + Base64.encode(digestBytes)); } /** {@inheritDoc} */ @Override public boolean authPasswordMatches(ByteSequence plaintextPassword, String authInfo, String authValue) public boolean authPasswordMatches(ByteSequence plaintextPassword, String authInfo, String authValue) { try { @@ -242,19 +225,16 @@ public ByteString getPlaintextValue(ByteSequence storedPassword) throws DirectoryException { LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PKCS5S2); LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PKCS5S2); throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); } /** {@inheritDoc} */ @Override public ByteString getAuthPasswordPlaintextValue(String authInfo, String authValue) public ByteString getAuthPasswordPlaintextValue(String authInfo, String authValue) throws DirectoryException { LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2); LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2); throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); } @@ -269,13 +249,11 @@ /** * Generates an encoded password string from the given clear-text password. * This method is primarily intended for use when it is necessary to generate * a password with the server offline (e.g., when setting the initial root * user password). * This method is primarily intended for use when it is necessary to generate a password with the server * offline (e.g., when setting the initial root user password). * * @param passwordBytes The bytes that make up the clear-text password. * @return The encoded password string, including the scheme name in curly * braces. * @return The encoded password string, including the scheme name in curly braces. * @throws DirectoryException If a problem occurs during processing. */ public static String encodeOffline(byte[] passwordBytes) @@ -285,8 +263,7 @@ byte[] digestBytes = encodeWithRandomSalt(ByteString.wrap(passwordBytes), saltBytes); byte[] hashPlusSalt = concatenateSaltPlusHash(saltBytes, digestBytes); return '{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' + Base64.encode(hashPlusSalt); return '{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' + Base64.encode(hashPlusSalt); } private static byte[] encodeWithRandomSalt(ByteString plaintext, byte[] saltBytes) @@ -295,8 +272,7 @@ try { final SecureRandom random = SecureRandom.getInstance(SECURE_PRNG_SHA1); final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); return encodeWithRandomSalt(plaintext, saltBytes, random, factory); return encodeWithRandomSalt(plaintext, saltBytes, random); } catch (DirectoryException e) { @@ -308,14 +284,14 @@ } } private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations, final SecretKeyFactory factory) throws DirectoryException private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations) throws DirectoryException { final char[] plaintextChars = plaintext.toString().toCharArray(); try { KeySpec spec = new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8); final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2); KeySpec spec = new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8); return factory.generateSecret(spec).getEncoded(); } catch (Exception e) @@ -324,45 +300,28 @@ } finally { if (plaintextChars != null) { Arrays.fill(plaintextChars, '0'); } Arrays.fill(plaintextChars, '0'); } } private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes, byte[] digestBytes, int iterations) private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes, byte[] digestBytes, int iterations) { synchronized (factoryLock) { try { final byte[] userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations, factory); return Arrays.equals(digestBytes, userDigestBytes); } catch (Exception e) { return false; } } try { final byte[] userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations); return Arrays.equals(digestBytes, userDigestBytes); } catch (Exception e) { return false; } } private byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes) private static byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes, SecureRandom random) throws DirectoryException { synchronized (factoryLock) { return encodeWithRandomSalt(plaintext, saltBytes, random, factory); } } private static byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes, SecureRandom random, final SecretKeyFactory factory) throws DirectoryException { random.nextBytes(saltBytes); return encodeWithSalt(plaintext, saltBytes, iterations, factory); return encodeWithSalt(plaintext, saltBytes, iterations); } private static DirectoryException cannotEncodePassword(Exception e)