shiro-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lhazlew...@apache.org
Subject svn commit: r1209662 [2/3] - in /shiro/trunk: ./ core/src/main/java/org/apache/shiro/authc/credential/ core/src/main/java/org/apache/shiro/codec/ core/src/main/java/org/apache/shiro/config/ core/src/main/java/org/apache/shiro/crypto/ core/src/main/java...
Date Fri, 02 Dec 2011 19:52:40 GMT
Modified: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java Fri Dec  2 19:52:37 2011
@@ -22,6 +22,7 @@ import org.apache.shiro.codec.Base64;
 import org.apache.shiro.codec.CodecException;
 import org.apache.shiro.codec.Hex;
 import org.apache.shiro.crypto.UnknownAlgorithmException;
+import org.apache.shiro.util.ByteSource;
 import org.apache.shiro.util.StringUtils;
 
 import java.security.MessageDigest;
@@ -41,6 +42,8 @@ import java.util.Arrays;
  */
 public class SimpleHash extends AbstractHash {
 
+    private static final int DEFAULT_ITERATIONS = 1;
+
     /**
      * The {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
      */
@@ -49,7 +52,17 @@ public class SimpleHash extends Abstract
     /**
      * The hashed data
      */
-    private byte[] bytes = null;
+    private byte[] bytes;
+
+    /**
+     * Supplied salt, if any.
+     */
+    private ByteSource salt;
+
+    /**
+     * Number of hash iterations to perform.  Defaults to 1 in the constructor.
+     */
+    private int iterations;
 
     /**
      * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead.
@@ -78,6 +91,7 @@ public class SimpleHash extends Abstract
      */
     public SimpleHash(String algorithmName) {
         this.algorithmName = algorithmName;
+        this.iterations = DEFAULT_ITERATIONS;
     }
 
     /**
@@ -99,7 +113,8 @@ public class SimpleHash extends Abstract
      * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
      */
     public SimpleHash(String algorithmName, Object source) throws CodecException, UnknownAlgorithmException {
-        this(algorithmName, source, null, 1);
+        //noinspection NullableProblems
+        this(algorithmName, source, null, DEFAULT_ITERATIONS);
     }
 
     /**
@@ -121,7 +136,7 @@ public class SimpleHash extends Abstract
      * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
      */
     public SimpleHash(String algorithmName, Object source, Object salt) throws CodecException, UnknownAlgorithmException {
-        this(algorithmName, source, salt, 1);
+        this(algorithmName, source, salt, DEFAULT_ITERATIONS);
     }
 
     /**
@@ -153,16 +168,65 @@ public class SimpleHash extends Abstract
             throw new NullPointerException("algorithmName argument cannot be null or empty.");
         }
         this.algorithmName = algorithmName;
-        hash(source, salt, hashIterations);
+        this.iterations = Math.max(DEFAULT_ITERATIONS, hashIterations);
+        ByteSource saltBytes = null;
+        if (salt != null) {
+            saltBytes = convertSaltToBytes(salt);
+            this.salt = saltBytes;
+        }
+        ByteSource sourceBytes = convertSourceToBytes(source);
+        hash(sourceBytes, saltBytes, hashIterations);
     }
 
-    private void hash(Object source, Object salt, int hashIterations) throws CodecException, UnknownAlgorithmException {
-        byte[] sourceBytes = toBytes(source);
-        byte[] saltBytes = null;
-        if (salt != null) {
-            saltBytes = toBytes(salt);
+    /**
+     * Acquires the specified {@code source} argument's bytes and returns them in the form of a {@code ByteSource} instance.
+     * <p/>
+     * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic
+     * conversion.  Can be overridden by subclasses for source-specific conversion.
+     *
+     * @param source the source object to be hashed.
+     * @return the source's bytes in the form of a {@code ByteSource} instance.
+     * @since 1.2
+     */
+    protected ByteSource convertSourceToBytes(Object source) {
+        return toByteSource(source);
+    }
+
+    /**
+     * Acquires the specified {@code salt} argument's bytes and returns them in the form of a {@code ByteSource} instance.
+     * <p/>
+     * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic
+     * conversion.  Can be overridden by subclasses for salt-specific conversion.
+     *
+     * @param salt the salt to be use for the hash.
+     * @return the salt's bytes in the form of a {@code ByteSource} instance.
+     * @since 1.2
+     */
+    protected ByteSource convertSaltToBytes(Object salt) {
+        return toByteSource(salt);
+    }
+
+    /**
+     * Converts a given object into a {@code ByteSource} instance.  Assumes the object can be converted to bytes.
+     *
+     * @param o the Object to convert into a {@code ByteSource} instance.
+     * @return the {@code ByteSource} representation of the specified object's bytes.
+     * @since 1.2
+     */
+    protected ByteSource toByteSource(Object o) {
+        if (o == null) {
+            return null;
         }
-        byte[] hashedBytes = hash(sourceBytes, saltBytes, hashIterations);
+        if (o instanceof ByteSource) {
+            return (ByteSource) o;
+        }
+        byte[] bytes = toBytes(o);
+        return ByteSource.Util.bytes(bytes);
+    }
+
+    private void hash(ByteSource source, ByteSource salt, int hashIterations) throws CodecException, UnknownAlgorithmException {
+        byte[] saltBytes = salt != null ? salt.getBytes() : null;
+        byte[] hashedBytes = hash(source.getBytes(), saltBytes, hashIterations);
         setBytes(hashedBytes);
     }
 
@@ -175,6 +239,14 @@ public class SimpleHash extends Abstract
         return this.algorithmName;
     }
 
+    public ByteSource getSalt() {
+        return this.salt;
+    }
+
+    public int getIterations() {
+        return this.iterations;
+    }
+
     public byte[] getBytes() {
         return this.bytes;
     }
