commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ggreg...@apache.org
Subject svn commit: r1328414 [1/2] - in /commons/proper/codec/trunk/src: changes/ main/java/org/apache/commons/codec/digest/ site/xdoc/ test/java/org/apache/commons/codec/digest/
Date Fri, 20 Apr 2012 15:36:59 GMT
Author: ggregory
Date: Fri Apr 20 15:36:58 2012
New Revision: 1328414

URL: http://svn.apache.org/viewvc?rev=1328414&view=rev
Log:
[CODEC-133] Add classes for MD5/SHA1/SHA-512-based Unix crypt(3) hash variants.

Added:
    commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/B64.java
    commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Crypt.java
    commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java
    commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java
    commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java
    commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Apr1CryptTest.java
    commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/B64Test.java
    commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/CryptTest.java
    commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Md5CryptTest.java
    commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha256CryptTest.java
    commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha512CryptTest.java
    commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/UnixCryptTest.java
Modified:
    commons/proper/codec/trunk/src/changes/changes.xml
    commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/package.html
    commons/proper/codec/trunk/src/site/xdoc/userguide.xml

Modified: commons/proper/codec/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/changes/changes.xml?rev=1328414&r1=1328413&r2=1328414&view=diff
==============================================================================
--- commons/proper/codec/trunk/src/changes/changes.xml (original)
+++ commons/proper/codec/trunk/src/changes/changes.xml Fri Apr 20 15:36:58 2012
@@ -51,6 +51,9 @@ The <action> type attribute can be add,u
     </release>
     -->
     <release version="1.7" date="TBD" description="Feature and fix release.">
+      <action dev="ggregory" type="fix" issue="CODEC-133" due-to="lathspell">
+        Add classes for MD5/SHA1/SHA-512-based Unix crypt(3) hash variants.
+      </action>
       <action dev="ggregory" type="fix" issue="CODEC-96" due-to="sebb">
         Base64 encode() method is no longer thread-safe, breaking clients using it as a shared BinaryEncoder.
         Note: the fix breaks binary compatibility, however the changes are to a class (BaseNCodec) which is

Added: commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/B64.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/B64.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/B64.java (added)
+++ commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/B64.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,74 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.util.Random;
+
+/**
+ * Base64 like method to convert binary bytes into ASCII chars.
+ * 
+ * TODO: Can Base64 be reused?
+ * 
+ * @version $Id $
+ * @since 1.7
+ */
+class B64 {
+
+    /**
+     * Table with characters for Base64 transformation.
+     */
+    static final String B64T = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+    /**
+     * Base64 like conversion of bytes to ASCII chars.
+     * 
+     * @param b2
+     *            A byte from the result.
+     * @param b1
+     *            A byte from the result.
+     * @param b0
+     *            A byte from the result.
+     * @param outLen
+     *            The number of expected output chars.
+     * @param buffer
+     *            Where the output chars is appended to.
+     */
+    static void b64from24bit(byte b2, byte b1, byte b0, int outLen, StringBuilder buffer) {
+        // The bit masking is necessary because the JVM byte type is signed!
+        int w = ((b2 << 16) & 0x00ffffff) | ((b1 << 8) & 0x00ffff) | (b0 & 0xff);
+        // It's effectively a "for" loop but kept to resemble the original C code.
+        int n = outLen;
+        while (n-- > 0) {
+            buffer.append(B64T.charAt(w & 0x3f));
+            w >>= 6;
+        }
+    }
+
+    /**
+     * Generates a string of random chars from the B64T set.
+     * 
+     * @param num
+     *            Number of chars to generate.
+     */
+    static String getRandomSalt(int num) {
+        StringBuilder saltString = new StringBuilder();
+        for (int i = 1; i <= num; i++) {
+            saltString.append(B64T.charAt(new Random().nextInt(B64T.length())));
+        }
+        return saltString.toString();
+    }
+}

Added: commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Crypt.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Crypt.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Crypt.java (added)
+++ commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Crypt.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,137 @@
+/*
+ * 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.commons.codec.digest;
+
+import org.apache.commons.codec.Charsets;
+
+/**
+ * GNU libc crypt(3) compatible hash method.
+ * 
+ * See {@link #crypt(String, String)} for further details.
+ * 
+ * @version $Id $
+ * @since 1.7
+ */
+public class Crypt {
+
+    /**
+     * Encrypts a password in a crypt(3) compatible way.
+     * 
+     * A random salt and the default algorithm (currently SHA-512) are used. See
+     * {@link #crypt(String, String)} for details.
+     * 
+     * @param keyBytes
+     *            The plaintext password.
+     * @return The hash value.
+     */
+    public static String crypt(byte[] keyBytes) throws Exception {
+        return crypt(keyBytes, null);
+    }
+
+    /**
+     * Encrypts a password in a crypt(3) compatible way.
+     * 
+     * A random salt and the default algorithm (currently SHA-512) are used. See
+     * {@link #crypt(String, String)} for details.
+     * 
+     * @param keyBytes
+     *            The plaintext password.
+     * @param salt
+     *            The salt value
+     * @return The hash value.
+     */
+    public static String crypt(byte[] keyBytes, String salt) throws Exception {
+        if (salt == null) {
+            return Sha2Crypt.sha512Crypt(keyBytes);
+        } else if (salt.startsWith(Sha2Crypt.SHA512_PREFIX)) {
+            return Sha2Crypt.sha512Crypt(keyBytes, salt);
+        } else if (salt.startsWith(Sha2Crypt.SHA256_PREFIX)) {
+            return Sha2Crypt.sha256Crypt(keyBytes, salt);
+        } else if (salt.startsWith(Md5Crypt.MD5_PREFIX)) {
+            return Md5Crypt.md5Crypt(keyBytes, salt);
+        } else {
+            return UnixCrypt.crypt(keyBytes, salt);
+        }
+    }
+
+    /**
+     * Calculates the digest using the strongest crypt(3) algorithm.
+     * 
+     * A random salt and the default algorithm (currently SHA-512) are used.
+     * 
+     * @see #crypt(String, String)
+     * @param key
+     *            The plaintext password.
+     * @return The hash value.
+     */
+    public static String crypt(String key) throws Exception {
+        return crypt(key, null);
+    }
+
+    /**
+     * Encrypts a password in a crypt(3) compatible way.
+     * 
+     * <p>
+     * The exact algorithm depends on the format of the salt string:
+     * <ul>
+     * <li>SHA-512 salts start with $6$ and are up to 16 chars long.
+     * <li>SHA-256 salts start with $5$ and are up to 16 chars long
+     * <li>MD5 salts start with "$1$" and are up to 8 chars long
+     * <li>DES, the traditional UnixCrypt algorithm is used else with only 2 chars
+     * <li>Only the first 8 chars of the passwords are used in the DES algorithm!
+     * </ul>
+     * The magic strings "$apr1$" and "$2a$" are not recognised by this method as its output should be identical with
+     * that of the libc implementation.
+     * 
+     * <p>
+     * The rest of the salt string is drawn from the set [a-zA-Z0-9./] and is cut at the maximum length of if a "$" sign
+     * is encountered. It is therefore valid to enter a complete hash value as salt to e.g. verify a password with:
+     * storedPwd.equals(crypt(enteredPwd, storedPwd))
+     * 
+     * <p>
+     * The resulting string starts with the marker string ($6$), continues with the salt value and ends with a "$" sign
+     * followed by the actual hash value. For DES the string only contains the salt and actual hash. It's toal length is
+     * dependend on the algorithm used:
+     * <ul>
+     * <li>SHA-512: 106 chars
+     * <li>SHA-256: 63 chars
+     * <li>MD5: 34 chars
+     * <li>DES: 13 chars
+     * </ul>
+     * 
+     * <p>
+     * Example:
+     * 
+     * <pre>
+     *      crypt("secret", "$1$xxxx") => "$1$xxxx$aMkevjfEIpa35Bh3G4bAc."
+     *      crypt("secret", "xx") => "xxWAum7tHdIUw"
+     * </pre>
+     * 
+     * This method comes in a variation that accepts a byte[] array to support input strings that are not encoded in
+     * UTF-8 but e.g. in ISO-8859-1 where equal characters result in different byte values.
+     * 
+     * @see "The man page of the libc crypt (3) function."
+     * @param key
+     *            The plaintext password as entered by the used.
+     * @param salt
+     *            The salt value
+     * @return The hash value i.e. encrypted password including the salt string
+     */
+    public static String crypt(String key, String salt) throws Exception {
+        return crypt(key.getBytes(Charsets.UTF_8), salt);
+    }
+}

