You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by se...@apache.org on 2016/02/21 10:26:16 UTC

svn commit: r1731515 - in /directory/shared/trunk/ldap/model/src: main/java/org/apache/directory/api/ldap/model/password/ test/java/org/apache/directory/api/ldap/model/password/

Author: seelmann
Date: Sun Feb 21 09:26:15 2016
New Revision: 1731515

URL: http://svn.apache.org/viewvc?rev=1731515&view=rev
Log:
* Cleanup PasswordUtil class and add tests
* Replace EncryptionMethod class with immutable PasswordDetails

Added:
    directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordDetails.java   (with props)
Removed:
    directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/EncryptionMethod.java
Modified:
    directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordUtil.java
    directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/api/ldap/model/password/PasswordUtilTest.java

Added: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordDetails.java
URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordDetails.java?rev=1731515&view=auto
==============================================================================
--- directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordDetails.java (added)
+++ directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordDetails.java Sun Feb 21 09:26:15 2016
@@ -0,0 +1,84 @@
+/*
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *   or more contributor license agreements.  See the NOTICE file
+ *   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 org.apache.directory.api.ldap.model.constants.LdapSecurityConstants;
+
+
+/**
+ * A class to store all informations about an password.
+ *
+ * This includes:
+ * - the used algorithm
+ * - the salt if any
+ * - the password itself.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class PasswordDetails
+{
+    private final LdapSecurityConstants algorithm;
+    private final byte[] salt;
+    private final byte[] password;
+
+
+    public PasswordDetails( LdapSecurityConstants algorithm, byte[] salt, byte[] password )
+    {
+        this.algorithm = algorithm;
+        this.salt = salt;
+        this.password = password;
+    }
+
+
+    /**
+     * The hash algorithm used to hash the password, null for plain text passwords.
+     * 
+     * @return the hash algorithm used to hash the password, null for plain text passwords
+     */
+    public LdapSecurityConstants getAlgorithm()
+    {
+        return algorithm;
+    }
+
+
+    /**
+     * The salt used to hash the password, null if no salt was used.
+     * 
+     * @return the salt used to hash the password, null if no salt was used
+     */
+    public byte[] getSalt()
+    {
+        return salt;
+    }
+
+
+    /**
+     * The hashed or plain text password.
+     * 
+     * @return the hashed or plain text password
+     */
+    public byte[] getPassword()
+    {
+        return password;
+    }
+
+}

