| /* |
| * or more contributor license agreements. See the NOTICE file |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| package org.apache.directory.api.ldap.model.password; |
| |
| |
| import java.security.Key; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.SecureRandom; |
| import java.security.spec.KeySpec; |
| import java.util.Arrays; |
| import java.util.Date; |
| |
| import javax.crypto.SecretKeyFactory; |
| import javax.crypto.spec.PBEKeySpec; |
| |
| import org.apache.commons.codec.digest.Crypt; |
| import org.apache.directory.api.i18n.I18n; |
| import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants; |
| import org.apache.directory.api.util.Base64; |
| import org.apache.directory.api.util.DateUtils; |
| import org.apache.directory.api.util.TimeProvider; |
| import org.apache.directory.api.util.Strings; |
| |
| /** |
| * A utility class containing methods related to processing passwords. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public final class PasswordUtil |
| { |
| |
| /** The SHA1 hash length */ |
| public static final int SHA1_LENGTH = 20; |
| |
| /** The SHA256 hash length */ |
| public static final int SHA256_LENGTH = 32; |
| |
| /** The SHA384 hash length */ |
| public static final int SHA384_LENGTH = 48; |
| |
| /** The SHA512 hash length */ |
| public static final int SHA512_LENGTH = 64; |
| |
| /** The MD5 hash length */ |
| public static final int MD5_LENGTH = 16; |
| |
| /** The PKCS5S2 hash length */ |
| public static final int PKCS5S2_LENGTH = 32; |
| |
| /** The CRYPT (DES) hash length */ |
| public static final int CRYPT_LENGTH = 11; |
| |
| /** The CRYPT (MD5) hash length */ |
| public static final int CRYPT_MD5_LENGTH = 22; |
| |
| /** The CRYPT (SHA-256) hash length */ |
| public static final int CRYPT_SHA256_LENGTH = 43; |
| |
| /** The CRYPT (SHA-512) hash length */ |
| public static final int CRYPT_SHA512_LENGTH = 86; |
| |
| /** The CRYPT (BCrypt) hash length */ |
| public static final int CRYPT_BCRYPT_LENGTH = 31; |
| |
| private static final byte[] CRYPT_SALT_CHARS = Strings |
| .getBytesUtf8( "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ); |
| |
| private PasswordUtil() |
| { |
| } |
| |
| |
| /** |
| * Get the algorithm from the stored password. |
| * It can be found on the beginning of the stored password, between |
| * curly brackets. |
| * @param credentials the credentials of the user |
| * @return the name of the algorithm to use |
| */ |
| public static LdapSecurityConstants findAlgorithm( byte[] credentials ) |
| { |
| if ( ( credentials == null ) || ( credentials.length == 0 ) ) |
| { |
| return null; |
| } |
| |
| if ( credentials[0] == '{' ) |
| { |
| // get the algorithm |
| int pos = 1; |
| |
| while ( pos < credentials.length ) |
| { |
| if ( credentials[pos] == '}' ) |
| { |
| break; |
| } |
| |
| pos++; |
| } |
| |
| if ( pos < credentials.length ) |
| { |
| if ( pos == 1 ) |
| { |
| // We don't have an algorithm : return the credentials as is |
| return null; |
| } |
| |
| String algorithm = Strings.toLowerCaseAscii( Strings.utf8ToString( credentials, 1, pos - 1 ) ); |
| |
| // support for crypt additional encryption algorithms (e.g. {crypt}$1$salt$ez2vlPGdaLYkJam5pWs/Y1) |
| if ( credentials.length > pos + 3 && credentials[pos + 1] == '$' |
| && Character.isDigit( credentials[pos + 2] ) ) |
| { |
| if ( credentials[pos + 3] == '$' ) |
| { |
| algorithm += Strings.utf8ToString( credentials, pos + 1, 3 ); |
| } |
| else if ( credentials.length > pos + 4 && credentials[pos + 4] == '$' ) |
| { |
| algorithm += Strings.utf8ToString( credentials, pos + 1, 4 ); |
| } |
| } |
| |
| return LdapSecurityConstants.getAlgorithm( algorithm ); |
| } |
| else |
| { |
| // We don't have an algorithm |
| return null; |
| } |
| } |
| else |
| { |
| // No '{algo}' part |
| return null; |
| } |
| } |
| |
| |
| /** |
| * @see #createStoragePassword(byte[], LdapSecurityConstants) |
| * |
| * @param credentials The password |
| * @param algorithm The algorithm to use |
| * @return The resulting byte[] containing the paswword |
| */ |
| public static byte[] createStoragePassword( String credentials, LdapSecurityConstants algorithm ) |
| { |
| return createStoragePassword( Strings.getBytesUtf8( credentials ), algorithm ); |
| } |
| |
| |
| /** |
| * create a hashed password in a format that can be stored in the server. |
| * If the specified algorithm requires a salt then a random salt of 8 byte size is used |
| * |
| * @param credentials the plain text password |
| * @param algorithm the hashing algorithm to be applied |
| * @return the password after hashing with the given algorithm |
| */ |
| public static byte[] createStoragePassword( byte[] credentials, LdapSecurityConstants algorithm ) |
| { |
| // check plain text password |
| if ( algorithm == null ) |
| { |
| return credentials; |
| } |
| |
| byte[] salt; |
| |
| switch ( algorithm ) |
| { |
| case HASH_METHOD_SSHA: |
| case HASH_METHOD_SSHA256: |
| case HASH_METHOD_SSHA384: |
| case HASH_METHOD_SSHA512: |
| case HASH_METHOD_SMD5: |
| // we use 8 byte salt always except for "crypt" which needs 2 byte salt |
| salt = new byte[8]; |
| new SecureRandom().nextBytes( salt ); |
| break; |
| |
| case HASH_METHOD_PKCS5S2: |
| // we use 16 byte salt for PKCS5S2 |
| salt = new byte[16]; |
| new SecureRandom().nextBytes( salt ); |
| break; |
| |
| case HASH_METHOD_CRYPT: |
| salt = generateCryptSalt( 2 ); |
| break; |
| |
| case HASH_METHOD_CRYPT_MD5: |
| case HASH_METHOD_CRYPT_SHA256: |
| case HASH_METHOD_CRYPT_SHA512: |
| salt = generateCryptSalt( 8 ); |
| break; |
| |
| case HASH_METHOD_CRYPT_BCRYPT: |
| salt = Strings.getBytesUtf8( BCrypt.genSalt() ); |
| break; |
| |
| default: |
| salt = null; |
| } |
| |
| byte[] hashedPassword = encryptPassword( credentials, algorithm, salt ); |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append( '{' ).append( Strings.upperCase( algorithm.getPrefix() ) ).append( '}' ); |
| |
| if ( algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT |
| || algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_BCRYPT ) |
| { |
| sb.append( Strings.utf8ToString( salt ) ); |
| sb.append( Strings.utf8ToString( hashedPassword ) ); |
| } |
| else if ( algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_MD5 |
| || algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_SHA256 |
| || algorithm == LdapSecurityConstants.HASH_METHOD_CRYPT_SHA512 ) |
| { |
| sb.append( algorithm.getSubPrefix() ); |
| sb.append( Strings.utf8ToString( salt ) ); |
| sb.append( '$' ); |
| sb.append( Strings.utf8ToString( hashedPassword ) ); |
| } |
| else if ( salt != null ) |
| { |
| byte[] hashedPasswordWithSaltBytes = new byte[hashedPassword.length + salt.length]; |
| |
| if ( algorithm == LdapSecurityConstants.HASH_METHOD_PKCS5S2 ) |
| { |
| merge( hashedPasswordWithSaltBytes, salt, hashedPassword ); |
| } |
| else |
| { |
| merge( hashedPasswordWithSaltBytes, hashedPassword, salt ); |
| } |
| |
| sb.append( String.valueOf( Base64.encode( hashedPasswordWithSaltBytes ) ) ); |
| } |
| else |
| { |
| sb.append( String.valueOf( Base64.encode( hashedPassword ) ) ); |
| } |
| |
| return Strings.getBytesUtf8( sb.toString() ); |
| } |
| |
| |
| /** |
| * |
| * Compare the credentials. |
| * We have at least 6 algorithms to encrypt the password : |
| * <ul> |
| * <li>- SHA</li> |
| * <li>- SSHA (salted SHA)</li> |
| * <li>- SHA-2(256, 384 and 512 and their salted versions)</li> |
| * <li>- MD5</li> |
| * <li>- SMD5 (slated MD5)</li> |
| * <li>- PKCS5S2 (PBKDF2)</li> |
| * <li>- crypt (unix crypt)</li> |
| * <li>- plain text, ie no encryption.</li> |
| * </ul> |
| * <p> |
| * If we get an encrypted password, it is prefixed by the used algorithm, between |
| * brackets : {SSHA}password ... |
| * </p> |
| * If the password is using SSHA, SMD5 or crypt, some 'salt' is added to the password : |
| * <ul> |
| * <li>- length(password) - 20, starting at 21st position for SSHA</li> |
| * <li>- length(password) - 16, starting at 16th position for SMD5</li> |
| * <li>- length(password) - 2, starting at 3rd position for crypt</li> |
| * </ul> |
| * <p> |
| * For (S)SHA, SHA-256 and (S)MD5, we have to transform the password from Base64 encoded text |
| * to a byte[] before comparing the password with the stored one. |
| * </p> |
| * <p> |
| * For PKCS5S2 the salt is stored in the beginning of the password |
| * </p> |
| * <p> |
| * For crypt, we only have to remove the salt. |
| * </p> |
| * <p> |
| * At the end, we use the digest() method for (S)SHA and (S)MD5, the crypt() method for |
| * the CRYPT algorithm and a straight comparison for PLAIN TEXT passwords. |
| * </p> |
| * <p> |
| * The stored password is always using the unsalted form, and is stored as a bytes array. |
| * </p> |
| * |
| * @param receivedCredentials the credentials provided by user |
| * @param storedCredentials the credentials stored in the server |
| * @return true if they are equal, false otherwise |
| */ |
| public static boolean compareCredentials( byte[] receivedCredentials, byte[] storedCredentials ) |
| { |
| LdapSecurityConstants algorithm = findAlgorithm( storedCredentials ); |
| |
| if ( algorithm != null ) |
| { |
| // Let's get the encrypted part of the stored password |
| // We should just keep the password, excluding the algorithm |
| // and the salt, if any. |
| // But we should also get the algorithm and salt to |
| // be able to encrypt the submitted user password in the next step |
| PasswordDetails passwordDetails = PasswordUtil.splitCredentials( storedCredentials ); |
| |
| // Reuse the saltedPassword information to construct the encrypted |
| // password given by the user. |
| byte[] userPassword = PasswordUtil.encryptPassword( receivedCredentials, passwordDetails.getAlgorithm(), |
| passwordDetails.getSalt() ); |
| |
| return compareBytes( userPassword, passwordDetails.getPassword() ); |
| } |
| else |
| { |
| return compareBytes( receivedCredentials, storedCredentials ); |
| } |
| } |
| |
| |
| /** |
| * Compare two byte[] in a constant time. This is necessary because using an Array.equals() is |
| * not Timing attack safe ([1], [2] and [3]), a breach that can be exploited to break some hashes. |
| * |
| * [1] https://en.wikipedia.org/wiki/Timing_attack |
| * [2] http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-library/ |
| * [3] https://cryptocoding.net/index.php/Coding_rules |
| * |
| * @param provided The provided password |
| * @param stored The stored password |
| * @return <tt>true</tt> if the compared passwords are equal |
| */ |
| private static boolean compareBytes( byte[] provided, byte[] stored ) |
| { |
| if ( stored == null ) |
| { |
| return provided == null; |
| } |
| else if ( provided == null ) |
| { |
| return false; |
| } |
| |
| // Now, compare the two passwords, using a constant time method |
| if ( stored.length != provided.length ) |
| { |
| return false; |
| } |
| |
| // loop on *every* byte in both passwords, and at the end, if one char at least is different, return false. |
| int result = 0; |
| |
| for ( int i = 0; i < stored.length; i++ ) |
| { |
| // If both bytes are equal, xor will be == 0, otherwise it will be != 0 and so will result. |
| result |= ( stored[i] ^ provided[i] ); |
| } |
| |
| return result == 0; |
| } |
| |
| |
| /** |
| * encrypts the given credentials based on the algorithm name and optional salt |
| * |
| * @param credentials the credentials to be encrypted |
| * @param algorithm the algorithm to be used for encrypting the credentials |
| * @param salt value to be used as salt (optional) |
| * @return the encrypted credentials |
| */ |
| private static byte[] encryptPassword( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt ) |
| { |
| switch ( algorithm ) |
| { |
| case HASH_METHOD_SHA: |
| case HASH_METHOD_SSHA: |
| return digest( LdapSecurityConstants.HASH_METHOD_SHA, credentials, salt ); |
| |
| case HASH_METHOD_SHA256: |
| case HASH_METHOD_SSHA256: |
| return digest( LdapSecurityConstants.HASH_METHOD_SHA256, credentials, salt ); |
| |
| case HASH_METHOD_SHA384: |
| case HASH_METHOD_SSHA384: |
| return digest( LdapSecurityConstants.HASH_METHOD_SHA384, credentials, salt ); |
| |
| case HASH_METHOD_SHA512: |
| case HASH_METHOD_SSHA512: |
| return digest( LdapSecurityConstants.HASH_METHOD_SHA512, credentials, salt ); |
| |
| case HASH_METHOD_MD5: |
| case HASH_METHOD_SMD5: |
| return digest( LdapSecurityConstants.HASH_METHOD_MD5, credentials, salt ); |
| |
| case HASH_METHOD_CRYPT: |
| String saltWithCrypted = Crypt.crypt( Strings.utf8ToString( credentials ), Strings |
| .utf8ToString( salt ) ); |
| String crypted = saltWithCrypted.substring( 2 ); |
| return Strings.getBytesUtf8( crypted ); |
| |
| case HASH_METHOD_CRYPT_MD5: |
| case HASH_METHOD_CRYPT_SHA256: |
| case HASH_METHOD_CRYPT_SHA512: |
| String saltWithCrypted2 = Crypt.crypt( Strings.utf8ToString( credentials ), |
| algorithm.getSubPrefix() + Strings.utf8ToString( salt ) ); |
| String crypted2 = saltWithCrypted2.substring( saltWithCrypted2.lastIndexOf( '$' ) + 1 ); |
| return Strings.getBytesUtf8( crypted2 ); |
| |
| case HASH_METHOD_CRYPT_BCRYPT: |
| String crypted3 = BCrypt.hashPw( Strings.utf8ToString( credentials ), Strings.utf8ToString( salt ) ); |
| return Strings.getBytesUtf8( crypted3.substring( crypted3.length() - 31 ) ); |
| |
| case HASH_METHOD_PKCS5S2: |
| return generatePbkdf2Hash( credentials, algorithm, salt ); |
| |
| default: |
| return credentials; |
| } |
| } |
| |
| |
| /** |
| * Compute the hashed password given an algorithm, the credentials and |
| * an optional salt. |
| * |
| * @param algorithm the algorithm to use |
| * @param password the credentials |
| * @param salt the optional salt |
| * @return the digested credentials |
| */ |
| private static byte[] digest( LdapSecurityConstants algorithm, byte[] password, byte[] salt ) |
| { |
| MessageDigest digest; |
| |
| try |
| { |
| digest = MessageDigest.getInstance( algorithm.getAlgorithm() ); |
| } |
| catch ( NoSuchAlgorithmException e1 ) |
| { |
| return null; |
| } |
| |
| if ( salt != null ) |
| { |
| digest.update( password ); |
| digest.update( salt ); |
| return digest.digest(); |
| } |
| else |
| { |
| return digest.digest( password ); |
| } |
| } |
| |
| |
| /** |
| * Decompose the stored password in an algorithm, an eventual salt |
| * and the password itself. |
| * |
| * If the algorithm is SHA, SSHA, MD5 or SMD5, the part following the algorithm |
| * is base64 encoded |
| * |
| * @param credentials The byte[] containing the credentials to split |
| * @return The password |
| */ |
| public static PasswordDetails splitCredentials( byte[] credentials ) |
| { |
| LdapSecurityConstants algorithm = findAlgorithm( credentials ); |
| |
| // check plain text password |
| if ( algorithm == null ) |
| { |
| return new PasswordDetails( null, null, credentials ); |
| } |
| |
| int algoLength = algorithm.getPrefix().length() + 2; |
| byte[] password; |
| |
| switch ( algorithm ) |
| { |
| case HASH_METHOD_MD5: |
| case HASH_METHOD_SMD5: |
| return getCredentials( credentials, algoLength, MD5_LENGTH, algorithm ); |
| |
| case HASH_METHOD_SHA: |
| case HASH_METHOD_SSHA: |
| return getCredentials( credentials, algoLength, SHA1_LENGTH, algorithm ); |
| |
| case HASH_METHOD_SHA256: |
| case HASH_METHOD_SSHA256: |
| return getCredentials( credentials, algoLength, SHA256_LENGTH, algorithm ); |
| |
| case HASH_METHOD_SHA384: |
| case HASH_METHOD_SSHA384: |
| return getCredentials( credentials, algoLength, SHA384_LENGTH, algorithm ); |
| |
| case HASH_METHOD_SHA512: |
| case HASH_METHOD_SSHA512: |
| return getCredentials( credentials, algoLength, SHA512_LENGTH, algorithm ); |
| |
| case HASH_METHOD_PKCS5S2: |
| return getPbkdf2Credentials( credentials, algoLength, algorithm ); |
| |
| case HASH_METHOD_CRYPT: |
| // The password is associated with a salt. Decompose it |
| // in two parts, no decoding required. |
| // The salt comes first, not like for SSHA and SMD5, and is 2 bytes long |
| // The algorithm, salt, and password will be stored into the PasswordDetails structure. |
| byte[] salt = new byte[2]; |
| password = new byte[credentials.length - salt.length - algoLength]; |
| split( credentials, algoLength, salt, password ); |
| return new PasswordDetails( algorithm, salt, password ); |
| |
| case HASH_METHOD_CRYPT_BCRYPT: |
| salt = Arrays.copyOfRange( credentials, algoLength, credentials.length - 31 ); |
| password = Arrays.copyOfRange( credentials, credentials.length - 31, credentials.length ); |
| |
| return new PasswordDetails( algorithm, salt, password ); |
| |
| case HASH_METHOD_CRYPT_MD5: |
| case HASH_METHOD_CRYPT_SHA256: |
| case HASH_METHOD_CRYPT_SHA512: |
| // skip $x$ |
| algoLength = algoLength + 3; |
| return getCryptCredentials( credentials, algoLength, algorithm ); |
| |
| default: |
| // unknown method |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13010_UNKNOWN_HASH_ALGO, algorithm ) ); |
| } |
| } |
| |
| |
| /** |
| * Compute the credentials |
| * |
| * @param credentials the credentials |
| * @param algoLength The algorithm length |
| * @param hashLen The hash length |
| * @param algorithm the algorithm to use |
| * @return The split password string, containing the credentials, the salt and the password |
| */ |
| private static PasswordDetails getCredentials( byte[] credentials, int algoLength, int hashLen, |
| LdapSecurityConstants algorithm ) |
| { |
| // The password is associated with a salt. Decompose it |
| // in two parts, after having decoded the password. |
| // The salt is at the end of the credentials. |
| // The algorithm, salt, and password will be stored into the PasswordDetails structure. |
| byte[] passwordAndSalt = Base64 |
| .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() ); |
| |
| int saltLength = passwordAndSalt.length - hashLen; |
| byte[] salt = saltLength == 0 ? null : new byte[saltLength]; |
| byte[] password = new byte[hashLen]; |
| split( passwordAndSalt, 0, password, salt ); |
| |
| return new PasswordDetails( algorithm, salt, password ); |
| } |
| |
| |
| private static void split( byte[] all, int offset, byte[] left, byte[] right ) |
| { |
| System.arraycopy( all, offset, left, 0, left.length ); |
| if ( right != null ) |
| { |
| System.arraycopy( all, offset + left.length, right, 0, right.length ); |
| } |
| } |
| |
| |
| private static void merge( byte[] all, byte[] left, byte[] right ) |
| { |
| System.arraycopy( left, 0, all, 0, left.length ); |
| System.arraycopy( right, 0, all, left.length, right.length ); |
| } |
| |
| |
| /** |
| * checks if the given password's change time is older than the max age |
| * |
| * @param pwdChangedZtime time when the password was last changed |
| * @param pwdMaxAgeSec the max age value in seconds |
| * @param timeProvider The TimeProvider instance to use |
| * @return true if expired, false otherwise |
| */ |
| public static boolean isPwdExpired( String pwdChangedZtime, int pwdMaxAgeSec, TimeProvider timeProvider ) |
| { |
| Date pwdChangeDate = DateUtils.getDate( pwdChangedZtime ); |
| |
| //DIRSERVER-1735 |
| long time = pwdMaxAgeSec * 1000L; |
| time += pwdChangeDate.getTime(); |
| |
| Date expiryDate = DateUtils.getDate( DateUtils.getGeneralizedTime( time ) ); |
| Date now = DateUtils.getDate( DateUtils.getGeneralizedTime( timeProvider ) ); |
| |
| boolean expired = false; |
| |
| if ( expiryDate.equals( now ) || expiryDate.before( now ) ) |
| { |
| expired = true; |
| } |
| |
| return expired; |
| } |
| |
| |
| /** |
| * generates a hash based on the <a href="http://en.wikipedia.org/wiki/PBKDF2">PKCS5S2 spec</a> |
| * |
| * Note: this has been implemented to generate hashes compatible with what JIRA generates. |
| * See the <a href="http://pythonhosted.org/passlib/lib/passlib.hash.atlassian_pbkdf2_sha1.html">JIRA's passlib</a> |
| * |
| * @param credentials the credentials |
| * @param algorithm the algorithm to use |
| * @param salt the optional salt |
| * @return the digested credentials |
| */ |
| private static byte[] generatePbkdf2Hash( byte[] credentials, LdapSecurityConstants algorithm, byte[] salt ) |
| { |
| try |
| { |
| SecretKeyFactory sk = SecretKeyFactory.getInstance( algorithm.getAlgorithm() ); |
| char[] password = Strings.utf8ToString( credentials ).toCharArray(); |
| KeySpec keySpec = new PBEKeySpec( password, salt, 10000, PKCS5S2_LENGTH * 8 ); |
| Key key = sk.generateSecret( keySpec ); |
| return key.getEncoded(); |
| } |
| catch ( Exception e ) |
| { |
| throw new RuntimeException( e ); |
| } |
| } |
| |
| |
| /** |
| * Gets the credentials from a PKCS5S2 hash. |
| * The salt for PKCS5S2 hash is prepended to the password |
| * |
| * @param credentials The password |
| * @param algoLength The length of the algorithm part |
| * @param algorithm The algorithm in use |
| * @return The split credentials, containing the algorithm, the salt and the password |
| */ |
| private static PasswordDetails getPbkdf2Credentials( byte[] credentials, int algoLength, LdapSecurityConstants algorithm ) |
| { |
| // The password is associated with a salt. Decompose it |
| // in two parts, after having decoded the password. |
| // The salt is at the *beginning* of the credentials, and is 16 bytes long |
| // The algorithm, salt, and password will be stored into the PasswordDetails structure. |
| byte[] passwordAndSalt = Base64 |
| .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() ); |
| |
| int saltLength = passwordAndSalt.length - PKCS5S2_LENGTH; |
| byte[] salt = new byte[saltLength]; |
| byte[] password = new byte[PKCS5S2_LENGTH]; |
| |
| split( passwordAndSalt, 0, salt, password ); |
| |
| return new PasswordDetails( algorithm, salt, password ); |
| } |
| |
| |
| private static byte[] generateCryptSalt( int length ) |
| { |
| byte[] salt = new byte[length]; |
| SecureRandom sr = new SecureRandom(); |
| for ( int i = 0; i < salt.length; i++ ) |
| { |
| salt[i] = CRYPT_SALT_CHARS[sr.nextInt( CRYPT_SALT_CHARS.length )]; |
| } |
| |
| return salt; |
| } |
| |
| |
| private static PasswordDetails getCryptCredentials( byte[] credentials, int algoLength, |
| LdapSecurityConstants algorithm ) |
| { |
| // The password is associated with a salt. Decompose it |
| // in two parts, no decoding required. |
| // The salt length is dynamic, between the 2nd and 3rd '$'. |
| // The algorithm, salt, and password will be stored into the PasswordDetails structure. |
| |
| // skip {crypt}$x$ |
| int pos = algoLength; |
| while ( pos < credentials.length ) |
| { |
| if ( credentials[pos] == '$' ) |
| { |
| break; |
| } |
| |
| pos++; |
| } |
| |
| byte[] salt = Arrays.copyOfRange( credentials, algoLength, pos ); |
| byte[] password = Arrays.copyOfRange( credentials, pos + 1, credentials.length ); |
| |
| return new PasswordDetails( algorithm, salt, password ); |
| } |
| |
| } |