Added: commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java (added)
+++ commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Md5Crypt.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,259 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.Charsets;
+
+/**
+ * The libc crypt() "$1$" and Apache "$apr1$" MD5-based hash algorithm.
+ * <p>
+ * Based on the public domain ("beer-ware") C implementation from Poul-Henning Kamp which was found at:
+ * </p>
+ * <p>
+ * http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libcrypt/crypt-md5.c?rev=1.1;content-type=text%2Fplain</br>
+ * Source: $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.1 1999/01/21 13:50:09 brandon Exp $
+ * </p>
+ * <p>
+ * Conversion to Kotlin and from there to Java in 2012.
+ * </p>
+ * <p>
+ * The C style comments are from the original C code, the ones with "//" from me.
+ * </p>
+ * 
+ * @version $Id $
+ * @since 1.7
+ */
+public class Md5Crypt {
+
+    /**
+     * The Identifier of the Apache variant.
+     */
+    static final String APR1_PREFIX = "$apr1$";
+
+    /**
+     * The number of bytes of the final hash.
+     */
+    private static final int BLOCKSIZE = 16;
+
+    /**
+     * The MessageDigest MD5_ALGORITHM.
+     */
+    private static final String MD5_ALGORITHM = "MD5";
+
+    /**
+     * The Identifier of this crypt() variant.
+     */
+    static final String MD5_PREFIX = "$1$";
+
+    /**
+     * The number of rounds of the big loop.
+     */
+    private static final int ROUNDS = 1000;
+
+    /** See {@link #apr1Crypt(String, String)} for details. */
+    public static String apr1Crypt(byte[] keyBytes) throws Exception {
+        return apr1Crypt(keyBytes, APR1_PREFIX + B64.getRandomSalt(8));
+    }
+
+    /** See {@link #apr1Crypt(String, String)} for details. */
+    public static String apr1Crypt(byte[] keyBytes, String salt) throws Exception {
+        // to make the md5Crypt regex happy
+        if (salt != null && !salt.startsWith(APR1_PREFIX)) {
+            salt = APR1_PREFIX + salt;
+        }
+        return Md5Crypt.md5Crypt(keyBytes, salt, APR1_PREFIX);
+    }
+
+    /** See {@link #apr1Crypt(String, String)} for details. */
+    public static String apr1Crypt(String keyBytes) throws Exception {
+        return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8));
+    }
+
+    /**
+     * Generates an Apache htpasswd compatible "$apr1$" MD5 based hash value. *
+     * 
+     * The algorithm is identical to the crypt(3) "$1$" one but produces different outputs due to the different salt
+     * prefix.
+     * 
+     * @param keyBytes
+     *            The plaintext string that should be hashed.
+     * @param salt
+     *            Salt string including the prefix and optionally garbage at the end. Will be generated randomly if
+     *            null.
+     */
+    public static String apr1Crypt(String keyBytes, String salt) throws Exception {
+        return apr1Crypt(keyBytes.getBytes(Charsets.UTF_8), salt);
+    }
+
+    /**
+     * Generates a libc6 crypt() compatible "$1$" hash value.
+     * 
+     * See {@link Crypt#crypt(String, String)} for details.
+     */
+    public static String md5Crypt(final byte[] keyBytes) throws Exception {
+        return md5Crypt(keyBytes, MD5_PREFIX + B64.getRandomSalt(8));
+    }
+
+    /**
+     * Generates a libc crypt() compatible "$1$" MD5 based hash value.
+     * 
+     * See {@link Crypt#crypt(String, String)} for details.
+     * 
+     * @param keyBytes
+     *            The plaintext string that should be hashed.
+     * @param salt
+     *            Salt string including the prefix and optionally garbage at the end. Will be generated randomly if
+     *            null.
+     */
+    public static String md5Crypt(byte[] keyBytes, String salt) throws Exception {
+        return md5Crypt(keyBytes, salt, MD5_PREFIX);
+    }
+
+    /**
+     * Generates a libc6 crypt() "$1$" or Apache htpasswd "$apr1$" hash value.
+     * 
+     * See {@link Crypt#crypt(String, String)} or {@link #apr1Crypt(String, String)} for details.
+     */
+    public static String md5Crypt(final byte[] keyBytes, final String salt, final String prefix) throws Exception {
+        int keyLen = keyBytes.length;
+
+        // Extract the real salt from the given string which can be a complete hash string.
+        String saltString;
+        if (salt == null) {
+            saltString = B64.getRandomSalt(8);
+        } else {
+            Pattern p = Pattern.compile("^" + prefix.replace("$", "\\$") + "([\\.\\/a-zA-Z0-9]{1,8}).*");
+            Matcher m = p.matcher(salt);
+            if (m == null || !m.find()) {
+                throw new IllegalArgumentException("Invalid salt value: " + salt);
+            }
+            saltString = m.group(1);
+        }
+        byte[] saltBytes = saltString.getBytes(Charsets.UTF_8);
+
+        MessageDigest ctx = MessageDigest.getInstance(MD5_ALGORITHM);
+
+        /*
+         * The password first, since that is what is most unknown
+         */
+        ctx.update(keyBytes);
+
+        /*
+         * Then our magic string
+         */
+        ctx.update(prefix.getBytes(Charsets.UTF_8));
+
+        /*
+         * Then the raw salt
+         */
+        ctx.update(saltBytes);
+
+        /*
+         * Then just as many characters of the MD5(pw,salt,pw)
+         */
+        MessageDigest ctx1 = MessageDigest.getInstance(MD5_ALGORITHM);
+        ctx1.update(keyBytes);
+        ctx1.update(saltBytes);
+        ctx1.update(keyBytes);
+        byte[] finalb = ctx1.digest();
+        int ii = keyLen;
+        while (ii > 0) {
+            ctx.update(finalb, 0, ii > 16 ? 16 : ii);
+            ii -= 16;
+        }
+
+        /*
+         * Don't leave anything around in vm they could use.
+         */
+        Arrays.fill(finalb, (byte) 0);
+
+        /*
+         * Then something really weird...
+         */
+        ii = keyLen;
+        int j = 0;
+        while (ii > 0) {
+            if ((ii & 1) == 1) {
+                ctx.update(finalb[j]);
+            } else {
+                ctx.update(keyBytes[j]);
+            }
+            ii >>= 1;
+        }
+
+        /*
+         * Now make the output string
+         */
+        StringBuilder passwd = new StringBuilder(prefix + saltString + "$");
+        finalb = ctx.digest();
+
+        /*
+         * and now, just to make sure things don't run too fast On a 60 Mhz Pentium this takes 34 msec, so you would
+         * need 30 seconds to build a 1000 entry dictionary...
+         */
+        for (int i = 0; i < ROUNDS; i++) {
+            ctx1 = MessageDigest.getInstance(MD5_ALGORITHM);
+            if ((i & 1) != 0) {
+                ctx1.update(keyBytes);
+            } else {
+                ctx1.update(finalb, 0, BLOCKSIZE);
+            }
+
+            if (i % 3 != 0) {
+                ctx1.update(saltBytes);
+            }
+
+            if (i % 7 != 0) {
+                ctx1.update(keyBytes);
+            }
+
+            if ((i & 1) != 0) {
+                ctx1.update(finalb, 0, BLOCKSIZE);
+            } else {
+                ctx1.update(keyBytes);
+            }
+            finalb = ctx1.digest();
+        }
+
+        // The following was nearly identical to the Sha2Crypt code.
+        // Again, the buflen is not really needed.
+        // int buflen = MD5_PREFIX.length() - 1 + salt_string.length() + 1 + BLOCKSIZE + 1;
+        B64.b64from24bit(finalb[0], finalb[6], finalb[12], 4, passwd);
+        B64.b64from24bit(finalb[1], finalb[7], finalb[13], 4, passwd);
+        B64.b64from24bit(finalb[2], finalb[8], finalb[14], 4, passwd);
+        B64.b64from24bit(finalb[3], finalb[9], finalb[15], 4, passwd);
+        B64.b64from24bit(finalb[4], finalb[10], finalb[5], 4, passwd);
+        B64.b64from24bit((byte) 0, (byte) 0, finalb[11], 2, passwd);
+
+        /*
+         * Don't leave anything around in vm they could use.
+         */
+        // Is there a better way to do this with the JVM?
+        ctx.reset();
+        ctx1.reset();
+        Arrays.fill(keyBytes, (byte) 0);
+        Arrays.fill(saltBytes, (byte) 0);
+        Arrays.fill(finalb, (byte) 0);
+
+        return passwd.toString();
+    }
+}