@@ -194,6 +266,32 @@ public class SimpleHash extends Abstract
     }
 
     /**
+     * Sets the iterations used to previously compute AN ALREADY GENERATED HASH.
+     * <p/>
+     * This is provided <em>ONLY</em> to reconstitute an already-created Hash instance.  It should ONLY ever be
+     * invoked when re-constructing a hash instance from an already-hashed value.
+     *
+     * @param iterations the number of hash iterations used to previously create the hash/digest.
+     * @since 1.2
+     */
+    public void setIterations(int iterations) {
+        this.iterations = Math.max(DEFAULT_ITERATIONS, iterations);
+    }
+
+    /**
+     * Sets the salt used to previously compute AN ALREADY GENERATED HASH.
+     * <p/>
+     * This is provided <em>ONLY</em> to reconstitute a Hash instance that has already been computed.  It should ONLY
+     * ever be invoked when re-constructing a hash instance from an already-hashed value.
+     *
+     * @param salt the salt used to previously create the hash/digest.
+     * @since 1.2
+     */
+    public void setSalt(ByteSource salt) {
+        this.salt = salt;
+    }
+
+    /**
      * Returns the JDK MessageDigest instance to use for executing the hash.
      *
      * @param algorithmName the algorithm to use for the hash, provided by subclasses.
@@ -217,7 +315,7 @@ public class SimpleHash extends Abstract
      * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
      */
     protected byte[] hash(byte[] bytes) throws UnknownAlgorithmException {
-        return hash(bytes, null, 1);
+        return hash(bytes, null, DEFAULT_ITERATIONS);
     }
 
     /**
@@ -229,7 +327,7 @@ public class SimpleHash extends Abstract
      * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
      */
     protected byte[] hash(byte[] bytes, byte[] salt) throws UnknownAlgorithmException {
-        return hash(bytes, salt, 1);
+        return hash(bytes, salt, DEFAULT_ITERATIONS);
     }
 
     /**
@@ -248,7 +346,7 @@ public class SimpleHash extends Abstract
             digest.update(salt);
         }
         byte[] hashed = digest.digest(bytes);
-        int iterations = hashIterations - 1; //already hashed once above
+        int iterations = hashIterations - DEFAULT_ITERATIONS; //already hashed once above
         //iterate remaining number:
         for (int i = 0; i < iterations; i++) {
             digest.reset();
@@ -257,6 +355,10 @@ public class SimpleHash extends Abstract
         return hashed;
     }
 
+    public boolean isEmpty() {
+        return this.bytes == null || this.bytes.length == 0;
+    }
+
     /**
      * Returns a hex-encoded string of the underlying {@link #getBytes byte array}.
      * <p/>
@@ -321,71 +423,9 @@ public class SimpleHash extends Abstract
      * @return toHex().hashCode()
      */
     public int hashCode() {
-        return toHex().hashCode();
-    }
-
-    private static void printMainUsage(Class<? extends AbstractHash> clazz, String type) {
-        System.out.println("Prints an " + type + " hash value.");
-        System.out.println("Usage: java " + clazz.getName() + " [-base64] [-salt <saltValue>] [-times <N>] <valueToHash>");
-        System.out.println("Options:");
-        System.out.println("\t-base64\t\tPrints the hash value as a base64 String instead of the default hex.");
-        System.out.println("\t-salt\t\tSalts the hash with the specified <saltValue>");
-        System.out.println("\t-times\t\tHashes the input <N> number of times");
-    }
-
-    private static boolean isReserved(String arg) {
-        return "-base64".equals(arg) || "-times".equals(arg) || "-salt".equals(arg);
-    }
-
-    static int doMain(Class<? extends AbstractHash> clazz, String[] args) {
-        String simple = clazz.getSimpleName();
-        int index = simple.indexOf("Hash");
-        String type = simple.substring(0, index).toUpperCase();
-
-        if (args == null || args.length < 1 || args.length > 7) {
-            printMainUsage(clazz, type);
-            return -1;
-        }
-        boolean hex = true;
-        String salt = null;
-        int times = 1;
-        String text = args[args.length - 1];
-        for (int i = 0; i < args.length; i++) {
-            String arg = args[i];
-            if (arg.equals("-base64")) {
-                hex = false;
-            } else if (arg.equals("-salt")) {
-                if ((i + 1) >= (args.length - 1)) {
-                    String msg = "Salt argument must be followed by a salt value.  The final argument is " +
-                            "reserved for the value to hash.";
-                    System.out.println(msg);
-                    printMainUsage(clazz, type);
-                    return -1;
-                }
-                salt = args[i + 1];
-            } else if (arg.equals("-times")) {
-                if ((i + 1) >= (args.length - 1)) {
-                    String msg = "Times argument must be followed by an integer value.  The final argument is " +
-                            "reserved for the value to hash";
-                    System.out.println(msg);
-                    printMainUsage(clazz, type);
-                    return -1;
-                }
-                try {
-                    times = Integer.valueOf(args[i + 1]);
-                } catch (NumberFormatException e) {
-                    String msg = "Times argument must be followed by an integer value.";
-                    System.out.println(msg);
-                    printMainUsage(clazz, type);
-                    return -1;
-                }
-            }
-        }
-
-        Hash hash = new Md2Hash(text, salt, times);
-        String hashed = hex ? hash.toHex() : hash.toBase64();
-        System.out.print(hex ? "Hex: " : "Base64: ");
-        System.out.println(hashed);
-        return 0;
+        if (this.bytes == null || this.bytes.length == 0) {
+            return 0;
+        }
+        return Arrays.hashCode(this.bytes);
     }
 }

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java Fri Dec  2 19:52:37 2011
@@ -21,39 +21,39 @@ package org.apache.shiro.crypto.hash;
 import org.apache.shiro.util.ByteSource;
 
 /**
- * Simple implementation of {@link HashRequest} that retains the {@link #getSource source} and
- * {@link #getSalt salt} properties as private attributes.
+ * Simple implementation of {@link HashRequest} that can be used when interacting with a {@link HashService}.
  *
  * @since 1.2
  */
 public class SimpleHashRequest implements HashRequest {
 
-    private final ByteSource source;
-    private final ByteSource salt;
+    private final ByteSource source; //cannot be null - this is the source to hash.
+    private final ByteSource salt; //null = no salt specified
+    private final int iterations; //0 = not specified by the requestor; let the HashService decide.
+    private final String algorithmName; //null = let the HashService decide.
 
     /**
-     * Creates a new {@code SimpleHashRequest} with the specified source to be hashed.
+     * Creates a new SimpleHashRequest instance.
      *
-     * @param source the source data to be hashed
-     * @throws NullPointerException if the specified {@code source} argument is {@code null}.
-     */
-    public SimpleHashRequest(ByteSource source) throws NullPointerException {
-        this(source, null);
-    }
-
-    /**
-     * Creates a new {@code SimpleHashRequest} with the specified source and salt.
+     * @param algorithmName the name of the hash algorithm to use.  This is often null as the
+     * {@link HashService} implementation is usually configured with an appropriate algorithm name, but this
+     * can be non-null if the hash service's algorithm should be overridden with a specific one for the duration
+     * of the request.
      *
-     * @param source the source data to be hashed
-     * @param salt   a salt a salt to be used by the {@link Hasher} during hash computation.
-     * @throws NullPointerException if the specified {@code source} argument is {@code null}.
+     * @param source the source to be hashed
+     * @param salt any public salt which should be used when computing the hash
+     * @param iterations the number of hash iterations to execute.  Zero (0) indicates no iterations were specified
+     * for the request, at which point the number of iterations is decided by the {@code HashService}
+     * @throws NullPointerException if {@code source} is null or empty.
      */
-    public SimpleHashRequest(ByteSource source, ByteSource salt) throws NullPointerException {
-        this.source = source;
-        this.salt = salt;
+    public SimpleHashRequest(String algorithmName, ByteSource source, ByteSource salt, int iterations) {
         if (source == null) {
-            throw new NullPointerException("source argument cannot be null.");
+            throw new NullPointerException("source argument cannot be null");
         }
+        this.source = source;
+        this.salt = salt;
+        this.algorithmName = algorithmName;
+        this.iterations = Math.max(0, iterations);
     }
 
     public ByteSource getSource() {
@@ -63,4 +63,12 @@ public class SimpleHashRequest implement
     public ByteSource getSalt() {
         return this.salt;
     }
+
+    public int getIterations() {
+        return iterations;
+    }
+
+    public String getAlgorithmName() {
+        return algorithmName;
+    }
 }

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,41 @@
+/*
+ * 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.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * {@code HashFormat} that outputs <em>only</em> the hash's digest bytes in Base64 format.  It does not print out
+ * anything else (salt, iterations, etc).  This implementation is mostly provided as a convenience for
+ * command-line hashing.
+ *
+ * @since 1.2
+ */
+public class Base64Format implements HashFormat {
+
+    /**
+     * Returns {@code hash != null ? hash.toBase64() : null}.
+     *
+     * @param hash the hash instance to format into a String.
+     * @return {@code hash != null ? hash.toBase64() : null}.
+     */
+    public String format(Hash hash) {
+        return hash != null ? hash.toBase64() : null;
+    }
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,307 @@
+/*
+ * 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.shiro.crypto.hash.format;
+
+import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.util.UnknownClassException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This default {@code HashFormatFactory} implementation heuristically determines a {@code HashFormat} class to
+ * instantiate based on the input argument and returns a new instance of the discovered class.  The heuristics are
+ * detailed in the {@link #getInstance(String) getInstance} method documentation.j
+ *
+ * @since 1.2
+ */
+public class DefaultHashFormatFactory implements HashFormatFactory {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultHashFormatFactory.class);
+
+    private static final String DEFAULT_HASH_FORMAT_PACKAGE_NAME = HashFormat.class.getPackage().getName();
+
+    private Map<String, String> formatClassNames; //id - to - fully qualified class name
+
+    private Set<String> searchPackages;
+
+    public DefaultHashFormatFactory() {
+        this.searchPackages = new HashSet<String>();
+        formatClassNames = new HashMap<String, String>();
+        formatClassNames.put(Shiro1CryptFormat.ID, Shiro1CryptFormat.class.getName());
+    }
+
+    public Map<String, String> getFormatClassNames() {
+        return formatClassNames;
+    }
+
+    public void setFormatClassNames(Map<String, String> formatClassNames) {
+        this.formatClassNames = formatClassNames;
+    }
+
+    public Set<String> getSearchPackages() {
+        return searchPackages;
+    }
+
+    public void setSearchPackages(Set<String> searchPackages) {
+        this.searchPackages = searchPackages;
+    }
+
+    public HashFormat getInstance(String in) {
+        if (in == null) {
+            return null;
+        }
+
+        HashFormat hashFormat = null;
+
+        Class clazz = getHashFormatClass(in);
+
+        //The 'in' argument didn't result in a corresponding HashFormat class using our heuristics.
+        //As a fallback, check to see if the argument is an MCF-formatted string.  If it is, odds are very high
+        //that the MCF ID id is the lookup token we can use to find a corresponding HashFormat class:
+        if (clazz == null && in.startsWith(ModularCryptFormat.TOKEN_DELIMITER)) {
+            String test = in.substring(ModularCryptFormat.TOKEN_DELIMITER.length());
+            String[] tokens = test.split("\\" + ModularCryptFormat.TOKEN_DELIMITER);
+            //the MCF ID is always the first token in the delimited string:
+            String possibleMcfId = (tokens != null && tokens.length > 0) ? tokens[0] : null;
+            if (possibleMcfId != null) {
+                //found a possible MCF ID - test it using our heuristics to see if we can find a corresponding class:
+                clazz = getHashFormatClass(possibleMcfId);
+            }
+        }
+
+        if (clazz != null) {
+            //we found a HashFormat class - instantiate it:
+            hashFormat = newHashFormatInstance(clazz);
+
+            //do further compatibility testing if we can:
+            if (hashFormat instanceof ParsableHashFormat) {
+                //This is not really an efficient way to test for format compatibility, but
+                //there is no other way that guarantees compatibility that I can think of at the moment.
+                //perhaps an isCompatible method can be introduced?  The struggle I have with this is how do you
+                //determine compatibility without parsing it fully?  If not fully parsed, then it truly can't be
+                //guaranteed compatible - at which point, you might as well just parse the thing - L.H. 22 Nov 2011
+                try {
+                    ParsableHashFormat phf = (ParsableHashFormat)hashFormat;
+                    phf.parse(in);
+                    // no exception - must be a match:
+                    return phf;
+                } catch (RuntimeException re) {
+                    log.debug("Candidate format instance of type [{}] is unable to " +
+                            "parse formatted String [{}].  Ignoring.", clazz, in);
+                    log.trace("HashFormat parsing caused exception: ", re);
+                }
+            }
+        }
+
+        return hashFormat;
+    }
+
+    /**
+     * Heuristically determine the fully qualified HashFormat implementation class name based on the specified
+     * token.
+     * <p/>
+     * This implementation functions as follows:
+     * <p/>
+     * All configured {@link #getSearchPackages() searchPackages} will be searched using heuristics defined in the
+     * {@link #getHashFormatClass(String, String) getHashFormatClass(packageName, token)} method documentation (relaying
+     * the {@code token} argument to that method for each configured package).
+     * <p/>
+     * If the class was not found in any configured {@code searchPackages}, the default
+     * {@code org.apache.shiro.crypto.hash.format} package will be attempted as a final fallback.
+     * </p>
+     * If the class was not discovered in any of the {@code searchPackages} or in Shiro's default fallback package,
+     * {@code null} is returned to indicate the class could not be found.
+     *
+     * @param token the string token from which a class name will be heuristically determined.
+     * @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined.
+     */
+    protected Class getHashFormatClass(String token) {
+
+        //check to see if the token is a fully qualified class name:
+        Class clazz = lookupHashFormatClass(token);
+
+        if (clazz == null) {
+            //check to see if the token is a FQCN alias:
+            if (!CollectionUtils.isEmpty(this.formatClassNames)) {
+                String value = this.formatClassNames.get(token);
+                if (value != null) {
+                    //found an alias - see if the value is a class:
+                    clazz = lookupHashFormatClass(token);
+                }
+            }
+        }
+
+        if (clazz == null) {
+            //token wasn't a FQCN or a FQCN alias - try searching in configured packages:
+            if (!CollectionUtils.isEmpty(this.searchPackages)) {
+                for (String packageName : this.searchPackages) {
+                    clazz = getHashFormatClass(packageName, token);
+                    if (clazz != null) {
+                        //found it:
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (clazz == null) {
+            //couldn't find it in any configured search packages.  Try Shiro's default search package:
+            clazz = getHashFormatClass(DEFAULT_HASH_FORMAT_PACKAGE_NAME, token);
+        }
+
+        if (clazz != null) {
+            assertHashFormatImpl(clazz);
+        }
+
+        return clazz;
+    }
+
+    /**
+     * Heuristically determine the fully qualified {@code HashFormat} implementation class name in the specified
+     * package based on the provided token.
+     * <p/>
+     * The token is expected to be a relevant fragment of an unqualified class name in the specified package.
+     * A 'relevant fragment' can be one of the following:
+     * <ul>
+     * <li>The {@code HashFormat} implementation unqualified class name</li>
+     * <li>The prefix of an unqualified class name ending with the text {@code Format}.  The first character of
+     * this prefix can be upper or lower case and both options will be tried.</li>
+     * <li>The prefix of an unqualified class name ending with the text {@code HashFormat}.  The first character of
+     * this prefix can be upper or lower case and both options will be tried.</li>
+     * <li>The prefix of an unqualified class name ending with the text {@code CryptoFormat}.  The first character
+     * of this prefix can be upper or lower case and both options will be tried.</li>
+     * </ul>
+     * <p/>
+     * Some examples:
+     * <table>
+     * <tr>
+     * <th>Package Name</th>
+     * <th>Token</th>
+     * <th>Expected Output Class</th>
+     * <th>Notes</th>
+     * </tr>
+     * <tr>
+     * <td>{@code com.foo.whatever}</td>
+     * <td>{@code MyBarFormat}</td>
+     * <td>{@code com.foo.whatever.MyBarFormat}</td>
+     * <td>Token is a complete unqualified class name</td>
+     * </tr>
+     * <tr>
+     * <td>{@code com.foo.whatever}</td>
+     * <td>{@code Bar}</td>
+     * <td>{@code com.foo.whatever.BarFormat} <em>or</em> {@code com.foo.whatever.BarHashFormat} <em>or</em>
+     * {@code com.foo.whatever.BarCryptFormat}</td>
+     * <td>The token is only part of the unqualified class name - i.e. all characters in front of the {@code *Format}
+     * {@code *HashFormat} or {@code *CryptFormat} suffix.  Note that the {@code *Format} variant will be tried before
+     * {@code *HashFormat} and then finally {@code *CryptFormat}</td>
+     * </tr>
+     * <tr>
+     * <td>{@code com.foo.whatever}</td>
+     * <td>{@code bar}</td>
+     * <td>{@code com.foo.whatever.BarFormat} <em>or</em> {@code com.foo.whatever.BarHashFormat} <em>or</em>
+     * {@code com.foo.whatever.BarCryptFormat}</td>
+     * <td>Exact same output as the above {@code Bar} input example. (The token differs only by the first character)</td>
+     * </tr>
+     * </table>
+     *
+     * @param packageName the package to search for matching {@code HashFormat} implementations.
+     * @param token       the string token from which a class name will be heuristically determined.
+     * @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined.
+     */
+    protected Class getHashFormatClass(String packageName, String token) {
+        String test = token;
+        Class clazz = null;
+        String pkg = packageName == null ? "" : packageName;
+
+        //1. Assume the arg is a fully qualified class name in the classpath:
+        clazz = lookupHashFormatClass(test);
+
+        if (clazz == null) {
+            test = pkg + "." + token;
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + StringUtils.uppercaseFirstChar(token) + "Format";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + token + "Format";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + StringUtils.uppercaseFirstChar(token) + "HashFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + token + "HashFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + StringUtils.uppercaseFirstChar(token) + "CryptFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            test = pkg + "." + token + "CryptFormat";
+            clazz = lookupHashFormatClass(test);
+        }
+
+        if (clazz == null) {
+            return null; //ran out of options
+        }
+
+        assertHashFormatImpl(clazz);
+
+        return clazz;
+    }
+
+    protected Class lookupHashFormatClass(String name) {
+        try {
+            return ClassUtils.forName(name);
+        } catch (UnknownClassException ignored) {
+        }
+
+        return null;
+    }
+
+    protected final void assertHashFormatImpl(Class clazz) {
+        if (!HashFormat.class.isAssignableFrom(clazz) || clazz.isInterface()) {
+            throw new IllegalArgumentException("Discovered class [" + clazz.getName() + "] is not a " +
+                    HashFormat.class.getName() + " implementation.");
+        }
+
+    }
+
+    protected final HashFormat newHashFormatInstance(Class clazz) {
+        assertHashFormatImpl(clazz);
+        return (HashFormat)ClassUtils.newInstance(clazz);
+    }
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,45 @@
+/*
+ * 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.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * A {@code HashFormat} is able to format a {@link Hash} instance into a well-defined formatted String.
+ * <p/>
+ * Note that not all HashFormat algorithms are reversible.  That is, they can't be parsed and reconstituted to the
+ * original Hash instance.  The traditional <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">
+ * Unix crypt(3)</a> is one such format.
+ * <p/>
+ * The formats that <em>are</em> reversible however will be represented as {@link ParsableHashFormat} instances.
+ *
+ * @see ParsableHashFormat
+ *
+ * @since 1.2
+ */
+public interface HashFormat {
+
+    /**
+     * Returns a formatted string representing the specified Hash instance.
+     *
+     * @param hash the hash instance to format into a String.
+     * @return a formatted string representing the specified Hash instance.
+     */
+    String format(Hash hash);
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,27 @@
+/*
+ * 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.shiro.crypto.hash.format;
+
+/**
+ * @since 1.2
+ */
+public interface HashFormatFactory {
+
+    HashFormat getInstance(String id);
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,41 @@
+/*
+ * 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.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * {@code HashFormat} that outputs <em>only</em> The hash's digest bytes in hex format.  It does not print out
+ * anything else (salt, iterations, etc).  This implementation is mostly provided as a convenience for
+ * command-line hashing.
+ *
+ * @since 1.2
+ */
+public class HexFormat implements HashFormat {
+
+    /**
+     * Returns {@code hash != null ? hash.toHex() : null}.
+     *
+     * @param hash the hash instance to format into a String.
+     * @return {@code hash != null ? hash.toHex() : null}.
+     */
+    public String format(Hash hash) {
+        return hash != null ? hash.toHex() : null;
+    }
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,42 @@
+/*
+ * 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.shiro.crypto.hash.format;
+
+/**
+ * A {@code HashFormat} that supports
+ * <a href="http://packages.python.org/passlib/modular_crypt_format.html">Modular Crypt Format</a> token rules.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">Crypt (unix)</a>
+ * @see <a href="http://www.tummy.com/journals/entries/jafo_20110117_054918">MCF Journal Entry</a>
+ * @since 1.2
+ */
+public interface ModularCryptFormat extends HashFormat {
+
+    public static final String TOKEN_DELIMITER = "$";
+
+    /**
+     * Returns the Modular Crypt Format identifier that indicates how the formatted String should be parsed.  This id
+     * is always in the MCF-formatted string's first token.
+     * <p/>
+     * Example values are {@code md5}, {@code 1}, {@code 2}, {@code apr1}, etc.
+     *
+     * @return the Modular Crypt Format identifier that indicates how the formatted String should be parsed.
+     */
+    String getId();
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java Fri Dec  2 19:52:37 2011
@@ -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.shiro.crypto.hash.format;
+
+import org.apache.shiro.crypto.hash.Hash;
+
+/**
+ * A {@code ParsableHashFormat} is able to parse a formatted string and convert it into a {@link Hash} instance.
+ * <p/>
+ * This interface exists to represent {@code HashFormat}s that can offer two-way conversion
+ * (Hash -&gt; String, String -&gt; Hash) capabilities.  Some HashFormats, such as many {@link ModularCryptFormat}s
+ * (like Unix Crypt(3)) only support one way conversion and therefore wouldn't implement this interface.
+ *
+ * @see Shiro1CryptFormat
+ *
+ * @since 1.2
+ */
+public interface ParsableHashFormat extends HashFormat {
+
+    /**
+     * Parses the specified formatted string and returns the corresponding Hash instance.
+     *
+     * @param formatted the formatted string representing a Hash.
+     * @return the corresponding Hash instance.
+     */
+    Hash parse(String formatted);
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,166 @@
+/*
+ * 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.shiro.crypto.hash.format;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.SimpleHash;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.StringUtils;
+
+/**
+ * The {@code Shiro1CryptFormat} is a fully reversible
+ * <a href="http://packages.python.org/passlib/modular_crypt_format.html">Modular Crypt Format</a> (MCF).  Because it is
+ * fully reversible (i.e. Hash -&gt; String, String -&gt; Hash), it does NOT use the traditional MCF encoding alphabet
+ * (the traditional MCF encoding, aka H64, is bit-destructive and cannot be reversed).  Instead, it uses fully
+ * reversible Base64 encoding for the Hash digest and any salt value.
+ * <h2>Format</h2>
+ * <p>Hash instances formatted with this implementation will result in a String with the following dollar-sign ($)
+ * delimited format:</p>
+ * <pre>
+ * <b>$</b>mcfFormatId<b>$</b>algorithmName<b>$</b>iterationCount<b>$</b>base64EncodedSalt<b>$</b>base64EncodedDigest
+ * </pre>
+ * <p>Each token is defined as follows:</p>
+ * <table>
+ *     <tr>
+ *         <th>Position</th>
+ *         <th>Token</th>
+ *         <th>Description</th>
+ *         <th>Required?</th>
+ *     </tr>
+ *     <tr>
+ *         <td>1</td>
+ *         <td>{@code mcfFormatId}</td>
+ *         <td>The Modular Crypt Format identifier for this implementation, equal to <b>{@code shiro1}</b>.
+ *             ( This implies that all {@code shiro1} MCF-formatted strings will always begin with the prefix
+ *             {@code $shiro1$} ).</td>
+ *         <td>true</td>
+ *     </tr>
+ *     <tr>
+ *         <td>2</td>
+ *         <td>{@code algorithmName}</td>
+ *         <td>The name of the hash algorithm used to perform the hash.  This is an algorithm name understood by
+ *         {@code MessageDigest}.{@link java.security.MessageDigest#getInstance(String) getInstance}, for example
+ *         {@code MD5}, {@code SHA-256}, {@code SHA-256}, etc.</td>
+ *         <td>true</td>
+ *     </tr>
+ *     <tr>
+ *         <td>3</td>
+ *         <td>{@code iterationCount}</td>
+ *         <td>The number of hash iterations performed.</td>
+ *         <td>true (1 <= N <= Integer.MAX_VALUE)</td>
+ *     </tr>
+ *     <tr>
+ *         <td>4</td>
+ *         <td>{@code base64EncodedSalt}</td>
+ *         <td>The Base64-encoded salt byte array.  This token only exists if a salt was used to perform the hash.</td>
+ *         <td>false</td>
+ *     </tr>
+ *     <tr>
+ *         <td>5</td>
+ *         <td>{@code base64EncodedDigest}</td>
+ *         <td>The Base64-encoded digest byte array.  This is the actual hash result.</td>
+ *         <td>true</td>
+ *     </tr>
+ * </table>
+ *
+ * @see ModularCryptFormat
+ * @see ParsableHashFormat
+ *
+ * @since 1.2
+ */
+public class Shiro1CryptFormat implements ModularCryptFormat, ParsableHashFormat {
+
+    public static final String ID = "shiro1";
+    public static final String MCF_PREFIX = TOKEN_DELIMITER + ID + TOKEN_DELIMITER;
+
+    public Shiro1CryptFormat() {
+    }
+
+    public String getId() {
+        return ID;
+    }
+
+    public String format(Hash hash) {
+        if (hash == null) {
+            return null;
+        }
+
+        String algorithmName = hash.getAlgorithmName();
+        ByteSource salt = hash.getSalt();
+        int iterations = hash.getIterations();
+        StringBuilder sb = new StringBuilder(MCF_PREFIX).append(algorithmName).append(TOKEN_DELIMITER).append(iterations).append(TOKEN_DELIMITER);
+
+        if (salt != null) {
+            sb.append(salt.toBase64());
+        }
+
+        sb.append(TOKEN_DELIMITER);
+        sb.append(hash.toBase64());
+
+        return sb.toString();
+    }
+
+    public Hash parse(String formatted) {
+        if (formatted == null) {
+            return null;
+        }
+        if (!formatted.startsWith(MCF_PREFIX)) {
+            //TODO create a HashFormatException class
+            String msg = "The argument is not a valid '" + ID + "' formatted hash.";
+            throw new IllegalArgumentException(msg);
+        }
+
+        String suffix = formatted.substring(MCF_PREFIX.length());
+        String[] parts = suffix.split("\\$");
+
+        //last part is always the digest/checksum, Base64-encoded:
+        int i = parts.length-1;
+        String digestBase64 = parts[i--];
+        //second-to-last part is always the salt, Base64-encoded:
+        String saltBase64 = parts[i--];
+        String iterationsString = parts[i--];
+        String algorithmName = parts[i];
+
+        byte[] digest = Base64.decode(digestBase64);
+        ByteSource salt = null;
+
+        if (StringUtils.hasLength(saltBase64)) {
+            byte[] saltBytes = Base64.decode(saltBase64);
+            salt = ByteSource.Util.bytes(saltBytes);
+        }
+
+        int iterations;
+        try {
+            iterations = Integer.parseInt(iterationsString);
+        } catch (NumberFormatException e) {
+            String msg = "Unable to parse formatted hash string: " + formatted;
+            throw new IllegalArgumentException(msg, e);
+        }
+
+        SimpleHash hash = new SimpleHash(algorithmName);
+        hash.setBytes(digest);
+        if (salt != null) {
+            hash.setSalt(salt);
+        }
+        hash.setIterations(iterations);
+
+        return hash;
+    }
+}

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java Fri Dec  2 19:52:37 2011
@@ -31,7 +31,7 @@ import java.util.concurrent.ConcurrentHa
  *
  * @since 1.2
  */
-public class DefaultEnvironment implements Environment, Destroyable {
+public class DefaultEnvironment implements NamedObjectEnvironment, Destroyable {
 
     /**
      * The default name under which the application's {@code SecurityManager} instance may be acquired, equal to
@@ -54,12 +54,12 @@ public class DefaultEnvironment implemen
      *
      * @param seed backing map to use to maintain Shiro objects.
      */
+    @SuppressWarnings({"unchecked"})
     public DefaultEnvironment(Map<String, ?> seed) {
         this.securityManagerName = DEFAULT_SECURITY_MANAGER_KEY;
         if (seed == null) {
             throw new IllegalArgumentException("Backing map cannot be null.");
         }
-        //noinspection unchecked
         this.objects = (Map<String, Object>) seed;
     }
 
@@ -88,8 +88,8 @@ public class DefaultEnvironment implemen
         if (securityManager == null) {
             throw new IllegalArgumentException("Null SecurityManager instances are not allowed.");
         }
-        String key = getSecurityManagerName();
-        this.objects.put(key, securityManager);
+        String name = getSecurityManagerName();
+        setObject(name, securityManager);
     }
 
     /**
@@ -98,8 +98,8 @@ public class DefaultEnvironment implemen
      * @return the {@code SecurityManager} in the backing map, or {@code null} if it has not yet been populated.
      */
     protected SecurityManager lookupSecurityManager() {
-        String key = getSecurityManagerName();
-        return (SecurityManager) this.objects.get(key);
+        String name = getSecurityManagerName();
+        return getObject(name, SecurityManager.class);
     }
 
     /**
@@ -133,6 +133,37 @@ public class DefaultEnvironment implemen
         return this.objects;
     }
 
+    @SuppressWarnings({"unchecked"})
+    public <T> T getObject(String name, Class<T> requiredType) throws RequiredTypeException {
+        if (name == null) {
+            throw new NullPointerException("name parameter cannot be null.");
+        }
+        if (requiredType == null) {
+            throw new NullPointerException("requiredType parameter cannot be null.");
+        }
+        Object o = this.objects.get(name);
+        if (o == null) {
+            return null;
+        }
+        if (!requiredType.isInstance(o)) {
+            String msg = "Object named '" + name + "' is not of required type [" + requiredType.getName() + "].";
+            throw new RequiredTypeException(msg);
+        }
+        return (T)o;
+    }
+
+    public void setObject(String name, Object instance) {
+        if (name == null) {
+            throw new NullPointerException("name parameter cannot be null.");
+        }
+        if (instance == null) {
+            this.objects.remove(name);
+        } else {
+            this.objects.put(name, instance);
+        }
+    }
+
+
     public void destroy() throws Exception {
         LifecycleUtils.destroy(this.objects.values());
     }

Added: shiro/trunk/core/src/main/java/org/apache/shiro/env/EnvironmentException.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/env/EnvironmentException.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/env/EnvironmentException.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/env/EnvironmentException.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,19 @@
+package org.apache.shiro.env;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Exception thrown for errors related to {@link Environment} instances or configuration.
+ *
+ * @since 1.2
+ */
+public class EnvironmentException extends ShiroException {
+
+    public EnvironmentException(String message) {
+        super(message);
+    }
+
+    public EnvironmentException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,22 @@
+package org.apache.shiro.env;
+
+/**
+ * An environment that supports object lookup by name.
+ *
+ * @since 1.2
+ */
+public interface NamedObjectEnvironment extends Environment {
+
+    /**
+     * Returns the object in Shiro's environment with the specified name and type or {@code null} if
+     * no object with that name was found.
+     *
+     * @param name the assigned name of the object.
+     * @param requiredType the class that the discovered object should be.  If the object is not the specified type, a
+     * @param <T> the type of the class
+     * @throws RequiredTypeException if the discovered object does not equal, extend, or implement the specified class.
+     * @return the object in Shiro's environment with the specified name (of the specified type) or {@code null} if
+     * no object with that name was found.
+     */
+    <T> T getObject(String name, Class<T> requiredType) throws RequiredTypeException;
+}

Added: shiro/trunk/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,18 @@
+package org.apache.shiro.env;
+
+/**
+ * Exception thrown when attempting to acquire an object of a required type and that object does not equal, extend, or
+ * implement a specified {@code Class}.
+ *
+ * @since 1.2
+ */
+public class RequiredTypeException extends EnvironmentException {
+
+    public RequiredTypeException(String message) {
+        super(message);
+    }
+
+    public RequiredTypeException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/util/ByteSource.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/util/ByteSource.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/util/ByteSource.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/util/ByteSource.java Fri Dec  2 19:52:37 2011
@@ -34,7 +34,7 @@ public interface ByteSource {
      *
      * @return the wrapped byte array.
      */
-    public byte[] getBytes();
+    byte[] getBytes();
 
     /**
      * Returns the <a href="http://en.wikipedia.org/wiki/Hexadecimal">Hex</a>-formatted String representation of the
@@ -43,7 +43,7 @@ public interface ByteSource {
      * @return the <a href="http://en.wikipedia.org/wiki/Hexadecimal">Hex</a>-formatted String representation of the
      *         underlying wrapped byte array.
      */
-    public String toHex();
+    String toHex();
 
     /**
      * Returns the <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a>-formatted String representation of the
@@ -52,7 +52,17 @@ public interface ByteSource {
      * @return the <a href="http://en.wikipedia.org/wiki/Base64">Base 64</a>-formatted String representation of the
      *         underlying wrapped byte array.
      */
-    public String toBase64();
+    String toBase64();
+
+    /**
+     * Returns {@code true} if the underlying wrapped byte array is null or empty (zero length), {@code false}
+     * otherwise.
+     *
+     * @return {@code true} if the underlying wrapped byte array is null or empty (zero length), {@code false}
+     *         otherwise.
+     * @since 1.2
+     */
+    boolean isEmpty();
 
     /**
      * Utility class that can construct ByteSource instances.  This is slightly nicer than needing to know the
@@ -123,5 +133,59 @@ public interface ByteSource {
         public static ByteSource bytes(InputStream stream) {
             return new SimpleByteSource(stream);
         }
+
+        /**
+         * Returns {@code true} if the specified object can be easily represented as a {@code ByteSource} using
+         * the {@link ByteSource.Util}'s default heuristics, {@code false} otherwise.
+         * <p/>
+         * This implementation merely returns {@link SimpleByteSource}.{@link SimpleByteSource#isCompatible(Object) isCompatible(source)}.
+         *
+         * @param source the object to test to see if it can be easily converted to ByteSource instances using default
+         *               heuristics.
+         * @return {@code true} if the specified object can be easily represented as a {@code ByteSource} using
+         *         the {@link ByteSource.Util}'s default heuristics, {@code false} otherwise.
+         */
+        public static boolean isCompatible(Object source) {
+            return SimpleByteSource.isCompatible(source);
+        }
+
+        /**
+         * Returns a {@code ByteSource} instance representing the specified byte source argument.  If the argument
+         * <em>cannot</em> be easily converted to bytes (as is indicated by the {@link #isCompatible(Object)} JavaDoc),
+         * this method will throw an {@link IllegalArgumentException}.
+         *
+         * @param source the byte-backed instance that should be represented as a {@code ByteSource} instance.
+         * @return a {@code ByteSource} instance representing the specified byte source argument.
+         * @throws IllegalArgumentException if the argument <em>cannot</em> be easily converted to bytes
+         *                                  (as indicated by the {@link #isCompatible(Object)} JavaDoc)
+         */
+        public static ByteSource bytes(Object source) throws IllegalArgumentException {
+            if (source == null) {
+                return null;
+            }
+            if (!isCompatible(source)) {
+                String msg = "Unable to heuristically acquire bytes for object of type [" +
+                        source.getClass().getName() + "].  If this type is indeed a byte-backed data type, you might " +
+                        "want to write your own ByteSource implementation to extract its bytes explicitly.";
+                throw new IllegalArgumentException(msg);
+            }
+            if (source instanceof byte[]) {
+                return bytes((byte[]) source);
+            } else if (source instanceof ByteSource) {
+                return (ByteSource) source;
+            } else if (source instanceof char[]) {
+                return bytes((char[]) source);
+            } else if (source instanceof String) {
+                return bytes((String) source);
+            } else if (source instanceof File) {
+                return bytes((File) source);
+            } else if (source instanceof InputStream) {
+                return bytes((InputStream) source);
+            } else {
+                throw new IllegalStateException("Encountered unexpected byte source.  This is a bug - please notify " +
+                        "the Shiro developer list asap (the isCompatible implementation does not reflect this " +
+                        "method's implementation).");
+            }
+        }
     }
 }

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java Fri Dec  2 19:52:37 2011
@@ -22,7 +22,8 @@ import org.apache.shiro.codec.Base64;
 import org.apache.shiro.codec.CodecSupport;
 import org.apache.shiro.codec.Hex;
 
-import java.io.*;
+import java.io.File;
+import java.io.InputStream;
 import java.util.Arrays;
 
 /**
@@ -104,10 +105,39 @@ public class SimpleByteSource implements
         this.bytes = new BytesHelper().getBytes(stream);
     }
 
+    /**
+     * Returns {@code true} if the specified object is a recognized data type that can be easily converted to
+     * bytes by instances of this class, {@code false} otherwise.
+     * <p/>
+     * This implementation returns {@code true} IFF the specified object is an instance of one of the following
+     * types:
+     * <ul>
+     * <li>{@code byte[]}</li>
+     * <li>{@code char[]}</li>
+     * <li>{@link ByteSource}</li>
+     * <li>{@link String}</li>
+     * <li>{@link File}</li>
+     * </li>{@link InputStream}</li>
+     * </ul>
+     *
+     * @param o the object to test to see if it can be easily converted to bytes by instances of this class.
+     * @return {@code true} if the specified object can be easily converted to bytes by instances of this class,
+     *         {@code false} otherwise.
+     * @since 1.2
+     */
+    public static boolean isCompatible(Object o) {
+        return o instanceof byte[] || o instanceof char[] || o instanceof String ||
+                o instanceof ByteSource || o instanceof File || o instanceof InputStream;
+    }
+
     public byte[] getBytes() {
         return this.bytes;
     }
 
+    public boolean isEmpty() {
+        return this.bytes == null || this.bytes.length == 0;
+    }
+
     public String toHex() {
         if ( this.cachedHex == null ) {
             this.cachedHex = Hex.encodeToString(getBytes());
@@ -127,7 +157,10 @@ public class SimpleByteSource implements
     }
 
     public int hashCode() {
-        return toString().hashCode();
+        if (this.bytes == null || this.bytes.length == 0) {
+            return 0;
+        }
+        return Arrays.hashCode(this.bytes);
     }
 
     public boolean equals(Object o) {

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/util/StringUtils.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/util/StringUtils.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/util/StringUtils.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/util/StringUtils.java Fri Dec  2 19:52:37 2011
@@ -478,4 +478,25 @@ public class StringUtils {
         return CollectionUtils.asSet(split);
     }
 
+    /**
+     * Returns the input argument, but ensures the first character is capitalized (if possible).
+     * @param in the string to uppercase the first character.
+     * @return the input argument, but with the first character capitalized (if possible).
+     * @since 1.2
+     */
+    public static String uppercaseFirstChar(String in) {
+        if (in == null || in.length() == 0) {
+            return in;
+        }
+        int length = in.length();
+        StringBuilder sb = new StringBuilder(length);
+
+        sb.append(Character.toUpperCase(in.charAt(0)));
+        if (length > 1) {
+            String remaining = in.substring(1);
+            sb.append(remaining);
+        }
+        return sb.toString();
+    }
+
 }

Added: shiro/trunk/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy (added)
+++ shiro/trunk/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy Fri Dec  2 19:52:37 2011
@@ -0,0 +1,25 @@
+package org.apache.shiro.authc.credential
+
+import org.apache.shiro.util.ByteSource
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: 9/20/11
+ * Time: 3:38 PM
+ * To change this template use File | Settings | File Templates.
+ */
+class DefaultPasswordServiceTest extends GroovyTestCase {
+
+    void testDefault() {
+        def passwordService = new DefaultPasswordService();
+
+        def password = ByteSource.Util.bytes("12345")
+
+        def formatted = passwordService.hashPassword(password)
+        System.out.println "Formatted/stored password: $formatted"
+
+        assertTrue passwordService.passwordsMatch(password, '$shiro1$SHA-512$300000$d07mwTTz3EHqQEdc5KBPCgzigcuwYmbfD3nw7ao7zmA=$B76M6PRqOl4kaScZjKHDWVcE08MwOrqTQyqmmPAIw9Sl0ONG/Rv7GxeUfc5fA3ujhxKJgGgDllDC1EchHFlncw==');
+    }
+
+}

Added: shiro/trunk/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy (added)
+++ shiro/trunk/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy Fri Dec  2 19:52:37 2011
@@ -0,0 +1,40 @@
+/*
+ * 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.shiro.codec
+
+import org.apache.shiro.crypto.SecureRandomNumberGenerator
+
+/**
+ * Test cases for the {@link H64} implementation.
+ */
+class H64Test extends GroovyTestCase {
+
+    void testNothing(){}
+
+    public void testDefault() {
+        byte[] orig = new SecureRandomNumberGenerator().nextBytes(6).bytes
+
+        System.out.println("bytes: $orig");
+
+        String encoded = H64.encodeToString(orig)
+        System.out.println("encoded: $encoded");
+
+        assertNotNull orig
+    }
+}

Added: shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy (added)
+++ shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy Fri Dec  2 19:52:37 2011
@@ -0,0 +1,158 @@
+/*
+ * 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.shiro.crypto.hash.format
+
+import org.apache.shiro.crypto.SecureRandomNumberGenerator
+import org.apache.shiro.crypto.hash.SimpleHash
+
+/**
+ * Unit tests for the {@link Shiro1CryptFormat} implementation.
+ *
+ * @since 1.2
+ */
+class Shiro1CryptFormatTest extends GroovyTestCase {
+
+    void testGetId() {
+        assertEquals "shiro1", new Shiro1CryptFormat().getId()
+    }
+
+    void testFormatDefault() {
+        def format = new Shiro1CryptFormat();
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+        def salt = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, salt, iterations)
+
+        String formatted = format.format(hash);
+
+        String expected =
+            Shiro1CryptFormat.MCF_PREFIX + alg + '$' + iterations + '$' + salt.toBase64() + '$' + hash.toBase64()
+
+        assertNotNull formatted
+        assertEquals expected, formatted
+    }
+
+    void testFormatWithoutSalt() {
+        def format = new Shiro1CryptFormat();
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, null, iterations)
+
+        String formatted = format.format(hash);
+
+        String expected = Shiro1CryptFormat.MCF_PREFIX + alg + '$' + iterations + '$$' + hash.toBase64()
+
+        assertNotNull formatted
+        assertEquals expected, formatted
+    }
+
+    void testFormatWithNullArgument() {
+        def format = new Shiro1CryptFormat()
+        def result = format.format(null)
+        assertNull result
+    }
+
+    void testParseDefault() {
+        def format = new Shiro1CryptFormat();
+        def delim = Shiro1CryptFormat.TOKEN_DELIMITER
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+        def salt = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, salt, iterations)
+
+        String formatted = Shiro1CryptFormat.MCF_PREFIX +
+                alg + delim +
+                iterations + delim +
+                salt.toBase64() + delim +
+                hash.toBase64()
+
+        def parsedHash = format.parse(formatted)
+
+        assertEquals hash, parsedHash
+        assertEquals hash.algorithmName, parsedHash.algorithmName
+        assertEquals hash.iterations, parsedHash.iterations
+        assertEquals hash.salt, parsedHash.salt
+        assertTrue Arrays.equals(hash.bytes, parsedHash.bytes)
+    }
+
+    void testParseWithoutSalt() {
+        def format = new Shiro1CryptFormat();
+        def delim = Shiro1CryptFormat.TOKEN_DELIMITER
+
+        def alg = "SHA-512"
+        def iterations = 10;
+        def rng = new SecureRandomNumberGenerator()
+        def source = rng.nextBytes()
+
+        def hash = new SimpleHash(alg, source, null, iterations)
+
+        String formatted = Shiro1CryptFormat.MCF_PREFIX +
+                alg + delim +
+                iterations + delim +
+                delim +
+                hash.toBase64()
+
+        def parsedHash = format.parse(formatted)
+
+        assertEquals hash, parsedHash
+        assertEquals hash.algorithmName, parsedHash.algorithmName
+        assertEquals hash.iterations, parsedHash.iterations
+        assertNull hash.salt
+        assertTrue Arrays.equals(hash.bytes, parsedHash.bytes)
+    }
+
+    void testParseWithNullArgument() {
+        def format = new Shiro1CryptFormat()
+        def result = format.parse(null)
+        assertNull result
+    }
+
+    void testParseWithInvalidId() {
+        def format = new Shiro1CryptFormat()
+        try {
+            format.parse('$foo$xxxxxxx')
+            fail("parse should have thrown an IllegalArgumentException")
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    void testParseWithNonNumericIterations() {
+        def format = new Shiro1CryptFormat();
+        def formatted = '$shiro1$SHA-512$N$foo$foo'
+
+        try {
+            format.parse(formatted)
+            fail("parse should have thrown an IllegalArgumentException")
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+}

Copied: shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHashServiceTest.java (from r1201985, shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java)
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHashServiceTest.java?p2=shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHashServiceTest.java&p1=shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java&r1=1201985&r2=1209662&rev=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHashServiceTest.java Fri Dec  2 19:52:37 2011
@@ -1,18 +1,16 @@
 package org.apache.shiro.crypto.hash;
 
-import java.util.Arrays;
-
 import junit.framework.TestCase;
-
 import org.apache.shiro.crypto.SecureRandomNumberGenerator;
-import org.apache.shiro.util.ByteSource;
 import org.junit.Test;
 
+import java.util.Arrays;
+
 /**
- * Test for {@link DefaultHasher} class.
+ * Test for {@link DefaultHashService} class.
  *
  */
-public class DefaultHasherTest {
+public class DefaultHashServiceTest {
 
 	/**
 	 * If the same string is hashed twice and no salt was supplied, hashed
@@ -20,12 +18,12 @@ public class DefaultHasherTest {
 	 */
 	@Test
 	public void testOnlyRandomSaltRandomness() {
-		Hasher hasher = createHasher();
+		HashService hashService = createHashService();
 
-		HashResponse firstHash = hashString(hasher, "password");
-		HashResponse secondHash = hashString(hasher, "password");
+		Hash firstHash = hashString(hashService, "password");
+		Hash secondHash = hashString(hashService, "password");
 
-		assertNotEquals(firstHash.getHash().toBase64(), secondHash.getHash().toBase64());
+		assertNotEquals(firstHash.toBase64(), secondHash.toBase64());
 	}
 
 	/**
@@ -35,12 +33,12 @@ public class DefaultHasherTest {
 	 */
 	@Test
 	public void testOnlyRandomSaltReturn() {
-		Hasher hasher = createHasher();
+		HashService hashService = createHashService();
 
-		HashResponse firstHash = hashString(hasher, "password");
-		HashResponse secondHash = hashString(hasher, "password", firstHash.getSalt().getBytes());
+		Hash firstHash = hashString(hashService, "password");
+		Hash secondHash = hashString(hashService, "password", firstHash.getSalt().getBytes());
 
-		TestCase.assertEquals(firstHash.getHash().toBase64(), secondHash.getHash().toBase64());
+		TestCase.assertEquals(firstHash.toBase64(), secondHash.toBase64());
 	}
 
 	/**
@@ -49,12 +47,12 @@ public class DefaultHasherTest {
 	 */
 	@Test
 	public void testOnlyRandomSaltHash() {
-		Hasher hasher = createHasher();
+		HashService hashService = createHashService();
 
-		HashResponse firstHash = hashString(hasher, "password");
-		HashResponse secondHash = hashString(hasher, "password2", firstHash.getSalt().getBytes());
+		Hash firstHash = hashString(hashService, "password");
+		Hash secondHash = hashString(hashService, "password2", firstHash.getSalt().getBytes());
 
-		assertNotEquals(firstHash.getHash().toBase64(), secondHash.getHash().toBase64());
+		assertNotEquals(firstHash.toBase64(), secondHash.toBase64());
 	}
 
 	/**
@@ -63,12 +61,12 @@ public class DefaultHasherTest {
 	 */
 	@Test
 	public void testBothSaltsRandomness() {
-		Hasher hasher = createHasherWithSalt();
+		HashService hashService = createHashServiceWithSalt();
 
-		HashResponse firstHash = hashString(hasher, "password");
-		HashResponse secondHash = hashString(hasher, "password");
+		Hash firstHash = hashString(hashService, "password");
+		Hash secondHash = hashString(hashService, "password");
 
-		assertNotEquals(firstHash.getHash().toBase64(), secondHash.getHash().toBase64());
+		assertNotEquals(firstHash.toBase64(), secondHash.toBase64());
 	}
 
 	/**
@@ -78,12 +76,12 @@ public class DefaultHasherTest {
 	 */
 	@Test
 	public void testBothSaltsReturn() {
-		Hasher hasher = createHasherWithSalt();
+		HashService hashService = createHashServiceWithSalt();
 
-		HashResponse firstHash = hashString(hasher, "password");
-		HashResponse secondHash = hashString(hasher, "password", firstHash.getSalt().getBytes());
+		Hash firstHash = hashString(hashService, "password");
+		Hash secondHash = hashString(hashService, "password", firstHash.getSalt().getBytes());
 
-		TestCase.assertEquals(firstHash.getHash().toBase64(), secondHash.getHash().toBase64());
+		TestCase.assertEquals(firstHash.toBase64(), secondHash.toBase64());
 	}
 
 	/**
@@ -92,12 +90,12 @@ public class DefaultHasherTest {
 	 */
 	@Test
 	public void testBothSaltsHash() {
-		Hasher hasher = createHasherWithSalt();
+		HashService hashService = createHashServiceWithSalt();
 
-		HashResponse firstHash = hashString(hasher, "password");
-		HashResponse secondHash = hashString(hasher, "password2", firstHash.getSalt().getBytes());
+		Hash firstHash = hashString(hashService, "password");
+		Hash secondHash = hashString(hashService, "password2", firstHash.getSalt().getBytes());
 
-		assertNotEquals(firstHash.getHash().toBase64(), secondHash.getHash().toBase64());
+		assertNotEquals(firstHash.toBase64(), secondHash.toBase64());
 	}
 
 	/**
@@ -105,38 +103,38 @@ public class DefaultHasherTest {
 	 */
 	@Test
 	public void testBaseSaltChangesResult() {
-		Hasher saltedHasher = createHasherWithSalt();
-		Hasher hasher = createHasher();
+		HashService saltedhashService = createHashServiceWithSalt();
+		HashService hashService = createHashService();
 
-		HashResponse firstHash = hashStringPredictable(saltedHasher, "password");
-		HashResponse secondHash = hashStringPredictable(hasher, "password");
+		Hash firstHash = hashStringPredictable(saltedhashService, "password");
+		Hash secondHash = hashStringPredictable(hashService, "password");
 
-		assertNotEquals(firstHash.getHash().toBase64(), secondHash.getHash().toBase64());
+		assertNotEquals(firstHash.toBase64(), secondHash.toBase64());
 	}
 
-	protected HashResponse hashString(Hasher hasher, String string) {
-		return hasher.computeHash(new SimpleHashRequest(ByteSource.Util.bytes(string)));
+	protected Hash hashString(HashService hashService, String string) {
+        return hashService.computeHash(new HashRequest.Builder().setSource(string).build());
 	}
 
-	protected HashResponse hashString(Hasher hasher, String string, byte[] salt) {
-		return hasher.computeHash(new SimpleHashRequest(ByteSource.Util.bytes(string), ByteSource.Util.bytes(salt)));
+	protected Hash hashString(HashService hashService, String string, byte[] salt) {
+		return hashService.computeHash(new HashRequest.Builder().setSource(string).setSalt(salt).build());
 	}
 
-	private HashResponse hashStringPredictable(Hasher hasher, String string) {
+	private Hash hashStringPredictable(HashService hashService, String string) {
 		byte[] salt = new byte[20];
 		Arrays.fill(salt, (byte) 2);
-		return hasher.computeHash(new SimpleHashRequest(ByteSource.Util.bytes(string), ByteSource.Util.bytes(salt)));
+		return hashService.computeHash(new HashRequest.Builder().setSource(string).setSalt(salt).build());
 	}
 
-	private Hasher createHasher() {
-		return new DefaultHasher();
+	private HashService createHashService() {
+		return new DefaultHashService();
 	}
 
-	private Hasher createHasherWithSalt() {
-		DefaultHasher defaultHasher = new DefaultHasher();
-		defaultHasher.setBaseSalt((new SecureRandomNumberGenerator()).nextBytes().getBytes());
+	private HashService createHashServiceWithSalt() {
+		DefaultHashService defaultHashService = new DefaultHashService();
+		defaultHashService.setPrivateSalt(new SecureRandomNumberGenerator().nextBytes());
 		
-		return defaultHasher;
+		return defaultHashService;
 	}
 
 	private void assertNotEquals(String str1, String str2) {



Mime
View raw message