Propchange: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordDetails.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordUtil.java
URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordUtil.java?rev=1731515&r1=1731514&r2=1731515&view=diff
==============================================================================
--- directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordUtil.java (original)
+++ directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/api/ldap/model/password/PasswordUtil.java Sun Feb 21 09:26:15 2016
@@ -144,6 +144,12 @@ public final class PasswordUtil
      */
     public static byte[] createStoragePassword( byte[] credentials, LdapSecurityConstants algorithm )
     {
+        // check plain text password
+        if ( algorithm == null )
+        {
+            return credentials;
+        }
+
         byte[] salt;
 
         switch ( algorithm )
@@ -264,21 +270,19 @@ public final class PasswordUtil
 
         if ( algorithm != null )
         {
-            EncryptionMethod encryptionMethod = new EncryptionMethod( 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
-            byte[] encryptedStored = PasswordUtil.splitCredentials( storedCredentials, encryptionMethod );
+            PasswordDetails passwordDetails = PasswordUtil.splitCredentials( storedCredentials );
 
             // Reuse the saltedPassword informations to construct the encrypted
             // password given by the user.
-            byte[] userPassword = PasswordUtil.encryptPassword( receivedCredentials, encryptionMethod.getAlgorithm(),
-                encryptionMethod.getSalt() );
+            byte[] userPassword = PasswordUtil.encryptPassword( receivedCredentials, passwordDetails.getAlgorithm(),
+                passwordDetails.getSalt() );
 
-            return compareBytes( userPassword, encryptedStored );
+            return compareBytes( userPassword, passwordDetails.getPassword() );
         }
         else
         {
@@ -419,66 +423,57 @@ public final class PasswordUtil
      * @return The password
      * @param credentials the credentials to split
      */
-    public static byte[] splitCredentials( byte[] credentials, EncryptionMethod encryptionMethod )
+    public static PasswordDetails splitCredentials( byte[] credentials )
     {
-        int algoLength = encryptionMethod.getAlgorithm().getPrefix().length() + 2;
+        LdapSecurityConstants algorithm = findAlgorithm( credentials );
 
-        switch ( encryptionMethod.getAlgorithm() )
+        // check plain text password
+        if ( algorithm == null )
         {
-            case HASH_METHOD_MD5:
-            case HASH_METHOD_SHA:
-                // We just have the password just after the algorithm, base64 encoded.
-                // Just decode the password and return it.
-                return Base64.decode(
-                    Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() );
+            return new PasswordDetails( null, null, credentials );
+        }
 
-            case HASH_METHOD_SMD5:
-                // The password is associated with a salt. Decompose it
-                // in two parts, after having decoded the password.
-                // The salt will be stored into the EncryptionMethod structure
-                // The salt is at the end of the credentials, and is 8 bytes long
-                byte[] passwordAndSalt = Base64.decode(
-                    Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() );
-
-                int saltLength = passwordAndSalt.length - MD5_LENGTH;
-                encryptionMethod.setSalt( new byte[saltLength] );
-                byte[] password = new byte[MD5_LENGTH];
-                split( passwordAndSalt, 0, password, encryptionMethod.getSalt() );
+        int algoLength = algorithm.getPrefix().length() + 2;
+        byte[] password = null;
 
-                return 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, encryptionMethod );
+                return getCredentials( credentials, algoLength, SHA1_LENGTH, algorithm );
 
             case HASH_METHOD_SHA256:
             case HASH_METHOD_SSHA256:
-                return getCredentials( credentials, algoLength, SHA256_LENGTH, encryptionMethod );
+                return getCredentials( credentials, algoLength, SHA256_LENGTH, algorithm );
 
             case HASH_METHOD_SHA384:
             case HASH_METHOD_SSHA384:
-                return getCredentials( credentials, algoLength, SHA384_LENGTH, encryptionMethod );
+                return getCredentials( credentials, algoLength, SHA384_LENGTH, algorithm );
 
             case HASH_METHOD_SHA512:
             case HASH_METHOD_SSHA512:
-                return getCredentials( credentials, algoLength, SHA512_LENGTH, encryptionMethod );
+                return getCredentials( credentials, algoLength, SHA512_LENGTH, algorithm );
 
             case HASH_METHOD_PKCS5S2:
-                return getPbkdf2Credentials( credentials, algoLength, encryptionMethod );
+                return getPbkdf2Credentials( credentials, algoLength, algorithm );
 
             case HASH_METHOD_CRYPT:
                 // The password is associated with a salt. Decompose it
                 // in two parts, storing the salt into the EncryptionMethod structure.
                 // The salt comes first, not like for SSHA and SMD5, and is 2 bytes long
-                encryptionMethod.setSalt( new byte[2] );
-                byte[] password2 = new byte[credentials.length - encryptionMethod.getSalt().length - algoLength];
-                split( credentials, algoLength, encryptionMethod.getSalt(), password2 );
+                byte[] salt = new byte[2];
+                password = new byte[credentials.length - salt.length - algoLength];
+                split( credentials, algoLength, salt, password );
 
-                return password2;
+                return new PasswordDetails( algorithm, salt, password );
 
             default:
                 // unknown method
-                return credentials;
-
+                throw new IllegalArgumentException( "Unknown hash algorithm " + algorithm );
         }
     }
 
@@ -486,8 +481,8 @@ public final class PasswordUtil
     /**
      * Compute the credentials
      */
-    private static byte[] getCredentials( byte[] credentials, int algoLength, int hashLen,
-        EncryptionMethod encryptionMethod )
+    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.
@@ -497,18 +492,21 @@ public final class PasswordUtil
             .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() );
 
         int saltLength = passwordAndSalt.length - hashLen;
-        encryptionMethod.setSalt( new byte[saltLength] );
+        byte[] salt = saltLength == 0 ? null : new byte[saltLength];
         byte[] password = new byte[hashLen];
-        split( passwordAndSalt, 0, password, encryptionMethod.getSalt() );
+        split( passwordAndSalt, 0, password, salt );
 
-        return password;
+        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 );
-        System.arraycopy( all, offset + left.length, right, 0, right.length );
+        if ( right != null )
+        {
+            System.arraycopy( all, offset + left.length, right, 0, right.length );
+        }
     }
 
 
@@ -579,7 +577,7 @@ public final class PasswordUtil
      * Gets the credentials from a PKCS5S2 hash.
      * The salt for PKCS5S2 hash is prepended to the password
      */