Added: commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java (added)
+++ commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/Sha2Crypt.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,513 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * SHA2-based Unix crypt implementation.
+ * 
+ * <p>
+ * Based on the C implementation released into the Public Domain by Ulrich Drepper &lt;drepper@redhat.com&gt;
+ * http://www.akkadia.org/drepper/SHA-crypt.txt
+ * </p>
+ * 
+ * <p>
+ * Conversion to Kotlin and from there to Java in 2012 by Christian Hammers &lt;ch@lathspell.de&gt; and likewise put
+ * into the Public Domain.
+ * </p>
+ * 
+ * @version $Id $
+ * @since 1.7
+ */
+public class Sha2Crypt {
+
+    /**
+     * Default number of rounds if not explicitly specified.
+     */
+    private static final int ROUNDS_DEFAULT = 5000;
+    
+    /**
+     * Maximum number of rounds.
+     */
+    private static final int ROUNDS_MAX = 999999999;
+    
+    /**
+     * Minimum number of rounds.
+     */
+    private static final int ROUNDS_MIN = 1000;
+    
+    /**
+     * Prefix for optional rounds specification.
+     */
+    private static final String ROUNDS_PREFIX = "rounds=";
+    
+    /**
+     * The MessageDigest algorithm.
+     */
+    private static final String SHA256_ALGORITHM = "SHA-256";
+    
+    /**
+     * The number of bytes the final hash value will have.
+     */
+    private static final int SHA256_BLOCKSIZE = 32;
+    
+    /**
+     * The prefixes that can be used to identify this crypt() variant.
+     */
+    static final String SHA256_PREFIX = "$5$";
+    
+    private static final String SHA512_ALGORITHM = "SHA-512";
+    
+    private static final int SHA512_BLOCKSIZE = 64;
+    
+    static final String SHA512_PREFIX = "$6$";
+
+    /**
+     * Generates a libc crypt() compatible "$5$" hash value with random salt.
+     * 
+     * See {@link Crypt#crypt(String, String)} for details.
+     */
+    public static String sha256Crypt(byte[] keyBytes) throws Exception {
+        return sha256Crypt(keyBytes, null);
+    }
+
+    /**
+     * Generates a libc6 crypt() compatible "$5$" hash value.
+     * 
+     * See {@link Crypt#crypt(String, String)} for details.
+     */
+    public static String sha256Crypt(byte[] keyBytes, String salt) throws Exception {
+        if (salt == null) {
+            salt = SHA256_PREFIX + B64.getRandomSalt(8);
+        }
+        return sha2Crypt(keyBytes, salt, SHA256_PREFIX, SHA256_BLOCKSIZE, SHA256_ALGORITHM);
+    }
+
+    /**
+     * Generates a libc6 crypt() compatible "$5$" or "$6$" SHA2 based hash value.
+     * 
+     * This is a nearly line by line conversion of the original C function. The numbered comments are from the algorithm
+     * description, the short C-style ones from the original C code and the ones with "Remark" from me.
+     * 
+     * See {@link Crypt#crypt(String, String)} for details.
+     * 
+     * @param keyBytes
+     *            The plaintext that should be hashed.
+     * @param salt_string
+     *            The real salt value without prefix or "rounds=".
+     * @param saltPrefix
+     *            Either $5$ or $6$.
+     * @param blocksize
+     *            A value that differs between $5$ and $6$.
+     * @param algorithm
+     *            The MessageDigest algorithm identifier string.
+     * @return The complete hash value including prefix and salt.
+     */
+    private static String sha2Crypt(byte[] keyBytes, String salt, String saltPrefix, int blocksize, String algorithm)
+            throws Exception {
+        int keyLen = keyBytes.length;
+
+        // Extracts effective salt and the number of rounds from the given salt.
+        int rounds = ROUNDS_DEFAULT;
+        boolean roundsCustom = false;
+        if (salt == null) {
+            throw new IllegalArgumentException("Invalid salt value: null");
+        }
+        Pattern p = Pattern.compile("^\\$([56])\\$(rounds=(\\d+)\\$)?([\\.\\/a-zA-Z0-9]{1,16}).*");
+        Matcher m = p.matcher(salt);
+        if (m == null || !m.find()) {
+            throw new IllegalArgumentException("Invalid salt value: " + salt);
+        }
+        if (m.group(3) != null) {
+            rounds = Integer.valueOf(m.group(3));
+            rounds = Math.max(ROUNDS_MIN, Math.min(ROUNDS_MAX, rounds));
+            roundsCustom = true;
+        }
+        String saltString = m.group(4);
+        byte[] saltBytes = saltString.getBytes("UTF-8");
+        int saltLen = saltBytes.length;
+
+        // 1. start digest A
+        // Prepare for the real work.
+        MessageDigest ctx = MessageDigest.getInstance(algorithm);
+
+        // 2. the password string is added to digest A
+        /*
+         * Add the key string.
+         */
+        ctx.update(keyBytes);
+
+        // 3. the salt string is added to digest A. This is just the salt string
+        // itself without the enclosing '$', without the magic salt_prefix $5$ and
+        // $6$ respectively and without the rounds=<N> specification.
+        //
+        // NB: the MD5 algorithm did add the $1$ salt_prefix. This is not deemed
+        // necessary since it is a constant string and does not add security
+        // and /possibly/ allows a plain text attack. Since the rounds=<N>
+        // specification should never be added this would also create an
+        // inconsistency.
+        /*
+         * The last part is the salt string. This must be at most 16 characters and it ends at the first `$' character
+         * (for compatibility with existing implementations).
+         */
+        ctx.update(saltBytes);
+
+        // 4. start digest B
+        /*
+         * Compute alternate sha512 sum with input KEY, SALT, and KEY. The final result will be added to the first
+         * context.
+         */
+        MessageDigest altCtx = MessageDigest.getInstance(algorithm);
+
+        // 5. add the password to digest B
+        /*
+         * Add key.
+         */
+        altCtx.update(keyBytes);
+
+        // 6. add the salt string to digest B
+        /*
+         * Add salt.
+         */
+        altCtx.update(saltBytes);
+
+        // 7. add the password again to digest B
+        /*
+         * Add key again.
+         */
+        altCtx.update(keyBytes);
+
+        // 8. finish digest B
+        /*
+         * Now get result of this (32 bytes) and add it to the other context.
+         */
+        byte[] altResult = altCtx.digest();
+
+        // 9. For each block of 32 or 64 bytes in the password string (excluding
+        // the terminating NUL in the C representation), add digest B to digest A
+        /*
+         * Add for any character in the key one byte of the alternate sum.
+         */
+        /*
+         * (Remark: the C code comment seems wrong for key length > 32!)
+         */
+        int cnt = keyBytes.length;
+        while (cnt > blocksize) {
+            ctx.update(altResult, 0, blocksize);
+            cnt -= blocksize;
+        }
+
+        // 10. For the remaining N bytes of the password string add the first
+        // N bytes of digest B to digest A
+        ctx.update(altResult, 0, cnt);
+
+        // 11. For each bit of the binary representation of the length of the
+        // password string up to and including the highest 1-digit, starting
+        // from to lowest bit position (numeric value 1):
+        //
+        // a) for a 1-digit add digest B to digest A
+        //
+        // b) for a 0-digit add the password string
+        //
+        // NB: this step differs significantly from the MD5 algorithm. It
+        // adds more randomness.
+        /*
+         * Take the binary representation of the length of the key and for every 1 add the alternate sum, for every 0
+         * the key.
+         */
+        cnt = keyBytes.length;
+        while (cnt > 0) {
+            if ((cnt & 1) != 0) {
+                ctx.update(altResult, 0, blocksize);
+            } else {
+                ctx.update(keyBytes);
+            }
+            cnt >>= 1;
+        }
+
+        // 12. finish digest A
+        /*
+         * Create intermediate result.
+         */
+        altResult = ctx.digest();
+
+        // 13. start digest DP
+        /*
+         * Start computation of P byte sequence.
+         */
+        altCtx = MessageDigest.getInstance(algorithm);
+
+        // 14. for every byte in the password (excluding the terminating NUL byte
+        // in the C representation of the string)
+        //
+        // add the password to digest DP
+        /*
+         * For every character in the password add the entire password.
+         */
+        for (int i = 1; i <= keyLen; i++) {
+            altCtx.update(keyBytes);
+        }
+
+        // 15. finish digest DP
+        /*
+         * Finish the digest.
+         */
+        byte[] tempResult = altCtx.digest();
+
+        // 16. produce byte sequence P of the same length as the password where
+        //
+        // a) for each block of 32 or 64 bytes of length of the password string
+        // the entire digest DP is used
+        //
+        // b) for the remaining N (up to 31 or 63) bytes use the first N
+        // bytes of digest DP
+        /*
+         * Create byte sequence P.
+         */
+        byte[] pBytes = new byte[keyLen];
+        int cp = 0;
+        while (cp < keyLen - blocksize) {
+            System.arraycopy(tempResult, 0, pBytes, cp, blocksize);
+            cp += blocksize;
+        }
+        System.arraycopy(tempResult, 0, pBytes, cp, keyLen - cp);
+
+        // 17. start digest DS
+        /*
+         * Start computation of S byte sequence.
+         */
+        altCtx = MessageDigest.getInstance(algorithm);
+
+        // 18. repeast the following 16+A[0] times, where A[0] represents the first
+        // byte in digest A interpreted as an 8-bit unsigned value
+        //
+        // add the salt to digest DS
+        /*
+         * For every character in the password add the entire password.
+         */
+        for (int i = 1; i <= 16 + (altResult[0] & 0xff); i++) {
+            altCtx.update(saltBytes);
+        }
+
+        // 19. finish digest DS
+        /*
+         * Finish the digest.
+         */
+        tempResult = altCtx.digest();
+
+        // 20. produce byte sequence S of the same length as the salt string where
+        //
+        // a) for each block of 32 or 64 bytes of length of the salt string
+        // the entire digest DS is used
+        //
+        // b) for the remaining N (up to 31 or 63) bytes use the first N
+        // bytes of digest DS
+        /*
+         * Create byte sequence S.
+         */
+        // Remark: The salt is limited to 16 chars, how does this make sense?
+        byte[] sBytes = new byte[saltLen];
+        cp = 0;
+        while (cp < saltLen - blocksize) {
+            System.arraycopy(tempResult, 0, sBytes, cp, blocksize);
+            cp += blocksize;
+        }
+        System.arraycopy(tempResult, 0, sBytes, cp, saltLen - cp);
+
+        // 21. repeat a loop according to the number specified in the rounds=<N>
+        // specification in the salt (or the default value if none is
+        // present). Each round is numbered, starting with 0 and up to N-1.
+        //
+        // The loop uses a digest as input. In the first round it is the
+        // digest produced in step 12. In the latter steps it is the digest
+        // produced in step 21.h. The following text uses the notation
+        // "digest A/C" to desribe this behavior.
+        /*
+         * Repeatedly run the collected hash value through sha512 to burn CPU cycles.
+         */
+        for (int i = 0; i <= rounds - 1; i++) {
+            // a) start digest C
+            /*
+             * New context.
+             */
+            ctx = MessageDigest.getInstance(algorithm);
+
+            // b) for odd round numbers add the byte sequense P to digest C
+            // c) for even round numbers add digest A/C
+            /*
+             * Add key or last result.
+             */
+            if ((i & 1) != 0) {
+                ctx.update(pBytes, 0, keyLen);
+            } else {
+                ctx.update(altResult, 0, blocksize);
+            }
+
+            // d) for all round numbers not divisible by 3 add the byte sequence S
+            /*
+             * Add salt for numbers not divisible by 3.
+             */
+            if (i % 3 != 0) {
+                ctx.update(sBytes, 0, saltLen);
+            }
+
+            // e) for all round numbers not divisible by 7 add the byte sequence P
+            /*
+             * Add key for numbers not divisible by 7.
+             */
+            if (i % 7 != 0) {
+                ctx.update(pBytes, 0, keyLen);
+            }
+
+            // f) for odd round numbers add digest A/C
+            // g) for even round numbers add the byte sequence P
+            /*
+             * Add key or last result.
+             */
+            if ((i & 1) != 0) {
+                ctx.update(altResult, 0, blocksize);
+            } else {
+                ctx.update(pBytes, 0, keyLen);
+            }
+
+            // h) finish digest C.
+            /*
+             * Create intermediate result.
+             */
+            altResult = ctx.digest();
+        }
+
+        // 22. Produce the output string. This is an ASCII string of the maximum
+        // size specified above, consisting of multiple pieces:
+        //
+        // a) the salt salt_prefix, $5$ or $6$ respectively
+        //
+        // b) the rounds=<N> specification, if one was present in the input
+        // salt string. A trailing '$' is added in this case to separate
+        // the rounds specification from the following text.
+        //
+        // c) the salt string truncated to 16 characters
+        //
+        // d) a '$' character
+        /*
+         * Now we can construct the result string. It consists of three parts.
+         */
+        StringBuilder buffer = new StringBuilder(saltPrefix + (roundsCustom ? ROUNDS_PREFIX + rounds + "$" : "")
+                + saltString + "$");
+
+        // e) the base-64 encoded final C digest. The encoding used is as
+        // follows:
+        // [...]
+        //
+        // Each group of three bytes from the digest produces four
+        // characters as output:
+        //
+        // 1. character: the six low bits of the first byte
+        // 2. character: the two high bits of the first byte and the
+        // four low bytes from the second byte
+        // 3. character: the four high bytes from the second byte and
+        // the two low bits from the third byte
+        // 4. character: the six high bits from the third byte
+        //
+        // The groups of three bytes are as follows (in this sequence).
+        // These are the indices into the byte array containing the
+        // digest, starting with index 0. For the last group there are
+        // not enough bytes left in the digest and the value zero is used
+        // in its place. This group also produces only three or two
+        // characters as output for SHA-512 and SHA-512 respectively.
+
+        // This was just a safeguard in the C implementation:
+        // int buflen = salt_prefix.length() - 1 + ROUNDS_PREFIX.length() + 9 + 1 + salt_string.length() + 1 + 86 + 1;
+
+        if (blocksize == 32) {
+            B64.b64from24bit(altResult[0], altResult[10], altResult[20], 4, buffer);
+            B64.b64from24bit(altResult[21], altResult[1], altResult[11], 4, buffer);
+            B64.b64from24bit(altResult[12], altResult[22], altResult[2], 4, buffer);
+            B64.b64from24bit(altResult[3], altResult[13], altResult[23], 4, buffer);
+            B64.b64from24bit(altResult[24], altResult[4], altResult[14], 4, buffer);
+            B64.b64from24bit(altResult[15], altResult[25], altResult[5], 4, buffer);
+            B64.b64from24bit(altResult[6], altResult[16], altResult[26], 4, buffer);
+            B64.b64from24bit(altResult[27], altResult[7], altResult[17], 4, buffer);
+            B64.b64from24bit(altResult[18], altResult[28], altResult[8], 4, buffer);
+            B64.b64from24bit(altResult[9], altResult[19], altResult[29], 4, buffer);
+            B64.b64from24bit((byte) 0, altResult[31], altResult[30], 3, buffer);
+        } else {
+            B64.b64from24bit(altResult[0], altResult[21], altResult[42], 4, buffer);
+            B64.b64from24bit(altResult[22], altResult[43], altResult[1], 4, buffer);
+            B64.b64from24bit(altResult[44], altResult[2], altResult[23], 4, buffer);
+            B64.b64from24bit(altResult[3], altResult[24], altResult[45], 4, buffer);
+            B64.b64from24bit(altResult[25], altResult[46], altResult[4], 4, buffer);
+            B64.b64from24bit(altResult[47], altResult[5], altResult[26], 4, buffer);
+            B64.b64from24bit(altResult[6], altResult[27], altResult[48], 4, buffer);
+            B64.b64from24bit(altResult[28], altResult[49], altResult[7], 4, buffer);
+            B64.b64from24bit(altResult[50], altResult[8], altResult[29], 4, buffer);
+            B64.b64from24bit(altResult[9], altResult[30], altResult[51], 4, buffer);
+            B64.b64from24bit(altResult[31], altResult[52], altResult[10], 4, buffer);
+            B64.b64from24bit(altResult[53], altResult[11], altResult[32], 4, buffer);
+            B64.b64from24bit(altResult[12], altResult[33], altResult[54], 4, buffer);
+            B64.b64from24bit(altResult[34], altResult[55], altResult[13], 4, buffer);
+            B64.b64from24bit(altResult[56], altResult[14], altResult[35], 4, buffer);
+            B64.b64from24bit(altResult[15], altResult[36], altResult[57], 4, buffer);
+            B64.b64from24bit(altResult[37], altResult[58], altResult[16], 4, buffer);
+            B64.b64from24bit(altResult[59], altResult[17], altResult[38], 4, buffer);
+            B64.b64from24bit(altResult[18], altResult[39], altResult[60], 4, buffer);
+            B64.b64from24bit(altResult[40], altResult[61], altResult[19], 4, buffer);
+            B64.b64from24bit(altResult[62], altResult[20], altResult[41], 4, buffer);
+            B64.b64from24bit((byte) 0, (byte) 0, altResult[63], 2, buffer);
+        }
+
+        /*
+         * Clear the buffer for the intermediate result so that people attaching to processes or reading core dumps
+         * cannot get any information.
+         */
+        // Is there a better way to do this with the JVM?
+        Arrays.fill(tempResult, (byte) 0);
+        Arrays.fill(pBytes, (byte) 0);
+        Arrays.fill(sBytes, (byte) 0);
+        ctx.reset();
+        altCtx.reset();
+        Arrays.fill(keyBytes, (byte) 0);
+        Arrays.fill(saltBytes, (byte) 0);
+
+        return buffer.toString();
+    }
+
+    /**
+     * Generates a libc crypt() compatible "$6$" hash value with random salt.
+     * 
+     * See {@link Crypt#crypt(String, String)} for details.
+     */
+    public static String sha512Crypt(byte[] keyBytes) throws Exception {
+        return sha512Crypt(keyBytes, null);
+    }
+
+    /**
+     * Generates a libc6 crypt() compatible "$6$" hash value.
+     * 
+     * See {@link Crypt#crypt(String, String)} for details.
+     */
+    public static String sha512Crypt(byte[] keyBytes, String salt) throws Exception {
+        if (salt == null) {
+            salt = SHA512_PREFIX + B64.getRandomSalt(8);
+        }
+        return sha2Crypt(keyBytes, salt, SHA512_PREFIX, SHA512_BLOCKSIZE, SHA512_ALGORITHM);
+    }
+}

Added: commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java (added)
+++ commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/UnixCrypt.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,446 @@
+/*
+ * 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.commons.codec.digest;
+
+import java.util.Random;
+
+import org.apache.commons.codec.Charsets;
+
+/**
+ * Unix crypt(3) algorithm implementation.
+ *
+ * This class only implements the traditional 56 bit DES based algorithm. Please
+ * use DigestUtils.crypt() for a method that distinguishes between all the
+ * algorithms supported in the current glibc's crypt().
+ *
+ * The Java implementation was taken from the JetSpeed Portal project (see
+ * org.apache.jetspeed.services.security.ldap.UnixCrypt).
+ *
+ * This class is slightly incompatible if the given salt contains characters
+ * that are not part of the allowed range [a-zA-Z0-9./].
+ *
+ * @version $Id $
+ * @since 1.7
+ */
+public class UnixCrypt {
+
+    private static final int CON_SALT[] = {
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 1, 2, 3,
+        4, 5, 6, 7, 8, 9, 10, 11, 5, 6,
+        7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+        17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
+        27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+        37, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+        41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+        51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
+        61, 62, 63, 0, 0, 0, 0, 0
+    };
+    
+    private static final int COV2CHAR[] = {
+        46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
+        56, 57, 65, 66, 67, 68, 69, 70, 71, 72,
+        73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
+        83, 84, 85, 86, 87, 88, 89, 90, 97, 98,
+        99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
+        109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
+        119, 120, 121, 122
+    };
+    
+    private static final char SALT_CHARS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./".toCharArray();
+    
+    private static final boolean SHIFT2[] = {
+        false, false, true, true, true, true, true, true, false, true,
+        true, true, true, true, true, false
+    };
+    
+    private static final int SKB[][] = {
+        {
+            0, 16, 0x20000000, 0x20000010, 0x10000, 0x10010, 0x20010000, 0x20010010, 2048, 2064,
+            0x20000800, 0x20000810, 0x10800, 0x10810, 0x20010800, 0x20010810, 32, 48, 0x20000020, 0x20000030,
+            0x10020, 0x10030, 0x20010020, 0x20010030, 2080, 2096, 0x20000820, 0x20000830, 0x10820, 0x10830,
+            0x20010820, 0x20010830, 0x80000, 0x80010, 0x20080000, 0x20080010, 0x90000, 0x90010, 0x20090000, 0x20090010,
+            0x80800, 0x80810, 0x20080800, 0x20080810, 0x90800, 0x90810, 0x20090800, 0x20090810, 0x80020, 0x80030,
+            0x20080020, 0x20080030, 0x90020, 0x90030, 0x20090020, 0x20090030, 0x80820, 0x80830, 0x20080820, 0x20080830,
+            0x90820, 0x90830, 0x20090820, 0x20090830
+        }, {
+            0, 0x2000000, 8192, 0x2002000, 0x200000, 0x2200000, 0x202000, 0x2202000, 4, 0x2000004,
+            8196, 0x2002004, 0x200004, 0x2200004, 0x202004, 0x2202004, 1024, 0x2000400, 9216, 0x2002400,
+            0x200400, 0x2200400, 0x202400, 0x2202400, 1028, 0x2000404, 9220, 0x2002404, 0x200404, 0x2200404,
+            0x202404, 0x2202404, 0x10000000, 0x12000000, 0x10002000, 0x12002000, 0x10200000, 0x12200000, 0x10202000, 0x12202000,
+            0x10000004, 0x12000004, 0x10002004, 0x12002004, 0x10200004, 0x12200004, 0x10202004, 0x12202004, 0x10000400, 0x12000400,
+            0x10002400, 0x12002400, 0x10200400, 0x12200400, 0x10202400, 0x12202400, 0x10000404, 0x12000404, 0x10002404, 0x12002404,
+            0x10200404, 0x12200404, 0x10202404, 0x12202404
+        }, {
+            0, 1, 0x40000, 0x40001, 0x1000000, 0x1000001, 0x1040000, 0x1040001, 2, 3,
+            0x40002, 0x40003, 0x1000002, 0x1000003, 0x1040002, 0x1040003, 512, 513, 0x40200, 0x40201,
+            0x1000200, 0x1000201, 0x1040200, 0x1040201, 514, 515, 0x40202, 0x40203, 0x1000202, 0x1000203,
+            0x1040202, 0x1040203, 0x8000000, 0x8000001, 0x8040000, 0x8040001, 0x9000000, 0x9000001, 0x9040000, 0x9040001,
+            0x8000002, 0x8000003, 0x8040002, 0x8040003, 0x9000002, 0x9000003, 0x9040002, 0x9040003, 0x8000200, 0x8000201,
+            0x8040200, 0x8040201, 0x9000200, 0x9000201, 0x9040200, 0x9040201, 0x8000202, 0x8000203, 0x8040202, 0x8040203,
+            0x9000202, 0x9000203, 0x9040202, 0x9040203
+        }, {
+            0, 0x100000, 256, 0x100100, 8, 0x100008, 264, 0x100108, 4096, 0x101000,
+            4352, 0x101100, 4104, 0x101008, 4360, 0x101108, 0x4000000, 0x4100000, 0x4000100, 0x4100100,
+            0x4000008, 0x4100008, 0x4000108, 0x4100108, 0x4001000, 0x4101000, 0x4001100, 0x4101100, 0x4001008, 0x4101008,
+            0x4001108, 0x4101108, 0x20000, 0x120000, 0x20100, 0x120100, 0x20008, 0x120008, 0x20108, 0x120108,
+            0x21000, 0x121000, 0x21100, 0x121100, 0x21008, 0x121008, 0x21108, 0x121108, 0x4020000, 0x4120000,
+            0x4020100, 0x4120100, 0x4020008, 0x4120008, 0x4020108, 0x4120108, 0x4021000, 0x4121000, 0x4021100, 0x4121100,
+            0x4021008, 0x4121008, 0x4021108, 0x4121108
+        }, {
+            0, 0x10000000, 0x10000, 0x10010000, 4, 0x10000004, 0x10004, 0x10010004, 0x20000000, 0x30000000,
+            0x20010000, 0x30010000, 0x20000004, 0x30000004, 0x20010004, 0x30010004, 0x100000, 0x10100000, 0x110000, 0x10110000,
+            0x100004, 0x10100004, 0x110004, 0x10110004, 0x20100000, 0x30100000, 0x20110000, 0x30110000, 0x20100004, 0x30100004,
+            0x20110004, 0x30110004, 4096, 0x10001000, 0x11000, 0x10011000, 4100, 0x10001004, 0x11004, 0x10011004,
+            0x20001000, 0x30001000, 0x20011000, 0x30011000, 0x20001004, 0x30001004, 0x20011004, 0x30011004, 0x101000, 0x10101000,
+            0x111000, 0x10111000, 0x101004, 0x10101004, 0x111004, 0x10111004, 0x20101000, 0x30101000, 0x20111000, 0x30111000,
+            0x20101004, 0x30101004, 0x20111004, 0x30111004
+        }, {
+            0, 0x8000000, 8, 0x8000008, 1024, 0x8000400, 1032, 0x8000408, 0x20000, 0x8020000,
+            0x20008, 0x8020008, 0x20400, 0x8020400, 0x20408, 0x8020408, 1, 0x8000001, 9, 0x8000009,
+            1025, 0x8000401, 1033, 0x8000409, 0x20001, 0x8020001, 0x20009, 0x8020009, 0x20401, 0x8020401,
+            0x20409, 0x8020409, 0x2000000, 0xa000000, 0x2000008, 0xa000008, 0x2000400, 0xa000400, 0x2000408, 0xa000408,
+            0x2020000, 0xa020000, 0x2020008, 0xa020008, 0x2020400, 0xa020400, 0x2020408, 0xa020408, 0x2000001, 0xa000001,
+            0x2000009, 0xa000009, 0x2000401, 0xa000401, 0x2000409, 0xa000409, 0x2020001, 0xa020001, 0x2020009, 0xa020009,
+            0x2020401, 0xa020401, 0x2020409, 0xa020409
+        }, {
+            0, 256, 0x80000, 0x80100, 0x1000000, 0x1000100, 0x1080000, 0x1080100, 16, 272,
+            0x80010, 0x80110, 0x1000010, 0x1000110, 0x1080010, 0x1080110, 0x200000, 0x200100, 0x280000, 0x280100,
+            0x1200000, 0x1200100, 0x1280000, 0x1280100, 0x200010, 0x200110, 0x280010, 0x280110, 0x1200010, 0x1200110,
+            0x1280010, 0x1280110, 512, 768, 0x80200, 0x80300, 0x1000200, 0x1000300, 0x1080200, 0x1080300,
+            528, 784, 0x80210, 0x80310, 0x1000210, 0x1000310, 0x1080210, 0x1080310, 0x200200, 0x200300,
+            0x280200, 0x280300, 0x1200200, 0x1200300, 0x1280200, 0x1280300, 0x200210, 0x200310, 0x280210, 0x280310,
+            0x1200210, 0x1200310, 0x1280210, 0x1280310
+        }, {
+            0, 0x4000000, 0x40000, 0x4040000, 2, 0x4000002, 0x40002, 0x4040002, 8192, 0x4002000,
+            0x42000, 0x4042000, 8194, 0x4002002, 0x42002, 0x4042002, 32, 0x4000020, 0x40020, 0x4040020,
+            34, 0x4000022, 0x40022, 0x4040022, 8224, 0x4002020, 0x42020, 0x4042020, 8226, 0x4002022,
+            0x42022, 0x4042022, 2048, 0x4000800, 0x40800, 0x4040800, 2050, 0x4000802, 0x40802, 0x4040802,
+            10240, 0x4002800, 0x42800, 0x4042800, 10242, 0x4002802, 0x42802, 0x4042802, 2080, 0x4000820,
+            0x40820, 0x4040820, 2082, 0x4000822, 0x40822, 0x4040822, 10272, 0x4002820, 0x42820, 0x4042820,
+            10274, 0x4002822, 0x42822, 0x4042822
+        }
+    };
+    
+    private static final int SPTRANS[][] = {
+        {
+            0x820200, 0x20000, 0x80800000, 0x80820200, 0x800000, 0x80020200, 0x80020000, 0x80800000, 0x80020200, 0x820200,
+            0x820000, 0x80000200, 0x80800200, 0x800000, 0, 0x80020000, 0x20000, 0x80000000, 0x800200, 0x20200,
+            0x80820200, 0x820000, 0x80000200, 0x800200, 0x80000000, 512, 0x20200, 0x80820000, 512, 0x80800200,
+            0x80820000, 0, 0, 0x80820200, 0x800200, 0x80020000, 0x820200, 0x20000, 0x80000200, 0x800200,
+            0x80820000, 512, 0x20200, 0x80800000, 0x80020200, 0x80000000, 0x80800000, 0x820000, 0x80820200, 0x20200,
+            0x820000, 0x80800200, 0x800000, 0x80000200, 0x80020000, 0, 0x20000, 0x800000, 0x80800200, 0x820200,
+            0x80000000, 0x80820000, 512, 0x80020200
+        }, {
+            0x10042004, 0, 0x42000, 0x10040000, 0x10000004, 8196, 0x10002000, 0x42000, 8192, 0x10040004,
+            4, 0x10002000, 0x40004, 0x10042000, 0x10040000, 4, 0x40000, 0x10002004, 0x10040004, 8192,
+            0x42004, 0x10000000, 0, 0x40004, 0x10002004, 0x42004, 0x10042000, 0x10000004, 0x10000000, 0x40000,
+            8196, 0x10042004, 0x40004, 0x10042000, 0x10002000, 0x42004, 0x10042004, 0x40004, 0x10000004, 0,
+            0x10000000, 8196, 0x40000, 0x10040004, 8192, 0x10000000, 0x42004, 0x10002004, 0x10042000, 8192,
+            0, 0x10000004, 4, 0x10042004, 0x42000, 0x10040000, 0x10040004, 0x40000, 8196, 0x10002000,
+            0x10002004, 4, 0x10040000, 0x42000
+        }, {
+            0x41000000, 0x1010040, 64, 0x41000040, 0x40010000, 0x1000000, 0x41000040, 0x10040, 0x1000040, 0x10000,
+            0x1010000, 0x40000000, 0x41010040, 0x40000040, 0x40000000, 0x41010000, 0, 0x40010000, 0x1010040, 64,
+            0x40000040, 0x41010040, 0x10000, 0x41000000, 0x41010000, 0x1000040, 0x40010040, 0x1010000, 0x10040, 0,
+            0x1000000, 0x40010040, 0x1010040, 64, 0x40000000, 0x10000, 0x40000040, 0x40010000, 0x1010000, 0x41000040,
+            0, 0x1010040, 0x10040, 0x41010000, 0x40010000, 0x1000000, 0x41010040, 0x40000000, 0x40010040, 0x41000000,
+            0x1000000, 0x41010040, 0x10000, 0x1000040, 0x41000040, 0x10040, 0x1000040, 0, 0x41010000, 0x40000040,
+            0x41000000, 0x40010040, 64, 0x1010000
+        }, {
+            0x100402, 0x4000400, 2, 0x4100402, 0, 0x4100000, 0x4000402, 0x100002, 0x4100400, 0x4000002,
+            0x4000000, 1026, 0x4000002, 0x100402, 0x100000, 0x4000000, 0x4100002, 0x100400, 1024, 2,
+            0x100400, 0x4000402, 0x4100000, 1024, 1026, 0, 0x100002, 0x4100400, 0x4000400, 0x4100002,
+            0x4100402, 0x100000, 0x4100002, 1026, 0x100000, 0x4000002, 0x100400, 0x4000400, 2, 0x4100000,
+            0x4000402, 0, 1024, 0x100002, 0, 0x4100002, 0x4100400, 1024, 0x4000000, 0x4100402,
+            0x100402, 0x100000, 0x4100402, 2, 0x4000400, 0x100402, 0x100002, 0x100400, 0x4100000, 0x4000402,
+            1026, 0x4000000, 0x4000002, 0x4100400
+        }, {
+            0x2000000, 16384, 256, 0x2004108, 0x2004008, 0x2000100, 16648, 0x2004000, 16384, 8,
+            0x2000008, 16640, 0x2000108, 0x2004008, 0x2004100, 0, 16640, 0x2000000, 16392, 264,
+            0x2000100, 16648, 0, 0x2000008, 8, 0x2000108, 0x2004108, 16392, 0x2004000, 256,
+            264, 0x2004100, 0x2004100, 0x2000108, 16392, 0x2004000, 16384, 8, 0x2000008, 0x2000100,
+            0x2000000, 16640, 0x2004108, 0, 16648, 0x2000000, 256, 16392, 0x2000108, 256,
+            0, 0x2004108, 0x2004008, 0x2004100, 264, 16384, 16640, 0x2004008, 0x2000100, 264,
+            8, 16648, 0x2004000, 0x2000008
+        }, {
+            0x20000010, 0x80010, 0, 0x20080800, 0x80010, 2048, 0x20000810, 0x80000, 2064, 0x20080810,
+            0x80800, 0x20000000, 0x20000800, 0x20000010, 0x20080000, 0x80810, 0x80000, 0x20000810, 0x20080010, 0,
+            2048, 16, 0x20080800, 0x20080010, 0x20080810, 0x20080000, 0x20000000, 2064, 16, 0x80800,
+            0x80810, 0x20000800, 2064, 0x20000000, 0x20000800, 0x80810, 0x20080800, 0x80010, 0, 0x20000800,
+            0x20000000, 2048, 0x20080010, 0x80000, 0x80010, 0x20080810, 0x80800, 16, 0x20080810, 0x80800,
+            0x80000, 0x20000810, 0x20000010, 0x20080000, 0x80810, 0, 2048, 0x20000010, 0x20000810, 0x20080800,
+            0x20080000, 2064, 16, 0x20080010
+        }, {
+            4096, 128, 0x400080, 0x400001, 0x401081, 4097, 4224, 0, 0x400000, 0x400081,
+            129, 0x401000, 1, 0x401080, 0x401000, 129, 0x400081, 4096, 4097, 0x401081,
+            0, 0x400080, 0x400001, 4224, 0x401001, 4225, 0x401080, 1, 4225, 0x401001,
+            128, 0x400000, 4225, 0x401000, 0x401001, 129, 4096, 128, 0x400000, 0x401001,
+            0x400081, 4225, 4224, 0, 128, 0x400001, 1, 0x400080, 0, 0x400081,
+            0x400080, 4224, 129, 4096, 0x401081, 0x400000, 0x401080, 1, 4097, 0x401081,
+            0x400001, 0x401080, 0x401000, 4097
+        }, {
+            0x8200020, 0x8208000, 32800, 0, 0x8008000, 0x200020, 0x8200000, 0x8208020, 32, 0x8000000,
+            0x208000, 32800, 0x208020, 0x8008020, 0x8000020, 0x8200000, 32768, 0x208020, 0x200020, 0x8008000,
+            0x8208020, 0x8000020, 0, 0x208000, 0x8000000, 0x200000, 0x8008020, 0x8200020, 0x200000, 32768,
+            0x8208000, 32, 0x200000, 32768, 0x8000020, 0x8208020, 32800, 0x8000000, 0, 0x208000,
+            0x8200020, 0x8008020, 0x8008000, 0x200020, 0x8208000, 32, 0x200020, 0x8008000, 0x8208020, 0x200000,
+            0x8200000, 0x8000020, 0x208000, 32800, 0x8008020, 0x8200000, 32, 0x8208000, 0x208020, 0,
+            0x8000000, 0x8200020, 32768, 0x208020
+        }
+    };
+
+    private static int[] body(int schedule[], int eSwap0, int eSwap1) {
+        int left = 0;
+        int right = 0;
+        int t = 0;
+        for (int j = 0; j < 25; j++) {
+            for (int i = 0; i < 32; i += 4) {
+                left = dEncrypt(left, right, i, eSwap0, eSwap1, schedule);
+                right = dEncrypt(right, left, i + 2, eSwap0, eSwap1, schedule);
+            }
+            t = left;
+            left = right;
+            right = t;
+        }
+
+        t = right;
+        right = left >>> 1 | left << 31;
+        left = t >>> 1 | t << 31;
+        left &= 0xffffffff;
+        right &= 0xffffffff;
+        int results[] = new int[2];
+        permOp(right, left, 1, 0x55555555, results);
+        right = results[0];
+        left = results[1];
+        permOp(left, right, 8, 0xff00ff, results);
+        left = results[0];
+        right = results[1];
+        permOp(right, left, 2, 0x33333333, results);
+        right = results[0];
+        left = results[1];
+        permOp(left, right, 16, 65535, results);
+        left = results[0];
+        right = results[1];
+        permOp(right, left, 4, 0xf0f0f0f, results);
+        right = results[0];
+        left = results[1];
+        int out[] = new int[2];
+        out[0] = left;
+        out[1] = right;
+        return out;
+    }
+
+    private static int byteToUnsigned(byte b) {
+        int value = b;
+        return value < 0 ? value + 256 : value;
+    }
+
+    private static int dEncrypt(int el, int r, int s, int e0, int e1, int sArr[]) {
+        int v = r ^ r >>> 16;
+        int u = v & e0;
+        v &= e1;
+        u = u ^ u << 16 ^ r ^ sArr[s];
+        int t = v ^ v << 16 ^ r ^ sArr[s + 1];
+        t = t >>> 4 | t << 28;
+        el ^= SPTRANS[1][t & 0x3f] | SPTRANS[3][t >>> 8 & 0x3f] | SPTRANS[5][t >>> 16 & 0x3f]
+                | SPTRANS[7][t >>> 24 & 0x3f] | SPTRANS[0][u & 0x3f] | SPTRANS[2][u >>> 8 & 0x3f]
+                | SPTRANS[4][u >>> 16 & 0x3f] | SPTRANS[6][u >>> 24 & 0x3f];
+        return el;
+    }
+
+    private static int[] desSetKey(byte key[]) {
+        int schedule[] = new int[32];
+        int c = fourBytesToInt(key, 0);
+        int d = fourBytesToInt(key, 4);
+        int results[] = new int[2];
+        permOp(d, c, 4, 0xf0f0f0f, results);
+        d = results[0];
+        c = results[1];
+        c = hPermOp(c, -2, 0xcccc0000);
+        d = hPermOp(d, -2, 0xcccc0000);
+        permOp(d, c, 1, 0x55555555, results);
+        d = results[0];
+        c = results[1];
+        permOp(c, d, 8, 0xff00ff, results);
+        c = results[0];
+        d = results[1];
+        permOp(d, c, 1, 0x55555555, results);
+        d = results[0];
+        c = results[1];
+        d = (d & 0xff) << 16 | d & 0xff00 | (d & 0xff0000) >>> 16 | (c & 0xf0000000) >>> 4;
+        c &= 0xfffffff;
+        int j = 0;
+        for (int i = 0; i < 16; i++) {
+            if (SHIFT2[i]) {
+                c = c >>> 2 | c << 26;
+                d = d >>> 2 | d << 26;
+            } else {
+                c = c >>> 1 | c << 27;
+                d = d >>> 1 | d << 27;
+            }
+            c &= 0xfffffff;
+            d &= 0xfffffff;
+            int s = SKB[0][c & 0x3f] | SKB[1][c >>> 6 & 0x3 | c >>> 7 & 0x3c] | SKB[2][c >>> 13 & 0xf | c >>> 14 & 0x30] | SKB[3][c >>> 20 & 0x1 | c >>> 21 & 0x6 | c >>> 22 & 0x38];
+            int t = SKB[4][d & 0x3f] | SKB[5][d >>> 7 & 0x3 | d >>> 8 & 0x3c] | SKB[6][d >>> 15 & 0x3f] | SKB[7][d >>> 21 & 0xf | d >>> 22 & 0x30];
+            schedule[j++] = (t << 16 | s & 0xffff) & 0xffffffff;
+            s = s >>> 16 | t & 0xffff0000;
+            s = s << 4 | s >>> 28;
+            schedule[j++] = s & 0xffffffff;
+        }
+
+        return schedule;
+    }
+
+    private static int fourBytesToInt(byte b[], int offset) {
+        int value = byteToUnsigned(b[offset++]);
+        value |= byteToUnsigned(b[offset++]) << 8;
+        value |= byteToUnsigned(b[offset++]) << 16;
+        value |= byteToUnsigned(b[offset++]) << 24;
+        return value;
+    }
+
+    private static int hPermOp(int a, int n, int m) {
+        int t = (a << 16 - n ^ a) & m;
+        a = a ^ t ^ t >>> 16 - n;
+        return a;
+    }
+
+    private static void intToFourBytes(int iValue, byte b[], int offset) {
+        b[offset++] = (byte) (iValue & 0xff);
+        b[offset++] = (byte) (iValue >>> 8 & 0xff);
+        b[offset++] = (byte) (iValue >>> 16 & 0xff);
+        b[offset++] = (byte) (iValue >>> 24 & 0xff);
+    }
+
+    private static void permOp(int a, int b, int n, int m, int results[]) {
+        int t = (a >>> n ^ b) & m;
+        a ^= t << n;
+        b ^= t;
+        results[0] = a;
+        results[1] = b;
+    }
+
+    /**
+     * Generates a crypt(3) compatible hash using the DES algorithm.
+     *
+     * As no salt is given, a random one will be used.
+     *
+     * @param original Plaintext password
+     *
+     * @return A 13 character string starting with the salt string.
+     */
+    public static String crypt(byte[] original) {
+        return crypt(original, null);
+    }
+
+    /**
+     * Generates a crypt(3) compatible hash using the DES algorithm.
+     *
+     * Using unspecified characters as salt results incompatible hash values.
+     *
+     * @param original Plaintext password
+     *
+     * @param salt A two character string drawn from [a-zA-Z0-9./] or null for a
+     * random one.
+     *
+     * @return A 13 character string starting with the salt string.
+     */
+    public static String crypt(byte[] original, String salt) {
+        if (salt == null) {
+            Random randomGenerator = new Random();
+            int numSaltChars = SALT_CHARS.length;
+            salt = "" + SALT_CHARS[Math.abs(randomGenerator.nextInt()) % numSaltChars] + SALT_CHARS[Math.abs(randomGenerator.nextInt()) % numSaltChars];
+        } else if (!salt.matches("^[" + B64.B64T + "]{2,}$")) {
+            throw new IllegalArgumentException("Invalid salt value: " + salt);
+        }
+
+        for (; salt.length() < 2; salt = salt + "A");
+        StringBuilder buffer = new StringBuilder("             ");
+        char charZero = salt.charAt(0);
+        char charOne = salt.charAt(1);
+        buffer.setCharAt(0, charZero);
+        buffer.setCharAt(1, charOne);
+        int eSwap0 = CON_SALT[charZero];
+        int eSwap1 = CON_SALT[charOne] << 4;
+        byte key[] = new byte[8];
+        for (int i = 0; i < key.length; i++) {
+            key[i] = 0;
+        }
+
+        for (int i = 0; i < key.length && i < original.length; i++) {
+            int iChar = original[i];
+            key[i] = (byte) (iChar << 1);
+        }
+
+        int schedule[] = desSetKey(key);
+        int out[] = body(schedule, eSwap0, eSwap1);
+        byte b[] = new byte[9];
+        intToFourBytes(out[0], b, 0);
+        intToFourBytes(out[1], b, 4);
+        b[8] = 0;
+        int i = 2;
+        int y = 0;
+        int u = 128;
+        for (; i < 13; i++) {
+            int j = 0;
+            int c = 0;
+            for (; j < 6; j++) {
+                c <<= 1;
+                if ((b[y] & u) != 0) {
+                    c |= 0x1;
+                }
+                u >>>= 1;
+                if (u == 0) {
+                    y++;
+                    u = 128;
+                }
+                buffer.setCharAt(i, (char) COV2CHAR[c]);
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Generates a crypt(3) compatible hash using the DES algorithm.
+     *
+     * As no salt is given, a random one is used.
+     *
+     * @param original Plaintext password
+     *
+     * @return A 13 character string starting with the salt string.
+     */
+    public static String crypt(String original) throws Exception {
+        return crypt(original.getBytes(Charsets.UTF_8));
+    }
+
+    /**
+     * Generates a crypt(3) compatible hash using the DES algorithm.
+     *
+     * @param original Plaintext password
+     * @param salt A two character string drawn from [a-zA-Z0-9./] or null for a
+     * random one.
+     * @return A 13 character string starting with the salt string.
+     */
+    public static String crypt(String original, String salt) throws Exception {
+        return crypt(original.getBytes(Charsets.UTF_8), salt);
+    }
+
+}

Modified: commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/package.html
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/package.html?rev=1328414&r1=1328413&r2=1328414&view=diff
==============================================================================
--- commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/package.html (original)
+++ commons/proper/codec/trunk/src/main/java/org/apache/commons/codec/digest/package.html Fri Apr 20 15:36:58 2012
@@ -16,6 +16,9 @@ limitations under the License.
 -->
 <html>
  <body>
-   Simplifies common {@link java.security.MessageDigest} tasks.
+   Simplifies common {@link java.security.MessageDigest} tasks and
+   includes a libc crypt(3) compatible crypt method that supports DES,
+   MD5, SHA-256 and SHA-512 based algorithms as well as the Apache
+   specific "$apr1$" variant.
  </body>
 </html>

Modified: commons/proper/codec/trunk/src/site/xdoc/userguide.xml
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/site/xdoc/userguide.xml?rev=1328414&r1=1328413&r2=1328414&view=diff
==============================================================================
--- commons/proper/codec/trunk/src/site/xdoc/userguide.xml (original)
+++ commons/proper/codec/trunk/src/site/xdoc/userguide.xml Fri Apr 20 15:36:58 2012
@@ -97,7 +97,7 @@
               Simplifies common
               <a
                 href="http://download.oracle.com/javase/6/docs/api/java/security/MessageDigest.html">MessageDigest</a>
-              tasks.
+              tasks and provides GNU libc crypt(3) compatible password hashing functions.
             </td>
           </tr>
         </table>

Added: commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Apr1CryptTest.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Apr1CryptTest.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Apr1CryptTest.java (added)
+++ commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Apr1CryptTest.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,68 @@
+/*
+ * 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.commons.codec.digest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class Apr1CryptTest {
+
+    @Test
+    public void testApr1CryptStrings() throws Exception {
+        // A random example using htpasswd
+        assertEquals("$apr1$TqI9WECO$LHZB2DqRlk9nObiB6vJG9.", Md5Crypt.apr1Crypt("secret", "$apr1$TqI9WECO"));
+        // empty data
+        assertEquals("$apr1$foo$P27KyD1htb4EllIPEYhqi0", Md5Crypt.apr1Crypt("", "$apr1$foo"));
+        // salt gets cut at dollar sign
+        assertEquals("$apr1$1234$mAlH7FRST6FiRZ.kcYL.j1", Md5Crypt.apr1Crypt("secret", "$apr1$1234"));
+        assertEquals("$apr1$1234$mAlH7FRST6FiRZ.kcYL.j1", Md5Crypt.apr1Crypt("secret", "$apr1$1234$567"));
+        assertEquals("$apr1$1234$mAlH7FRST6FiRZ.kcYL.j1", Md5Crypt.apr1Crypt("secret", "$apr1$1234$567$890"));
+        // salt gets cut at maximum length
+        assertEquals("$apr1$12345678$0lqb/6VUFP8JY/s/jTrIk0", Md5Crypt.apr1Crypt("secret", "$apr1$1234567890123456"));
+        assertEquals("$apr1$12345678$0lqb/6VUFP8JY/s/jTrIk0", Md5Crypt.apr1Crypt("secret", "$apr1$123456789012345678"));
+    }
+
+    @Test
+    public void testApr1CryptBytes() throws Exception {
+        // An empty Bytearray equals an empty String
+        assertEquals("$apr1$foo$P27KyD1htb4EllIPEYhqi0", Md5Crypt.apr1Crypt(new byte[0], "$apr1$foo"));
+        // UTF-8 stores \u00e4 "a with diaeresis" as two bytes 0xc3 0xa4.
+        assertEquals("$apr1$./$EeFrYzWWbmTyGdf4xULYc.", Md5Crypt.apr1Crypt("t\u00e4st", "$apr1$./$"));
+        // ISO-8859-1 stores "a with diaeresis" as single byte 0xe4.
+        assertEquals("$apr1$./$kCwT1pY9qXAJElYG9q1QE1", Md5Crypt.apr1Crypt("t\u00e4st".getBytes("ISO-8859-1"), "$apr1$./$"));
+    }
+
+    @Test
+    public void testApr1CryptExplicitCall() throws Exception {
+        // When explicitly called the prefix is optional
+        assertEquals("$apr1$1234$mAlH7FRST6FiRZ.kcYL.j1", Md5Crypt.apr1Crypt("secret", "1234"));
+        // When explicitly called without salt, a random one will be used.
+        assertTrue(Md5Crypt.apr1Crypt("secret".getBytes()).matches("^\\$apr1\\$[a-zA-Z0-9./]{0,8}\\$.{1,}$"));
+        assertTrue(Md5Crypt.apr1Crypt("secret".getBytes(), null).matches("^\\$apr1\\$[a-zA-Z0-9./]{0,8}\\$.{1,}$"));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testApr1CryptNullData() throws Exception {
+        Md5Crypt.apr1Crypt((byte[]) null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testApr1CryptWithEmptySalt() throws Exception {
+        Md5Crypt.apr1Crypt("secret".getBytes(), "");
+    }
+}

Added: commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/B64Test.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/B64Test.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/B64Test.java (added)
+++ commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/B64Test.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,31 @@
+/*
+ * 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.commons.codec.digest;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class B64Test {
+
+    @Test
+    public void testB64from24bit() {
+        StringBuilder buffer = new StringBuilder("");
+        B64.b64from24bit((byte) 8, (byte) 16, (byte) 64, 2, buffer);
+        B64.b64from24bit((byte) 7, (byte) 77, (byte) 120, 4, buffer);
+        assertEquals("./spo/", buffer.toString());
+    }
+}

Added: commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/CryptTest.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/CryptTest.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/CryptTest.java (added)
+++ commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/CryptTest.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,43 @@
+/*
+ * 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.commons.codec.digest;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class CryptTest {
+
+    @Test
+    public void testDefaultCryptVariant() throws Exception {
+        // If salt is null or completely omitted, a random "$6$" is used.
+        assertTrue(Crypt.crypt("secret").startsWith("$6$"));
+        assertTrue(Crypt.crypt("secret", null).startsWith("$6$"));
+    }
+
+    /**
+     * An empty string as salt is invalid.
+     *
+     * The C and Perl implementations return an empty string, PHP threads it
+     * as NULL. Our implementation should throw an Exception as any resulting
+     * hash would not be verifyable with other implementations of crypt().
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testCryptWithEmptySalt() throws Exception {
+        Crypt.crypt("secret", "");
+    }
+
+}

Added: commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Md5CryptTest.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Md5CryptTest.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Md5CryptTest.java (added)
+++ commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Md5CryptTest.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,63 @@
+/*
+ * 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.commons.codec.digest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class Md5CryptTest {
+
+    @Test
+    public void testMd5CryptStrings() throws Exception {
+        // empty data
+        assertEquals("$1$foo$9mS5ExwgIECGE5YKlD5o91", Crypt.crypt("", "$1$foo"));
+        // salt gets cut at dollar sign
+        assertEquals("$1$1234$ImZYBLmYC.rbBKg9ERxX70", Crypt.crypt("secret", "$1$1234"));
+        assertEquals("$1$1234$ImZYBLmYC.rbBKg9ERxX70", Crypt.crypt("secret", "$1$1234$567"));
+        assertEquals("$1$1234$ImZYBLmYC.rbBKg9ERxX70", Crypt.crypt("secret", "$1$1234$567$890"));
+        // salt gets cut at maximum length
+        assertEquals("$1$12345678$hj0uLpdidjPhbMMZeno8X/", Crypt.crypt("secret", "$1$1234567890123456"));
+        assertEquals("$1$12345678$hj0uLpdidjPhbMMZeno8X/", Crypt.crypt("secret", "$1$123456789012345678"));
+    }
+
+    @Test
+    public void testMd5CryptBytes() throws Exception {
+        // An empty Bytearray equals an empty String
+        assertEquals("$1$foo$9mS5ExwgIECGE5YKlD5o91", Crypt.crypt(new byte[0], "$1$foo"));
+        // UTF-8 stores \u00e4 "a with diaeresis" as two bytes 0xc3 0xa4.
+        assertEquals("$1$./$52agTEQZs877L9jyJnCNZ1", Crypt.crypt("t\u00e4st", "$1$./$"));
+        // ISO-8859-1 stores "a with diaeresis" as single byte 0xe4.
+        assertEquals("$1$./$J2UbKzGe0Cpe63WZAt6p//", Crypt.crypt("t\u00e4st".getBytes("ISO-8859-1"), "$1$./$"));
+    }
+
+    @Test
+    public void testMd5CryptExplicitCall() throws Exception {
+        assertTrue(Md5Crypt.md5Crypt("secret".getBytes()).matches("^\\$1\\$[a-zA-Z0-9./]{0,8}\\$.{1,}$"));
+        assertTrue(Md5Crypt.md5Crypt("secret".getBytes(), null).matches("^\\$1\\$[a-zA-Z0-9./]{0,8}\\$.{1,}$"));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testMd5CryptNullData() throws Exception {
+        Md5Crypt.md5Crypt((byte[]) null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMd5CryptWithEmptySalt() throws Exception {
+        Md5Crypt.md5Crypt("secret".getBytes(), "");
+    }
+}

Added: commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha256CryptTest.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha256CryptTest.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha256CryptTest.java (added)
+++ commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha256CryptTest.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,63 @@
+/*
+ * 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.commons.codec.digest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class Sha256CryptTest {
+
+    @Test
+    public void testSha256CryptStrings() throws Exception {
+        // empty data
+        assertEquals("$5$foo$Fq9CX624QIfnCAmlGiPKLlAasdacKCRxZztPoeo7o0B", Crypt.crypt("", "$5$foo"));
+        // salt gets cut at dollar sign
+        assertEquals("$5$45678$LulJuUIJIn.1uU.KPV9x92umMYFopzVDD.o2ZqA1i2/", Crypt.crypt("secret", "$5$45678"));
+        assertEquals("$5$45678$LulJuUIJIn.1uU.KPV9x92umMYFopzVDD.o2ZqA1i2/", Crypt.crypt("secret", "$5$45678$012"));
+        assertEquals("$5$45678$LulJuUIJIn.1uU.KPV9x92umMYFopzVDD.o2ZqA1i2/", Crypt.crypt("secret", "$5$45678$012$456"));
+        // salt gets cut at maximum length
+        assertEquals("$5$1234567890123456$GUiFKBSTUAGvcK772ulTDPltkTOLtFvPOmp9o.9FNPB", Crypt.crypt("secret", "$5$1234567890123456"));
+        assertEquals("$5$1234567890123456$GUiFKBSTUAGvcK772ulTDPltkTOLtFvPOmp9o.9FNPB", Crypt.crypt("secret", "$5$1234567890123456789"));
+    }
+
+    @Test
+    public void testSha256CryptBytes() throws Exception {
+        // An empty Bytearray equals an empty String
+        assertEquals("$5$foo$Fq9CX624QIfnCAmlGiPKLlAasdacKCRxZztPoeo7o0B", Crypt.crypt(new byte[0], "$5$foo"));
+        // UTF-8 stores \u00e4 "a with diaeresis" as two bytes 0xc3 0xa4.
+        assertEquals("$5$./$iH66LwY5sTDTdHeOxq5nvNDVAxuoCcyH/y6Ptte82P8", Crypt.crypt("t\u00e4st", "$5$./$"));
+        // ISO-8859-1 stores "a with diaeresis" as single byte 0xe4.
+        assertEquals("$5$./$qx5gFfCzjuWUOvsDDy.5Nor3UULPIqLVBZhgGNS0c14", Crypt.crypt("t\u00e4st".getBytes("ISO-8859-1"), "$5$./$"));
+    }
+
+    @Test
+    public void testSha256CryptExplicitCall() throws Exception {
+        assertTrue(Sha2Crypt.sha256Crypt("secret".getBytes()).matches("^\\$5\\$[a-zA-Z0-9./]{0,16}\\$.{1,}$"));
+        assertTrue(Sha2Crypt.sha256Crypt("secret".getBytes(), null).matches("^\\$5\\$[a-zA-Z0-9./]{0,16}\\$.{1,}$"));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSha256CryptNullData() throws Exception {
+        Sha2Crypt.sha256Crypt((byte[]) null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSha256CryptWithEmptySalt() throws Exception {
+        Sha2Crypt.sha256Crypt("secret".getBytes(), "");
+    }
+}

Added: commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha512CryptTest.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha512CryptTest.java?rev=1328414&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha512CryptTest.java (added)
+++ commons/proper/codec/trunk/src/test/java/org/apache/commons/codec/digest/Sha512CryptTest.java Fri Apr 20 15:36:58 2012
@@ -0,0 +1,63 @@
+/*
+ * 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.commons.codec.digest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class Sha512CryptTest {
+
+    @Test
+    public void testSha512CryptStrings() throws Exception {
+        // empty data
+        assertEquals("$6$foo$Nywkte7LPWjaJhWjNeGJN.dFdY3pN1wYlGifyRLYOVlGS9EMSiZaDDe/BGSOYQ327q9.32I4UqQ5odsqvsBLX/", Crypt.crypt("", "$6$foo"));
+        // salt gets cut at dollar sign
+        assertEquals("$6$45678$f2en/Y053Knir/wu/T8DQKSbiUGcPcbXKsmyVlP820dIpXoY0KlqgUqRVFfavdRXwDMUZYsxPOymA4zgX0qE5.", Crypt.crypt("secret", "$6$45678"));
+        assertEquals("$6$45678$f2en/Y053Knir/wu/T8DQKSbiUGcPcbXKsmyVlP820dIpXoY0KlqgUqRVFfavdRXwDMUZYsxPOymA4zgX0qE5.", Crypt.crypt("secret", "$6$45678$012"));
+        assertEquals("$6$45678$f2en/Y053Knir/wu/T8DQKSbiUGcPcbXKsmyVlP820dIpXoY0KlqgUqRVFfavdRXwDMUZYsxPOymA4zgX0qE5.", Crypt.crypt("secret", "$6$45678$012$456"));
+        // salt gets cut at maximum length
+        assertEquals("$6$1234567890123456$d2HCAnimIF5VMqUnwaZ/4JhNDJ.ttsjm0nbbmc9eE7xUYiw79GMvXUc5ZqG5BlqkXSbASZxrvR0QefAgdLbeH.", Crypt.crypt("secret", "$6$1234567890123456"));
+        assertEquals("$6$1234567890123456$d2HCAnimIF5VMqUnwaZ/4JhNDJ.ttsjm0nbbmc9eE7xUYiw79GMvXUc5ZqG5BlqkXSbASZxrvR0QefAgdLbeH.", Crypt.crypt("secret", "$6$1234567890123456789"));
+    }
+
+    @Test
+    public void testSha512CryptBytes() throws Exception {
+        // An empty Bytearray equals an empty String
+        assertEquals("$6$foo$Nywkte7LPWjaJhWjNeGJN.dFdY3pN1wYlGifyRLYOVlGS9EMSiZaDDe/BGSOYQ327q9.32I4UqQ5odsqvsBLX/", Crypt.crypt(new byte[0], "$6$foo"));
+        // UTF-8 stores \u00e4 "a with diaeresis" as two bytes 0xc3 0xa4.
+        assertEquals("$6$./$fKtWqslQkwI8ZxjdWoeS.jHHrte97bZxiwB5gwCRHX6LG62fUhT6Bb5MRrjWvieh0C/gxh8ItFuTsVy80VrED1", Crypt.crypt("t\u00e4st", "$6$./$"));
+        // ISO-8859-1 stores "a with diaeresis" as single byte 0xe4.
+        assertEquals("$6$./$L49DSK.d2df/LxGLJQMyS5A/Um.TdHqgc46j5FpScEPlqQHP5dEazltaDNDZ6UEs2mmNI6kPwtH/rsP9g5zBI.", Crypt.crypt("t\u00e4st".getBytes("ISO-8859-1"), "$6$./$"));
+    }
+
+    @Test
+    public void testSha512CryptExplicitCall() throws Exception {
+        assertTrue(Sha2Crypt.sha512Crypt("secret".getBytes()).matches("^\\$6\\$[a-zA-Z0-9./]{0,16}\\$.{1,}$"));
+        assertTrue(Sha2Crypt.sha512Crypt("secret".getBytes(), null).matches("^\\$6\\$[a-zA-Z0-9./]{0,16}\\$.{1,}$"));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSha512CryptNullData() throws Exception {
+        Sha2Crypt.sha512Crypt((byte[]) null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSha512CryptWithEmptySalt() throws Exception {
+        Sha2Crypt.sha512Crypt("secret".getBytes(), "");
+    }
+}



Mime
View raw message