-    private static byte[] getPbkdf2Credentials( byte[] credentials, int algoLength, EncryptionMethod encryptionMethod )
+    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.
@@ -589,12 +587,12 @@ public final class PasswordUtil
             .decode( Strings.utf8ToString( credentials, algoLength, credentials.length - algoLength ).toCharArray() );
 
         int saltLength = passwordAndSalt.length - PKCS5S2_LENGTH;
-        encryptionMethod.setSalt( new byte[saltLength] );
+        byte[] salt = new byte[saltLength];
         byte[] password = new byte[PKCS5S2_LENGTH];
 
-        split( passwordAndSalt, 0, encryptionMethod.getSalt(), password );
+        split( passwordAndSalt, 0, salt, password );
 
-        return password;
+        return new PasswordDetails( algorithm, salt, password );
     }
 
 }

Modified: directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/api/ldap/model/password/PasswordUtilTest.java
URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/api/ldap/model/password/PasswordUtilTest.java?rev=1731515&r1=1731514&r2=1731515&view=diff
==============================================================================
--- directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/api/ldap/model/password/PasswordUtilTest.java (original)
+++ directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/api/ldap/model/password/PasswordUtilTest.java Sun Feb 21 09:26:15 2016
@@ -20,12 +20,36 @@
 
 package org.apache.directory.api.ldap.model.password;
 
-import static org.junit.Assert.assertTrue;
+
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_CRYPT;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_MD5;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_PKCS5S2;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SHA;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SHA256;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SHA384;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SHA512;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SMD5;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SSHA;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SSHA256;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SSHA384;
+import static org.apache.directory.api.ldap.model.constants.LdapSecurityConstants.HASH_METHOD_SSHA512;
+import static org.apache.directory.api.ldap.model.password.PasswordUtil.MD5_LENGTH;
+import static org.apache.directory.api.ldap.model.password.PasswordUtil.PKCS5S2_LENGTH;
+import static org.apache.directory.api.ldap.model.password.PasswordUtil.SHA1_LENGTH;
+import static org.apache.directory.api.ldap.model.password.PasswordUtil.SHA256_LENGTH;
+import static org.apache.directory.api.ldap.model.password.PasswordUtil.SHA384_LENGTH;
+import static org.apache.directory.api.ldap.model.password.PasswordUtil.SHA512_LENGTH;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
+import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants;
 import org.apache.directory.api.util.Strings;
 import org.junit.Test;
 
+
 /**
  * A test for the PasswordUtil class.
  *
@@ -39,21 +63,258 @@ public class PasswordUtilTest
     {
         // Simple cases
         assertTrue( PasswordUtil.compareCredentials( null, null ) );
-        assertTrue( PasswordUtil.compareCredentials( new byte[]{}, new byte[]{} ) );
-        assertTrue( PasswordUtil.compareCredentials( new byte[]{ 0x01 }, new byte[]{ 0x01 } ) );
-        
+        assertTrue( PasswordUtil.compareCredentials( new byte[]
+            {}, new byte[]
+            {} ) );
+        assertTrue( PasswordUtil.compareCredentials( new byte[]
+            { 0x01 }, new byte[]
+            { 0x01 } ) );
+
         // Simple failures
-        assertFalse( PasswordUtil.compareCredentials( null, new byte[]{ 0x01 } ) );
-        assertFalse( PasswordUtil.compareCredentials( new byte[]{ 0x01 }, null ) );
-        assertFalse( PasswordUtil.compareCredentials( new byte[]{ 0x01 }, new byte[]{ 0x02 } ) );
-        
+        assertFalse( PasswordUtil.compareCredentials( null, new byte[]
+            { 0x01 } ) );
+        assertFalse( PasswordUtil.compareCredentials( new byte[]
+            { 0x01 }, null ) );
+        assertFalse( PasswordUtil.compareCredentials( new byte[]
+            { 0x01 }, new byte[]
+            { 0x02 } ) );
+
         // With some different lengths
-        assertFalse( PasswordUtil.compareCredentials( Strings.getBytesUtf8( "Password1" ), Strings.getBytesUtf8( "Password1 " ) ) );
+        assertFalse( PasswordUtil.compareCredentials( Strings.getBytesUtf8( "Password1" ),
+            Strings.getBytesUtf8( "Password1 " ) ) );
 
         // With different passwords
-        assertFalse( PasswordUtil.compareCredentials( Strings.getBytesUtf8( "Password1" ), Strings.getBytesUtf8( "password1" ) ) );
+        assertFalse( PasswordUtil.compareCredentials( Strings.getBytesUtf8( "Password1" ),
+            Strings.getBytesUtf8( "password1" ) ) );
 
         // With same passwords
-        assertTrue( PasswordUtil.compareCredentials( Strings.getBytesUtf8( "Password1" ), Strings.getBytesUtf8( "Password1" ) ) );
+        assertTrue( PasswordUtil.compareCredentials( Strings.getBytesUtf8( "Password1" ),
+            Strings.getBytesUtf8( "Password1" ) ) );
+    }
+
+
+    @Test
+    public void testPasswordPlainText()
+    {
+        testPassword( "secret", "secret", null, 6, 0 );
+    }
+
+
+    @Test
+    public void testPasswordMD5Encrypted()
+    {
+        testPassword( "secret", "{MD5}Xr4ilOzQ4PCOq3aQ0qbuaQ==", HASH_METHOD_MD5, MD5_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordMD5EncryptedLowercase()
+    {
+        testPassword( "secret", "{md5}Xr4ilOzQ4PCOq3aQ0qbuaQ==", HASH_METHOD_MD5, MD5_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSMD5Encrypted()
+    {
+        testPassword( "secret", "{SMD5}tQ9wo/VBuKsqBtylMMCcORbnYOJFMyDJ", HASH_METHOD_SMD5, MD5_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSMD5EncryptedLowercase()
+    {
+        testPassword( "secret", "{smd5}tQ9wo/VBuKsqBtylMMCcORbnYOJFMyDJ", HASH_METHOD_SMD5, MD5_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSHAEncrypted()
+    {
+        testPassword( "secret", "{SHA}5en6G6MezRroT3XKqkdPOmY/BfQ=", HASH_METHOD_SHA, SHA1_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSHAEncryptedLowercase()
+    {
+        testPassword( "secret", "{sha}5en6G6MezRroT3XKqkdPOmY/BfQ=", HASH_METHOD_SHA, SHA1_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSSHAEncrypted()
+    {
+        testPassword( "secret", "{SSHA}mjVVxasFkk59wMW4L1Ldt+YCblfhULHs03WW7g==", HASH_METHOD_SSHA, SHA1_LENGTH, 8 );
     }
+
+
+    @Test
+    public void testPasswordSSHAEncryptedLowercase()
+    {
+        testPassword( "secret", "{ssha}mjVVxasFkk59wMW4L1Ldt+YCblfhULHs03WW7g==", HASH_METHOD_SSHA, SHA1_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSHA256Encrypted()
+    {
+        testPassword( "secret", "{SHA256}K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=", HASH_METHOD_SHA256,
+            SHA256_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSHA256EncryptedLowercase()
+    {
+        testPassword( "secret", "{sha256}K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=",
+            HASH_METHOD_SHA256, SHA256_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSSHA256Encrypted()
+    {
+        testPassword( "secret", "{SSHA256}MVfpHvqPUIXJb1uZCVtX1JeDokt9EHgHMMSexe/92lb2vfMrmUHnkw==",
+            HASH_METHOD_SSHA256, SHA256_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSSHA256EncryptedLowercase()
+    {
+        testPassword( "secret", "{ssha256}MVfpHvqPUIXJb1uZCVtX1JeDokt9EHgHMMSexe/92lb2vfMrmUHnkw==",
+            HASH_METHOD_SSHA256, SHA256_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSHA384Encrypted()
+    {
+        testPassword( "secret", "{SHA384}WKd1ukESvjAFrkQHznV9iP2nHUBJe7gCbsrFTU4//HIyzo3jq1rLMK45dg/ufFPt",
+            HASH_METHOD_SHA384, SHA384_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSHA384EncryptedLowercase()
+    {
+        testPassword( "secret", "{sha384}WKd1ukESvjAFrkQHznV9iP2nHUBJe7gCbsrFTU4//HIyzo3jq1rLMK45dg/ufFPt",
+            HASH_METHOD_SHA384, SHA384_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSSHA384Encrypted()
+    {
+        testPassword( "secret", "{SSHA384}Ryj+LRp+FKIt0X6PhsqT4kK/76hO6bNeQWha0sMflaY2x2L+nSv/Z7oVMQFTde8Vttn+RFJFIL0=",
+            HASH_METHOD_SSHA384, SHA384_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSSHA384EncryptedLowercase()
+    {
+        testPassword( "secret", "{ssha384}Ryj+LRp+FKIt0X6PhsqT4kK/76hO6bNeQWha0sMflaY2x2L+nSv/Z7oVMQFTde8Vttn+RFJFIL0=",
+            HASH_METHOD_SSHA384, SHA384_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSHA512Encrypted()
+    {
+        testPassword( "secret",
+            "{SHA512}vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==",
+            HASH_METHOD_SHA512, SHA512_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSHA512EncryptedLowercase()
+    {
+        testPassword( "secret",
+            "{sha512}vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==",
+            HASH_METHOD_SHA512, SHA512_LENGTH, 0 );
+    }
+
+
+    @Test
+    public void testPasswordSSHA512Encrypted()
+    {
+        testPassword( "secret",
+            "{SSHA512}ZXa+mKUUX657jXwVF4t6djmniDAZG2O2Xk8YTbmau5qWjpZ6FGH0Nql0uR18+sUxATjJbF6YHZr6GjRxVDLgknh9nUZmK26+",
+            HASH_METHOD_SSHA512, SHA512_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordSSHA512EncryptedLowercase()
+    {
+        testPassword( "secret",
+            "{ssha512}ZXa+mKUUX657jXwVF4t6djmniDAZG2O2Xk8YTbmau5qWjpZ6FGH0Nql0uR18+sUxATjJbF6YHZr6GjRxVDLgknh9nUZmK26+",
+            HASH_METHOD_SSHA512, SHA512_LENGTH, 8 );
+    }
+
+
+    @Test
+    public void testPasswordPKCS5S2Encrypted()
+    {
+        testPassword( "secret", "{PKCS5S2}3L9Bz29r+5fGHlItzYcMlWeJHl7xWYTlaeEOzzx5aHntdP4DyK4hKQCidxcHMwz8",
+            HASH_METHOD_PKCS5S2, PKCS5S2_LENGTH, 16 );
+    }
+
+
+    @Test
+    public void testPasswordPKCS5S2EncryptedLowercase()
+    {
+        testPassword( "secret", "{pkcs5s2}3L9Bz29r+5fGHlItzYcMlWeJHl7xWYTlaeEOzzx5aHntdP4DyK4hKQCidxcHMwz8",
+            HASH_METHOD_PKCS5S2, PKCS5S2_LENGTH, 16 );
+    }
+
+
+    @Test
+    public void testPasswordCRYPTEncrypted()
+    {
+        testPassword( "secret", "{CRYPT}qFkH8Z1woBlXw", HASH_METHOD_CRYPT, 11, 2 );
+    }
+
+
+    @Test
+    public void testPasswordCRYPTEncryptedLowercase()
+    {
+        testPassword( "secret", "{crypt}qFkH8Z1woBlXw", HASH_METHOD_CRYPT, 11, 2 );
+    }
+
+
+    private void testPassword( String plainText, String encrypted, LdapSecurityConstants algorithm, int passwordLength,
+        int saltLength )
+    {
+        // assert findAlgorithm
+        assertEquals( algorithm, PasswordUtil.findAlgorithm( Strings.getBytesUtf8( encrypted ) ) );
+
+        // assert compareCredentials
+        assertTrue(
+            PasswordUtil.compareCredentials( Strings.getBytesUtf8( plainText ), Strings.getBytesUtf8( encrypted ) ) );
+
+        // assert splitCredentials
+        PasswordDetails passwordDetails = PasswordUtil.splitCredentials( Strings.getBytesUtf8( encrypted ) );
+        assertEquals( algorithm, passwordDetails.getAlgorithm() );
+        if ( saltLength == 0 )
+        {
+            assertNull( passwordDetails.getSalt() );
+        }
+        else
+        {
+            assertNotNull( passwordDetails.getSalt() );
+            assertEquals( saltLength, passwordDetails.getSalt().length );
+        }
+        assertNotNull( passwordDetails.getPassword() );
+        assertEquals( passwordLength, passwordDetails.getPassword().length );
+
+        // assert createStoragePassword / compareCredentials roundtrip
+        byte[] generated = PasswordUtil.createStoragePassword( plainText, algorithm );
+        assertTrue(
+            PasswordUtil.compareCredentials( Strings.getBytesUtf8( plainText ), generated ) );
+    }
+
 }