shiro-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lhazlew...@apache.org
Subject svn commit: r1209662 [1/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
Author: lhazlewood
Date: Fri Dec  2 19:52:37 2011
New Revision: 1209662

URL: http://svn.apache.org/viewvc?rev=1209662&view=rev
Log:
SHIRO-213, SHIRO-279, SHIRO-280, SHIRO-302: Added PasswordService and supporting implementations, HashService (renamed from Hasher to avoid confusion/conflict w/ the command-line Hasher) and supporting implementations, a new HashFormat interface and supporting implementations.  More tests and documentation to follow today and later this week.

Added:
    shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/codec/H64.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java
      - copied, changed from r1201985, shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHasher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java
      - copied, changed from r1201985, shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java
      - copied, changed from r1201985, shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hasher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Base64Format.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/DefaultHashFormatFactory.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormat.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HashFormatFactory.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/HexFormat.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ModularCryptFormat.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/ParsableHashFormat.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.java
    shiro/trunk/core/src/main/java/org/apache/shiro/env/EnvironmentException.java
    shiro/trunk/core/src/main/java/org/apache/shiro/env/NamedObjectEnvironment.java
    shiro/trunk/core/src/main/java/org/apache/shiro/env/RequiredTypeException.java
    shiro/trunk/core/src/test/groovy/org/apache/shiro/authc/credential/
    shiro/trunk/core/src/test/groovy/org/apache/shiro/authc/credential/DefaultPasswordServiceTest.groovy
    shiro/trunk/core/src/test/groovy/org/apache/shiro/codec/H64Test.groovy
    shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/format/
    shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/format/Shiro1CryptFormatTest.groovy
    shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHashServiceTest.java
      - copied, changed from r1201985, shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java
Removed:
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHasher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashResponse.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hasher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashResponse.java
    shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHasherTest.groovy
    shiro/trunk/core/src/test/java/org/apache/shiro/crypto/hash/DefaultHasherTest.java
Modified:
    shiro/trunk/RELEASE-NOTES
    shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
    shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java
    shiro/trunk/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java
    shiro/trunk/core/src/main/java/org/apache/shiro/env/DefaultEnvironment.java
    shiro/trunk/core/src/main/java/org/apache/shiro/util/ByteSource.java
    shiro/trunk/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java
    shiro/trunk/core/src/main/java/org/apache/shiro/util/StringUtils.java
    shiro/trunk/tools/hasher/src/main/java/org/apache/shiro/tools/hasher/Hasher.java
    shiro/trunk/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
    shiro/trunk/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java

Modified: shiro/trunk/RELEASE-NOTES
URL: http://svn.apache.org/viewvc/shiro/trunk/RELEASE-NOTES?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/RELEASE-NOTES (original)
+++ shiro/trunk/RELEASE-NOTES Fri Dec  2 19:52:37 2011
@@ -33,8 +33,10 @@ Backwards Incompatible Changes
   exactly how the Session may be used for subject state persistence.  This allows a
   single point of control rather than needing to configure Shiro in multiple places.
 
-  If you overrode this method in Shiro 1.0 or 1.2, please look at the new
+  If you overrode this method in Shiro 1.0 or 1.1, please look at the new
   org.apache.shiro.mgt.DefaultSubjectDAO implementation, which performs compatible logic.
+  Documentation for this is covered here:
+  http://shiro.apache.org/session-management.html#SessionManagement-SessionsandSubjectState
 
 - The org.apache.shiro.web.session.mgt.ServletContainerSessionManager implementation
   (enabled by default for all web applications) no longer subclasses
@@ -48,6 +50,7 @@ Backwards Incompatible Changes
   be honored.  It was better to remove the extends clause to ensure that any
   such configuration would fail fast when Shiro starts up to reflect the invalid config.
 
+
 Potential Breaking Changes
 --------------------------------
 - The org.apache.shiro.web.filter.mgt.FilterChainManager class's
@@ -56,6 +59,40 @@ Potential Breaking Changes
   If you ever called this method, you can call the
   addFilter(name, filter, true) method to achieve the <= 1.1 behavior.
 
+- The org.apache.shiro.crypto.SecureRandomNumberGenerator previously defaulted to generating
+  128 random _bytes_ each time the nextBytes() method was called.  This is too large for most purposes, so the
+  default has been changed to 16 _bytes_ (which equals 128 bits - what was originally intended).  If for some reason
+  you need more than 16 bytes (128 bits) of randomly generated bits, you will need to configure the
+  'defaultNextByteSize' property to match your desired size (in bytes, NOT bits).
+
+- Shiro's Block Cipher Services (AesCipherService, BlowfishCipherService) have had the following changes:
+
+  1) The internal Cipher Mode and Streaming Cipher Mode have been changed from CFB to the new default of CBC.
+     CBC is more commonly used for block ciphers today (e.g. SSL).
+     If you were using an AES or Blowfish CipherService you will want to revert to the previous defaults in your config
+     to ensure you can still decrypt previously encrypted data.  For example, in code:
+
+     blockCipherService.setMode(OperationMode.CFB);
+     blockCipherService.setStreamingMode(OperationMode.CFB);
+
+     or, in shiro.ini:
+
+     blockCipherService.modeName = CFB
+     blockCipherService.streamingModeName = CFB
+
+  2) The internal Streaming Padding Scheme has been changed from NONE to PKCS5 as PKCS5 is more commonly used.
+     If you were using an AES or Blowfish CipherService for streaming operations, you will want to revert to the
+     previous padding scheme default to ensure you can still decrypt previously encrypted data.  For example, in code:
+
+     blockCipherService.setStreamingPaddingScheme(PaddingScheme.NONE);
+
+     or, in shiro.ini:
+
+     blockCipherService.streamingPaddingSchemeName = NoPadding
+
+     Note the difference in code vs shiro.ini in this last example: 'NoPadding' is the correct text value, 'NONE' is
+     the correct Enum value.
+
 ###########################################################
 # 1.1.0
 ###########################################################

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java Fri Dec  2 19:52:37 2011
@@ -18,143 +18,156 @@
  */
 package org.apache.shiro.authc.credential;
 
-import org.apache.shiro.authc.AuthenticationInfo;
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.authc.SaltedAuthenticationInfo;
 import org.apache.shiro.codec.Base64;
-import org.apache.shiro.codec.CodecSupport;
-import org.apache.shiro.codec.Hex;
-import org.apache.shiro.crypto.hash.*;
+import org.apache.shiro.crypto.RandomNumberGenerator;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.crypto.hash.SimpleHash;
 import org.apache.shiro.util.ByteSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Arrays;
 
 /**
- * Default implementation of the {@link PasswordService} interface.  Delegates to an internal (configurable)
- * {@link Hasher} instance.
+ * Default implementation of the {@link PasswordService} interface.
  *
  * @since 1.2
  */
 public class DefaultPasswordService implements PasswordService {
 
-    private ConfigurableHasher hasher;
-
-    private String storedCredentialsEncoding = "base64";
+    public static final String DEFAULT_HASH_ALGORITHM_NAME = "SHA-512";
+    //see http://www.katasoft.com/blog/2011/04/04/strong-password-hashing-apache-shiro
+    public static final int DEFAULT_HASH_ITERATIONS = 500000; //500,000
+    public static final int DEFAULT_SALT_SIZE = 32; //32 bytes == 256 bits
+
+    private static final String MCF_PREFIX = "$shiro1$"; //Modular Crypt Format prefix specific to Shiro's needs
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultPasswordService.class);
+
+    private String hashAlgorithmName;
+    private int hashIterations;
+    private int saltSize;
+    private RandomNumberGenerator randomNumberGenerator;
 
     public DefaultPasswordService() {
-        this.hasher = new DefaultHasher();
-        this.hasher.setHashAlgorithmName(Sha512Hash.ALGORITHM_NAME);
-        //see http://www.katasoft.com/blog/2011/04/04/strong-password-hashing-apache-shiro:
-        this.hasher.setHashIterations(200000);
+        this.hashAlgorithmName = DEFAULT_HASH_ALGORITHM_NAME;
+        this.hashIterations = DEFAULT_HASH_ITERATIONS;
+        this.saltSize = DEFAULT_SALT_SIZE;
+        this.randomNumberGenerator = new SecureRandomNumberGenerator();
     }
 
-    public HashResponse hashPassword(ByteSource plaintextPassword) {
-        byte[] plaintextBytes = plaintextPassword != null ? plaintextPassword.getBytes() : null;
-        if (plaintextBytes == null || plaintextBytes.length == 0) {
+    public String hashPassword(String plaintext) {
+        if (plaintext == null || plaintext.length() == 0) {
             return null;
         }
-
-        return this.hasher.computeHash(new SimpleHashRequest(plaintextPassword));
+        return hashPassword(ByteSource.Util.bytes(plaintext));
     }
 
-    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
-
-        ByteSource publicSalt = null;
-        if (info instanceof SaltedAuthenticationInfo) {
-            publicSalt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
+    public String hashPassword(ByteSource plaintext) {
+        if (plaintext == null) {
+            return null;
         }
+        byte[] plaintextBytes = plaintext.getBytes();
+        if (plaintextBytes == null || plaintextBytes.length == 0) {
+            return null;
+        }
+        String algorithmName = getHashAlgorithmName();
+        ByteSource salt = getRandomNumberGenerator().nextBytes(getSaltSize());
+        int iterations = Math.max(1, getHashIterations());
 
-        Hash tokenCredentialsHash = hashProvidedCredentials(token, publicSalt);
-        byte[] storedCredentialsBytes = getCredentialsBytes(info);
+        Hash result = new SimpleHash(algorithmName, plaintext, salt, iterations);
 
-        return Arrays.equals(tokenCredentialsHash.getBytes(), storedCredentialsBytes);
+        //Modular Crypt Format
+        //TODO: make this pluggable:
+        return new StringBuilder(MCF_PREFIX).append(algorithmName).append("$").append(iterations).append("$")
+                .append(salt.toBase64()).append("$").append(result.toBase64()).toString();
     }
 
-    protected byte[] getCredentialsBytes(AuthenticationInfo info) {
-        Object credentials = info.getCredentials();
+    public boolean passwordsMatch(ByteSource submittedPassword, String savedPassword) {
+        if (savedPassword == null) {
+            return isEmpty(submittedPassword);
+        } else {
+            return !isEmpty(submittedPassword) && doPasswordsMatch(submittedPassword, savedPassword);
+        }
+    }
 
-        byte[] bytes = new BytesHelper().getBytes(credentials);
+    private static boolean isEmpty(ByteSource source) {
+        return source == null || source.getBytes() == null || source.getBytes().length == 0;
+    }
 
-        if (this.storedCredentialsEncoding != null &&
-                (credentials instanceof String || credentials instanceof char[])) {
-            assertEncodingSupported(this.storedCredentialsEncoding);
-            bytes = decode(bytes, this.storedCredentialsEncoding);
+    private boolean doPasswordsMatch(ByteSource submittedPassword, String savedPassword) {
+        if (!savedPassword.startsWith(MCF_PREFIX)) {
+            log.warn("Encountered unrecognized saved password format.  Falling back to simple equality " +
+                    "comparison.  Use the PasswordService to hash new passwords as well as match them.");
+            return ByteSource.Util.bytes(savedPassword).equals(submittedPassword);
         }
 
-        return bytes;
-    }
+        String suffix = savedPassword.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--];
+
+        /*String timestampString = null;
 
-    protected byte[] decode(byte[] storedCredentials, String encodingName) {
-        if ("hex".equalsIgnoreCase(encodingName)) {
-            return Hex.decode(storedCredentials);
-        } else if ("base64".equalsIgnoreCase(encodingName) ||
-                "base-64".equalsIgnoreCase(encodingName)) {
-            return Base64.decode(storedCredentials);
+        if (parts.length == 5) {
+            timestampString = parts[i--];
+        } */
+
+        byte[] digest = Base64.decode(digestBase64);
+
+        byte[] salt = Base64.decode(saltBase64);
+        int iterations;
+        try {
+            iterations = Integer.parseInt(iterationsString);
+        } catch (NumberFormatException e) {
+            log.error("Unable to parse saved password string: " + savedPassword, e);
+            throw e;
         }
-        throw new IllegalStateException("Unsupported encoding '" + encodingName + "'.");
-    }
 
-    protected Hash hashProvidedCredentials(AuthenticationToken token, ByteSource salt) {
-        Object credentials = token.getCredentials();
-        byte[] credentialsBytes = new BytesHelper().getBytes(credentials);
-        ByteSource credentialsByteSource = ByteSource.Util.bytes(credentialsBytes);
+        //now compute the digest on the submitted password.  If the resulting digest matches the saved digest,
+        //the password matches:
 
-        HashRequest request = new SimpleHashRequest(credentialsByteSource, salt);
+        Hash submittedHash = new SimpleHash(algorithmName, submittedPassword, salt, iterations);
 
-        HashResponse response = this.hasher.computeHash(request);
+        return Arrays.equals(digest, submittedHash.getBytes());
+    }
 
-        return response.getHash();
+    public String getHashAlgorithmName() {
+        return hashAlgorithmName;
     }
 
-    /**
-     * Returns {@code true} if the argument equals (ignoring case):
-     * <ul>
-     * <li>{@code hex}</li>
-     * <li>{@code base64}</li>
-     * <li>{@code base-64}</li>
-     * </ul>
-     * {@code false} otherwise.
-     * <p/>
-     * Subclasses should override this method as well as the {@link #decode(byte[], String)} method if other
-     * encodings should be supported.
-     *
-     * @param encodingName the name of the encoding to check.
-     * @return {@code }
-     */
-    protected boolean isEncodingSupported(String encodingName) {
-        return "hex".equalsIgnoreCase(encodingName) ||
-                "base64".equalsIgnoreCase(encodingName) ||
-                "base-64".equalsIgnoreCase(encodingName);
+    public void setHashAlgorithmName(String hashAlgorithmName) {
+        this.hashAlgorithmName = hashAlgorithmName;
     }
 
+    public int getHashIterations() {
+        return hashIterations;
+    }
 
-    protected void assertEncodingSupported(String encodingName) throws IllegalArgumentException {
-        if (!isEncodingSupported(encodingName)) {
-            String msg = "Unsupported encoding '" + encodingName + "'.  Please check for typos.";
-            throw new IllegalArgumentException(msg);
-        }
+    public void setHashIterations(int hashIterations) {
+        this.hashIterations = hashIterations;
     }
 
-    public ConfigurableHasher getHasher() {
-        return hasher;
+    public int getSaltSize() {
+        return saltSize;
     }
 
-    public void setHasher(ConfigurableHasher hasher) {
-        this.hasher = hasher;
+    public void setSaltSize(int saltSize) {
+        this.saltSize = saltSize;
     }
 
-    public void setStoredCredentialsEncoding(String storedCredentialsEncoding) {
-        if (storedCredentialsEncoding != null) {
-            assertEncodingSupported(storedCredentialsEncoding);
-        }
-        this.storedCredentialsEncoding = storedCredentialsEncoding;
+    public RandomNumberGenerator getRandomNumberGenerator() {
+        return randomNumberGenerator;
     }
 
-    //will probably be removed in Shiro 2.0.  See SHIRO-203:
-    //https://issues.apache.org/jira/browse/SHIRO-203
-    private static final class BytesHelper extends CodecSupport {
-        public byte[] getBytes(Object o) {
-            return toBytes(o);
-        }
+    public void setRandomNumberGenerator(RandomNumberGenerator randomNumberGenerator) {
+        this.randomNumberGenerator = randomNumberGenerator;
     }
 }

Added: shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,81 @@
+/*
+ * 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.authc.credential;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A {@link CredentialsMatcher} that employs best-practices comparisons for hashed text passwords.
+ * <p/>
+ * This implementation delegates to an internal {@link PasswordService} to perform the actual password
+ * comparison.  This class is essentially a bridge between the generic CredentialsMatcher interface and the
+ * more specific {@code PasswordService} component.
+ *
+ * @since 1.2
+ */
+public class PasswordMatcher implements CredentialsMatcher {
+
+    private PasswordService passwordService;
+
+    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+        PasswordService service = ensurePasswordService();
+        ByteSource submittedPassword = getSubmittedPassword(token);
+        String hashedPassword = getStoredHashedPassword(info);
+        return service.passwordsMatch(submittedPassword, hashedPassword);
+    }
+
+    private PasswordService ensurePasswordService() {
+        PasswordService service = getPasswordService();
+        if (service == null) {
+            String msg = "Required PasswordService has not been configured.";
+            throw new IllegalStateException(msg);
+        }
+        return service;
+    }
+
+    protected ByteSource getSubmittedPassword(AuthenticationToken token) {
+        Object credentials = token.getCredentials();
+        if (credentials == null) {
+            return null;
+        }
+        return ByteSource.Util.bytes(credentials);
+    }
+
+    protected String getStoredHashedPassword(AuthenticationInfo storedAccountInfo) {
+        Object credentials = storedAccountInfo.getCredentials();
+        if (credentials == null) {
+            return null;
+        }
+        if (!(credentials instanceof String)) {
+            String msg = "The stored account credentials is expected to be a String representation of a hashed password.";
+            throw new IllegalArgumentException(msg);
+        }
+        return (String)credentials;
+    }
+
+    public PasswordService getPasswordService() {
+        return passwordService;
+    }
+
+    public void setPasswordService(PasswordService passwordService) {
+        this.passwordService = passwordService;
+    }
+}

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java Fri Dec  2 19:52:37 2011
@@ -18,7 +18,6 @@
  */
 package org.apache.shiro.authc.credential;
 
-import org.apache.shiro.crypto.hash.HashResponse;
 import org.apache.shiro.util.ByteSource;
 
 /**
@@ -26,72 +25,112 @@ import org.apache.shiro.util.ByteSource;
  * <p/>
  * Most importantly, implementations of this interface are expected to employ best-practices to ensure that
  * passwords remain as safe as possible in application environments.
- * <p/>
- * As this interface extends the CredentialsMatcher interface, it will perform credentials matching for password-based
- * authentication attempts.  However, this interface includes another additional method,
- * {@link #hashPassword(org.apache.shiro.util.ByteSource)} which will hash a raw password value into a more
- * secure hashed format.
  * <h2>Usage</h2>
- * To use this service effectively, you must do the following:
- * <p/>
- * <ol>
- * <li>Define an implementation of this interface in your Shiro configuration.  For example, in {@code shiro.ini}:
+ * A {@code PasswordService} is used at two different times during an application's lifecycle:
+ * <ul>
+ * <li>When creating a user account or resetting their password</li>
+ * <li>When a user logs in, when passwords must be compared</li>
+ * </ul>
+ * <h3>Account Creation or Password Reset</h3>
+ * Whenever you create a new user account or reset that account's password, we must translate the end-user submitted
+ * raw/plaintext password value to a string format that is much safer to store.  You do that by calling the
+ * {@link #hashPassword(ByteSource)} method to create the safer hashed and formatted value.  For
+ * example:
  * <pre>
- * [main]
+ * ByteSource plaintextBytes = ByteSource.Util.bytes(submittedPlaintextPassword);
+ * String hashed = passwordService.hashPassword(plaintextBytes);
  * ...
- * passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
+ * userAccount.setHashedPassword(hashed);
+ * userAccount.save(); //create or update to your data store
  * </pre>
- * </li>
- * <li>Configure the {@code passwordService} instance with the most secure settings based on your application's needs.
- * See the {@link DefaultPasswordService DefaultPasswordService JavaDoc} for configuration options.  For example:
+ * Be sure to save this hashed password in your data store and never the original/raw submitted password.
+ * <h3>Login Password Comparison</h3>
+ * Shiro performs the comparison during login automatically.  Along with your {@code PasswordService}, you just
+ * have to configure a {@link PasswordMatcher} on a realm that has password-based accounts.   During a login attempt,
+ * shiro will use the {@code PasswordMatcher} and the {@code PasswordService} to automatically compare submitted
+ * passwords.
+ * <p/>
+ * For example, if using Shiro's INI, here is how you might configure the PasswordMatcher and PasswordService:
  * <pre>
+ * [main]
  * ...
- * passwordService.hasher.baseSalt = _some_random_base64_encoded_byte_array_
- * passwordService.hasher.hashIterations = 250000
- * ...
- * </pre>
- * </li>
- * <li>Wire the password service into the {@code Realm} that will query for password-based accounts.  The realm
- * implementation is usually a subclass of {@link org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm}, which
- * supports configuration of a {@link CredentialsMatcher} instance:
- * <pre>
+ * passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
+ * # configure the passwordService to use the hashing settings you desire
  * ...
- * myRealm.credentialsMatcher = $passwordService
+ * passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
+ * passwordMatcher.passwordService = $passwordService
  * ...
+ * # Finally, set the matcher on a realm that requires password matching for account authentication:
+ * myRealm = ...
+ * myRealm.credentialsMatcher = $passwordMatcher
  * </pre>
- * </li>
- * <li>During your application's new-user or password-reset workflow (whenever a user submits to you a new plaintext
- * password), call the {@link #hashPassword(org.apache.shiro.util.ByteSource)} method immediately to acquire the
- * hashed version.  Store the returned {@link org.apache.shiro.crypto.hash.HashResponse#getHash() hash} and
- * {@link org.apache.shiro.crypto.hash.HashResponse#getSalt() salt} instance to your user data store (and <em>NOT</em>
- * the original raw password).</li>
- * <li>Ensure your corresponding Realm implementation (that was configured with the {@code PasswordService} as its
- * credentialsMatcher above) returns instances of
- * {@link org.apache.shiro.authc.SaltedAuthenticationInfo SaltedAuthenticationInfo} during authentication attempts
- * (typically represented by a call to your realm's
- * {@link org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) doGetAuthenticationInfo}
- * method).  Ensure the {@code SaltedAuthenticationInfo} instance you construct returns the saved hash and salt you
- * saved from step #4.</li>
- * </ol>
- * If you perform these steps and configure the {@code PasswordService) appropriately, you can rest assured you will be
- * using very strong password hashing techniques.
  *
+ * @see DefaultPasswordService
+ * @see PasswordMatcher
  * @since 1.2
  */
-public interface PasswordService extends CredentialsMatcher {
+public interface PasswordService {
 
     /**
-     * Hashes the specified plain text password (usually acquired from your application's 'new user' or 'password reset'
-     * workflow).  After this call returns, you typically will store the returned
-     * response's {@link org.apache.shiro.crypto.hash.HashResponse#getHash() hash} and
-     * {@link org.apache.shiro.crypto.hash.HashResponse#getSalt() salt} with the corresponding user record (e.g.
-     * in a database).
+     * Hashes the specified plaintext password (usually acquired from your application's 'new user' or 'password reset'
+     * workflow).  After this call returns, you typically will store the returned formatted String with the
+     * corresponding user record (e.g. as the 'password' or 'passwordHash' attribute).
+     * <p/>
+     * The String returned from this argument must be presented to the
+     * {@link #passwordsMatch(ByteSource, String) passwordsMatch} method when performing a
+     * password comparison check.
+     * <h3>Usage</h3>
+     * The input argument type is a {@code ByteSource} to support either String or character array
+     * {@code (char[])} arguments; character arrays are often a safer way to represent passwords as they can be
+     * cleared/nulled-out after use.
+     * <p/>
+     * Regardless of your choice of using Strings or character arrays to represent submitted passwords, you can wrap
+     * either as a {@code ByteSource} by using {@link ByteSource.Util}, for example, when the passwords are captured as
+     * Strings:
+     * <pre>
+     * ByteSource passwordBytes = ByteSource.Util.bytes(submittedPasswordString);
+     * String formattedHashedValue = passwordService.hashPassword(passwordBytes);
+     * </pre>
+     * or, identically, when captured as a character array:
+     * <pre>
+     * ByteSource passwordBytes = ByteSource.Util.bytes(submittedPasswordCharacterArray);
+     * String formattedHashedValue = passwordService.hashPassword(passwordBytes);
+     * </pre>
+     * <p/>
+     * The resulting {@code formattedHashedValue} should be stored with the account to be retrieved later during a
+     * login attempt.  For example:
+     * <pre>
+     * String formattedHashedValue = passwordService.hashPassword(passwordBytes);
+     * ...
+     * userAccount.setHashedPassword(formattedHashedValue);
+     * userAccount.save(); //create or update to your data store
+     * </pre>
      *
-     * @param plaintextPassword a plain text password, usually acquired from your application's 'new user' or 'password reset'
-     *                          workflow.
-     * @return the password hash and salt to be stored with the corresponding user record.
+     * @param plaintext a {@code ByteSource} encapsulating a plaintext password's bytes, usually acquired from your
+     *                  application's 'new user' or 'password reset' workflow.
+     * @return the hashed password, formatted for storage.
      */
-    HashResponse hashPassword(ByteSource plaintextPassword);
-
+    String hashPassword(ByteSource plaintext);
 
+    /**
+     * Returns {@code true} if the {@code submittedPlaintext} password matches the existing {@code saved} password,
+     * {@code false} otherwise.
+     * <h3>Usage</h3>
+     * The {@code submittedPlaintext} argument is a {@code ByteSource} to support both String and character array
+     * arguments.  Regardless of which you use to capture submitted passwords, you can wrap either as a
+     * {@code ByteSource} as follows:
+     * <pre>
+     * ByteSource submittedPasswordBytes = ByteSource.Util.bytes(submittedPasswordStringOrCharacterArray);
+     * passwordService.passwordsMatch(submittedPasswordBytes, formattedHashedPassword);
+     * </pre>
+     *
+     * @param submittedPlaintext a raw/plaintext password submitted by an end user/Subject.
+     * @param saved              the previously hashed and formatted password known to be associated with an account.
+     *                           This value must have been previously generated from the
+     *                           {@link #hashPassword(ByteSource) hashPassword} method (typically
+     *                           when the account is created or the account's password is reset).
+     * @return {@code true} if the {@code submittedPlaintext} password matches the existing {@code saved} password,
+     *         {@code false} otherwise.
+     */
+    boolean passwordsMatch(ByteSource submittedPlaintext, String saved);
 }

Added: shiro/trunk/core/src/main/java/org/apache/shiro/codec/H64.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/codec/H64.java?rev=1209662&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/codec/H64.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/codec/H64.java Fri Dec  2 19:52:37 2011
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+/*
+ * The apr_md5_encode() routine in the APR project's apr_md5.c file uses much
+ * code obtained from the FreeBSD 3.0 MD5 crypt() function, which is licenced
+ * as follows:
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+package org.apache.shiro.codec;
+
+import java.io.IOException;
+
+/**
+ * Codec for <a href="http://en.wikipedia.org/wiki/Crypt_(Unix)">Unix Crypt</a>-style encoding.  While similar to
+ * Base64, it is not compatible with Base64.
+ * <p/>
+ * This implementation is based on encoding algorithms found in the Apache Portable Runtime library's
+ * <a href="http://svn.apache.org/viewvc/apr/apr/trunk/crypto/apr_md5.c?revision=HEAD&view=markup">apr_md5.c</a>
+ * implementation for its {@code crypt}-style support.  The APR team in turn received inspiration for its encoding
+ * implementation based on FreeBSD 3.0's {@code /usr/src/lib/libcrypt/crypt.c} implementation.  The
+ * accompanying license headers have been retained at the top of this source file.
+ * <p/>
+ * This file and all that it contains is ASL 2.0 compatible.
+ *
+ * @since 1.2
+ */
+public class H64 {
+
+    private static final char[] itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
+
+    private static short toShort(byte b) {
+        return (short) (b & 0xff);
+    }
+
+    private static int toInt(byte[] bytes, int offset, int numBytes) {
+        if (numBytes < 1 || numBytes > 4) {
+            throw new IllegalArgumentException("numBytes must be between 1 and 4.");
+        }
+        int val = toShort(bytes[offset]); //1st byte
+        for (int i = 1; i < numBytes; i++) { //any remaining bytes:
+            short s = toShort(bytes[offset + i]);
+            switch (i) {
+                case 1: val |= s << 8; break;
+                case 2: val |= s << 16; break;
+                case 3: val |= s << 24; break;
+            }
+        }
+        return val;
+    }
+
+    /**
+     * Appends the specified character into the buffer, rethrowing any encountered
+     * {@link IOException} as an {@link IllegalStateException} (since this method is used for internal
+     * implementation needs and we only ever use StringBuilders, we should never encounter an IOException).
+     *
+     * @param buf the buffer to append to
+     * @param c   the character to append.
+     */
+    private static void append(Appendable buf, char c) {
+        try {
+            buf.append(c);
+        } catch (IOException e) {
+            throw new IllegalStateException("Unable to append character to internal buffer.", e);
+        }
+    }
+
+    /**
+     * Encodes the specified integer to {@code numChars} H64-compatible characters and appends them into {@code buf}.
+     *
+     * @param value    the integer to encode to H64-compatible characters
+     * @param buf      the output buffer
+     * @param numChars the number of characters the value should be converted to.  3, 2 or 1.
+     */
+    private static void encodeAndAppend(int value, Appendable buf, int numChars) {
+        for (int i = 0; i < numChars; i++) {
+            append(buf, itoa64[value & 0x3f]);
+            value >>= 6;
+        }
+    }
+
+    /**
+     * Encodes the specified bytes to an {@code H64}-encoded String.
+     *
+     * @param bytes
+     * @return
+     */
+    public static String encodeToString(byte[] bytes) {
+        if (bytes == null || bytes.length == 0) return null;
+
+        StringBuilder buf = new StringBuilder();
+
+        int length = bytes.length;
+        int remainder = length % 3;
+        int i = 0; //starting byte
+        int last3ByteIndex = length - remainder; //last byte whose index is a multiple of 3
+
+        for(; i < last3ByteIndex; i += 3) {
+            int twentyFourBit = toInt(bytes, i, 3);
+            encodeAndAppend(twentyFourBit, buf, 4);
+        }
+        if (remainder > 0) {
+            //one or two bytes that we still need to encode:
+            int a = toInt(bytes, i, remainder);
+            encodeAndAppend(a, buf, remainder + 1);
+        }
+        return buf.toString();
+    }
+}

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/config/ReflectionBuilder.java Fri Dec  2 19:52:37 2011
@@ -22,11 +22,7 @@ import org.apache.commons.beanutils.Bean
 import org.apache.commons.beanutils.PropertyUtils;
 import org.apache.shiro.codec.Base64;
 import org.apache.shiro.codec.Hex;
-import org.apache.shiro.util.ClassUtils;
-import org.apache.shiro.util.CollectionUtils;
-import org.apache.shiro.util.Factory;
-import org.apache.shiro.util.Nameable;
-import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.util.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -361,6 +357,9 @@ public class ReflectionBuilder {
             value = toList(stringValue);
         } else if (isTypedProperty(object, propertyName, byte[].class)) {
             value = toBytes(stringValue);
+        } else if (isTypedProperty(object, propertyName, ByteSource.class)) {
+            byte[] bytes = toBytes(stringValue);
+            value = ByteSource.Util.bytes(bytes);
         } else {
             value = resolveValue(stringValue);
         }

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java Fri Dec  2 19:52:37 2011
@@ -34,14 +34,15 @@ import java.security.SecureRandom;
  */
 public class SecureRandomNumberGenerator implements RandomNumberGenerator {
 
-    protected static final int DEFAULT_NEXT_BYTES_SIZE = 128;
+    protected static final int DEFAULT_NEXT_BYTES_SIZE = 16; //16 bytes == 128 bits (a common number in crypto)
 
     private int defaultNextBytesSize;
     private SecureRandom secureRandom;
 
     /**
      * Creates a new instance with a default backing {@link SecureRandom SecureRandom} and a
-     * {@link #getDefaultNextBytesSize() defaultNextBytesSize} of {@code 128}.
+     * {@link #getDefaultNextBytesSize() defaultNextBytesSize} of {@code 16}, which equals 128 bits, a size commonly
+     * used in cryptographic algorithms.
      */
     public SecureRandomNumberGenerator() {
         this.defaultNextBytesSize = DEFAULT_NEXT_BYTES_SIZE;
@@ -82,7 +83,7 @@ public class SecureRandomNumberGenerator
 
     /**
      * Returns the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.  Defaults to
-     * {@code 128}, a commonly used number in cryptographic algorithms.
+     * {@code 16}, which equals 128 bits, a size commonly used in cryptographic algorithms.
      *
      * @return the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.
      */
@@ -92,7 +93,7 @@ public class SecureRandomNumberGenerator
 
     /**
      * Sets the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}. Defaults to
-     * {@code 128}, a commonly used number in cryptographic algorithms.
+     * {@code 16}, which equals 128 bits, a size commonly used in cryptographic algorithms.
      *
      * @param defaultNextBytesSize the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.
      * @throws IllegalArgumentException if the argument is 0 or negative

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java Fri Dec  2 19:52:37 2011
@@ -288,7 +288,10 @@ public abstract class AbstractHash exten
      * @return toHex().hashCode()
      */
     public int hashCode() {
-        return toHex().hashCode();
+        if (this.bytes == null || this.bytes.length == 0) {
+            return 0;
+        }
+        return Arrays.hashCode(this.bytes);
     }
 
     private static void printMainUsage(Class<? extends AbstractHash> clazz, String type) {

Copied: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java (from r1201985, shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHasher.java)
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java?p2=shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java&p1=shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHasher.java&r1=1201985&r2=1209662&rev=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHasher.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHashService.java Fri Dec  2 19:52:37 2011
@@ -19,20 +19,22 @@
 package org.apache.shiro.crypto.hash;
 
 import org.apache.shiro.crypto.RandomNumberGenerator;
+import org.apache.shiro.util.ByteSource;
 
 /**
- * A {@code Hasher} that allows configuration of its strategy via JavaBeans-compatible setter methods.
+ * A {@code HashService} that allows configuration of its strategy via JavaBeans-compatible setter methods.
  *
  * @since 1.2
  */
-public interface ConfigurableHasher extends Hasher {
+public interface ConfigurableHashService extends HashService {
 
     /**
-     * Sets the 'private' base salt to be paired with a 'public' (random or supplied) salt during hash computation.
+     * Sets the 'private' (internal) salt to be paired with a 'public' (random or supplied) salt during hash computation.
      *
-     * @param baseSalt the 'private' base salt to be paired with a 'public' (random or supplied) salt during hash computation.
+     * @param privateSalt the 'private' internal salt to be paired with a 'public' (random or supplied) salt during
+     *                    hash computation.
      */
-    void setBaseSalt(byte[] baseSalt);
+    void setPrivateSalt(ByteSource privateSalt);
 
     /**
      * Sets the number of hash iterations that will be performed during hash computation.
@@ -45,8 +47,8 @@ public interface ConfigurableHasher exte
      * Sets the name of the {@link java.security.MessageDigest MessageDigest} algorithm that will be used to compute
      * hashes.
      *
-     * @param name the name of the {@link java.security.MessageDigest MessageDigest} algorithm that will be used to compute
-     *             hashes.
+     * @param name the name of the {@link java.security.MessageDigest MessageDigest} algorithm that will be used to
+     *             compute hashes.
      */
     void setHashAlgorithmName(String name);
 

Copied: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java (from r1201985, shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java)
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java?p2=shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java&p1=shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java&r1=1201985&r2=1209662&rev=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHashService.java Fri Dec  2 19:52:37 2011
@@ -23,11 +23,12 @@ import org.apache.shiro.crypto.SecureRan
 import org.apache.shiro.util.ByteSource;
 
 /**
- * Default implementation of the {@link Hasher} interface, supporting secure-random salt generation, an internal
- * private {@link #setBaseSalt(byte[]) baseSalt}, multiple hash iterations and customizable hash algorithm name.
- * <h2>Base Salt</h2>
- * It is <b><em>strongly recommended</em></b> to configure a {@link #setBaseSalt(byte[]) base salt}.
- * Indeed, the {@link Hasher} concept exists largely to support the {@code base salt} concept:
+ * Default implementation of the {@link HashService} interface, supporting a customizable hash algorithm name,
+ * secure-random salt generation, multiple hash iterations and an optional internal
+ * {@link #setPrivateSalt(ByteSource) privateSalt}.
+ * <h2>Private Salt</h2>
+ * If using this implementation as part of a password hashing strategy, it might be desirable to configure a
+ * {@link #setPrivateSalt(ByteSource) private salt}:
  * <p/>
  * A hash and the salt used to compute it are often stored together.  If an attacker is ever able to access
  * the hash (e.g. during password cracking) and it has the full salt value, the attacker has all of the input necessary
@@ -36,34 +37,36 @@ import org.apache.shiro.util.ByteSource;
  * However, if part of the salt is not available to the attacker (because it is not stored with the hash), it is
  * <em>much</em> harder to crack the hash value since the attacker does not have the complete inputs necessary.
  * <p/>
- * The {@link #getBaseSalt() baseSalt} property exists to satisfy this private-and-not-shared part of the salt.  If you
- * configure this attribute, you obtain this additional very important safety feature.
+ * The {@link #getPrivateSalt() privateSalt} property exists to satisfy this private-and-not-shared part of the salt.
+ * If you configure this attribute, you can obtain this additional very important safety feature.
  * <p/>
- * <b>*</b>By default, the {@link #getBaseSalt() baseSalt} is null, since a sensible default cannot be used that isn't
- * easily compromised (because Shiro is an open-source project and any default could be easily seen and used).  It is
- * expected all end-users will want to provide their own.
+ * <b>*</b>By default, the {@link #getPrivateSalt() privateSalt} is null, since a sensible default cannot be used that
+ * isn't easily compromised (because Shiro is an open-source project and any default could be easily seen and used).
  * <h2>Random Salts</h2>
  * When a salt is not specified in a request, this implementation generates secure random salts via its
  * {@link #setRandomNumberGenerator(org.apache.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} property.
- * Random salts (combined with the internal {@link #getBaseSalt() baseSalt}) is the strongest salting strategy,
- * as salts should ideally never be based on known/guessable data.  The default is a
+ * Random salts (and potentially combined with the internal {@link #getPrivateSalt() privateSalt}) is a very strong
+ * salting strategy, as salts should ideally never be based on known/guessable data.  The default instance is a
  * {@link SecureRandomNumberGenerator}.
  * <h2>Password Hash Iterations</h2>
- * The most secure hashing strategy employs multiple hash iterations to slow down the hashing process.  This technique
+ * Secure hashing strategies often employ multiple hash iterations to slow down the hashing process.  This technique
  * is usually used for password hashing, since the longer it takes to compute a password hash, the longer it would
  * take for an attacker to compromise a password.  This
  * <a href="http://www.katasoft.com/blog/2011/04/04/strong-password-hashing-apache-shiro">Katasoft blog article</a>
  * explains in greater detail why this is useful, as well as information on how many iterations is 'enough'.
  * <p/>
  * You may set the number of hash iterations via the {@link #setHashIterations(int)} property.  The default is
- * {@code 1}, but should be increased significantly for password hashing. See the linked blog article for more info.
+ * {@code 1}, but should be increased significantly if the {@code HashService} is intended to be used for password
+ * hashing. See the linked blog article for more info.
  * <h2>Hash Algorithm</h2>
- * You may specify a hash algorithm via the {@link #setHashAlgorithmName(String)} property.  The default is
- * {@code SHA-512}.
+ * You may specify a hash algorithm via the {@link #setHashAlgorithmName(String)} property.  Any algorithm name
+ * understood by the JDK
+ * {@link java.security.MessageDigest#getInstance(String) MessageDigest.getInstance(String algorithmName)} method
+ * will work.  The default is {@code SHA-512}.
  *
  * @since 1.2
  */
-public class DefaultHasher implements ConfigurableHasher {
+public class DefaultHashService implements ConfigurableHashService {
 
     /**
      * The RandomNumberGenerator to use to randomly generate the public part of the hash salt.
@@ -78,7 +81,7 @@ public class DefaultHasher implements Co
     /**
      * The 'private' part of the hash salt.
      */
-    private byte[] baseSalt;
+    private ByteSource privateSalt;
 
     /**
      * The number of hash iterations to perform when computing hashes.
@@ -86,21 +89,28 @@ public class DefaultHasher implements Co
     private int iterations;
 
     /**
-     * Constructs a new {@code DefaultHasher} instance with the following defaults:
+     * Whether or not to generate public salts if a request does not provide one.
+     */
+    private boolean generatePublicSalt;
+
+    /**
+     * Constructs a new {@code DefaultHashService} instance with the following defaults:
      * <ul>
      * <li>{@link #setHashAlgorithmName(String) hashAlgorithmName} = {@code SHA-512}</li>
      * <li>{@link #setHashIterations(int) hashIterations} = {@code 1}</li>
      * <li>{@link #setRandomNumberGenerator(org.apache.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} =
-     * {@link SecureRandomNumberGenerator}</li>
+     * new {@link SecureRandomNumberGenerator}()</li>
+     * <li>{@link #setGeneratePublicSalt(boolean) generatePublicSalt} = {@code false}</li>
      * </ul>
      * <p/>
-     * If this hasher will be used for password hashing it is <b><em>strongly recommended</em></b> to set the
-     * {@link #setBaseSalt(byte[]) baseSalt} and significantly increase the number of
+     * If this hashService will be used for password hashing it is recommended to set the
+     * {@link #setPrivateSalt(ByteSource) privateSalt} and significantly increase the number of
      * {@link #setHashIterations(int) hashIterations}.  See the class-level JavaDoc for more information.
      */
-    public DefaultHasher() {
+    public DefaultHashService() {
         this.algorithmName = "SHA-512";
         this.iterations = 1;
+        this.generatePublicSalt = false;
         this.rng = new SecureRandomNumberGenerator();
     }
 
@@ -114,29 +124,30 @@ public class DefaultHasher implements Co
      * A salt will be generated and used to compute the hash.  The salt is generated as follows:
      * <ol>
      * <li>Use the {@link #getRandomNumberGenerator() randomNumberGenerator} to generate a new random number.</li>
-     * <li>{@link #combine(byte[], byte[]) combine} this random salt with any configured {@link #getBaseSalt() baseSalt}
+     * <li>{@link #combine(ByteSource, ByteSource) combine} this random salt with any configured
+     * {@link #getPrivateSalt() privateSalt}
      * </li>
      * <li>Use the combined value as the salt used during hash computation</li>
      * </ol>
      * </li>
      * <li>
-     * If the request's salt is not null:
+     * If the request salt is not null:
      * <p/>
      * This indicates that the hash computation is for comparison purposes (of a
-     * previously computed hash).  The request salt will be {@link #combine(byte[], byte[]) combined} with any
-     * configured {@link #getBaseSalt() baseSalt} and used as the complete salt during hash computation.
+     * previously computed hash).  The request salt will be {@link #combine(ByteSource, ByteSource) combined} with any
+     * configured {@link #getPrivateSalt() privateSalt} and used as the complete salt during hash computation.
      * </li>
      * </ul>
      * <p/>
-     * The returned {@code HashResponse}'s {@link org.apache.shiro.crypto.hash.HashResponse#getSalt() salt} property
-     * will contain <em>only</em> the 'public' part of the salt and <em>NOT</em> the baseSalt.  See the class-level JavaDoc
-     * explanation for more info.
+     * The returned {@code Hash}'s {@link Hash#getSalt() salt} property
+     * will contain <em>only</em> the 'public' part of the salt and <em>NOT</em> the privateSalt.  See the class-level
+     * JavaDoc explanation for more info.
      *
      * @param request the request to process
      * @return the response containing the result of the hash computation, as well as any hash salt used that should be
      *         exposed to the caller.
      */
-    public HashResponse computeHash(HashRequest request) {
+    public Hash computeHash(HashRequest request) {
         if (request == null) {
             return null;
         }
@@ -148,55 +159,117 @@ public class DefaultHasher implements Co
         }
 
         ByteSource requestSalt = request.getSalt();
-        byte[] publicSaltBytes = requestSalt != null ? requestSalt.getBytes() : null;
-        if (publicSaltBytes != null && publicSaltBytes.length == 0) {
-            publicSaltBytes = null;
+        ByteSource publicSalt = requestSalt != null ? requestSalt : null;
+        if (requestSalt != null && (requestSalt.getBytes() == null || requestSalt.getBytes().length == 0)) {
+            publicSalt = null;
         }
-        if (publicSaltBytes == null) {
-        	publicSaltBytes = getRandomNumberGenerator().nextBytes().getBytes();
+
+        if (publicSalt == null) {
+            publicSalt = getRandomNumberGenerator().nextBytes();
         }
 
         String algorithmName = getHashAlgorithmName();
-        byte[] baseSalt = getBaseSalt();
-        byte[] saltBytes = combine(baseSalt, publicSaltBytes);
+        ByteSource privateSalt = getPrivateSalt();
+        ByteSource combinedSalt = combine(privateSalt, publicSalt);
         int iterations = Math.max(1, getHashIterations());
 
-        Hash result = new SimpleHash(algorithmName, sourceBytes, saltBytes, iterations);
-        ByteSource publicSalt = ByteSource.Util.bytes(publicSaltBytes);
+        Hash computed = new SimpleHash(algorithmName, sourceBytes, combinedSalt, iterations);
+
+        SimpleHash result = new SimpleHash(algorithmName);
+        result.setBytes(computed.getBytes());
+        result.setIterations(iterations);
+        //Only expose the public salt - not the real/combined salt that was used:
+        result.setSalt(publicSalt);
 
-        return new SimpleHashResponse(result, publicSalt);
+        return result;
     }
 
+
     /**
-     * Combines the specified 'private' base salt bytes with the specified additional extra bytes to use as the
-     * total salt during hash computation.  {@code baseSaltBytes} will be {@code null} }if no base salt has been configured.
+     * Returns the public salt that should be used to compute a hash based on the specified request or
+     * {@code null} if no public salt should be used.
+     * <p/>
+     * This implementation functions as follows:
+     * <ol>
+     * <li>If the request salt is not null and non-empty, this will be used, return it.</li>
+     * <li>If the request salt is null or empty:
+     * <ol>
+     * <li>If a private salt has been set <em>OR</em> {@link #isGeneratePublicSalt()} is {@code true},
+     * auto generate a random public salt via the configured
+     * {@link #getRandomNumberGenerator() randomNumberGenerator}.</li>
+     * <li>If a private salt has not been configured and {@link #isGeneratePublicSalt()} is {@code false},
+     * do nothing - return {@code null} to indicate a salt should not be used during hash computation.</li>
+     * </ol>
+     * </li>
+     * </ol>
      *
-     * @param baseSaltBytes the (possibly {@code null}) 'private' base salt to combine with the specified extra bytes
-     * @param extraBytes the extra bytes to use in addition to the gien base salt bytes.
-     * @return a combination of the specified base salt bytes and extra bytes that will be used as the total
-     * salt during hash computation.
-     */
-    protected byte[] combine(byte[] baseSaltBytes, byte[] extraBytes) {
-        int baseSaltLength = baseSaltBytes != null ? baseSaltBytes.length : 0;
-        int randomBytesLength = extraBytes != null ? extraBytes.length : 0;
+     * @param request request the request to process
+     * @return the public salt that should be used to compute a hash based on the specified request or
+     *         {@code null} if no public salt should be used.
+     */
+    protected ByteSource getPublicSalt(HashRequest request) {
+
+        ByteSource publicSalt = request.getSalt();
+
+        if (publicSalt != null && !publicSalt.isEmpty()) {
+            //a public salt was explicitly requested to be used - go ahead and use it:
+            return publicSalt;
+        }
+
+        publicSalt = null;
+
+        //check to see if we need to generate one:
+        ByteSource privateSalt = getPrivateSalt();
+        boolean privateSaltExists = privateSalt != null && !privateSalt.isEmpty();
+
+        //If a private salt exists, we must generate a public salt to protect the integrity of the private salt.
+        //Or generate it if the instance is explicitly configured to do so:
+        if (privateSaltExists || isGeneratePublicSalt()) {
+            publicSalt = getRandomNumberGenerator().nextBytes();
+        }
+
+        return publicSalt;
+    }
+
+    /**
+     * Combines the specified 'private' salt bytes with the specified additional extra bytes to use as the
+     * total salt during hash computation.  {@code privateSaltBytes} will be {@code null} }if no private salt has been
+     * configured.
+     *
+     * @param privateSalt the (possibly {@code null}) 'private' salt to combine with the specified extra bytes
+     * @param publicSalt  the extra bytes to use in addition to the given private salt.
+     * @return a combination of the specified private salt bytes and extra bytes that will be used as the total
+     *         salt during hash computation.
+     */
+    protected ByteSource combine(ByteSource privateSalt, ByteSource publicSalt) {
+
+        byte[] privateSaltBytes = privateSalt != null ? privateSalt.getBytes() : null;
+        int privateSaltLength = privateSaltBytes != null ? privateSaltBytes.length : 0;
+
+        byte[] publicSaltBytes = publicSalt != null ? publicSalt.getBytes() : null;
+        int extraBytesLength = publicSaltBytes != null ? publicSaltBytes.length : 0;
+
+        int length = privateSaltLength + extraBytesLength;
+
+        if (length <= 0) {
+            return null;
+        }
 
-        int length = baseSaltLength + randomBytesLength;
         byte[] combined = new byte[length];
 
         int i = 0;
-        for (int j = 0; j < baseSaltLength; j++) {
-            assert baseSaltBytes != null;
-            combined[i++] = baseSaltBytes[j];
+        for (int j = 0; j < privateSaltLength; j++) {
+            assert privateSaltBytes != null;
+            combined[i++] = privateSaltBytes[j];
         }
-        for (int j = 0; j < randomBytesLength; j++) {
-            assert extraBytes != null;
-            combined[i++] = extraBytes[j];
+        for (int j = 0; j < extraBytesLength; j++) {
+            assert publicSaltBytes != null;
+            combined[i++] = publicSaltBytes[j];
         }
 
-        return combined;
+        return ByteSource.Util.bytes(combined);
     }
 
-
     public void setHashAlgorithmName(String name) {
         this.algorithmName = name;
     }
@@ -205,12 +278,12 @@ public class DefaultHasher implements Co
         return this.algorithmName;
     }
 
-    public void setBaseSalt(byte[] baseSalt) {
-        this.baseSalt = baseSalt;
+    public void setPrivateSalt(ByteSource privateSalt) {
+        this.privateSalt = privateSalt;
     }
 
-    public byte[] getBaseSalt() {
-        return this.baseSalt;
+    public ByteSource getPrivateSalt() {
+        return this.privateSalt;
     }
 
     public void setHashIterations(int count) {
@@ -228,4 +301,42 @@ public class DefaultHasher implements Co
     public RandomNumberGenerator getRandomNumberGenerator() {
         return this.rng;
     }
+
+    /**
+     * Returns {@code true} if a public salt should be randomly generated and used to compute a hash if a
+     * {@link HashRequest} does not specify a salt, {@code false} otherwise.
+     * <p/>
+     * The default value is {@code false} but should definitely be set to {@code true} if the
+     * {@code HashService} instance is being used for password hashing.
+     * <p/>
+     * <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured.  If a
+     * private salt has been configured and a request does not provide a salt, a random salt will always be generated
+     * to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is,
+     * which is undesirable).
+     *
+     * @return {@code true} if a public salt should be randomly generated and used to compute a hash if a
+     *         {@link HashRequest} does not specify a salt, {@code false} otherwise.
+     */
+    public boolean isGeneratePublicSalt() {
+        return generatePublicSalt;
+    }
+
+    /**
+     * Sets whether or not a public salt should be randomly generated and used to compute a hash if a
+     * {@link HashRequest} does not specify a salt.
+     * <p/>
+     * The default value is {@code false} but should definitely be set to {@code true} if the
+     * {@code HashService} instance is being used for password hashing.
+     * <p/>
+     * <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured.  If a
+     * private salt has been configured and a request does not provide a salt, a random salt will always be generated
+     * to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is,
+     * which is undesirable).
+     *
+     * @param generatePublicSalt whether or not a public salt should be randomly generated and used to compute a hash
+     *                           if a {@link HashRequest} does not specify a salt.
+     */
+    public void setGeneratePublicSalt(boolean generatePublicSalt) {
+        this.generatePublicSalt = generatePublicSalt;
+    }
 }

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java Fri Dec  2 19:52:37 2011
@@ -26,9 +26,8 @@ import org.apache.shiro.util.ByteSource;
  * inherited {@link #toHex() toHex()} and {@link #toBase64() toBase64()} methods.
  * <p/>
  * The bytes returned by the parent interface's {@link #getBytes() getBytes()} are the hashed value of the
- * original input source.
+ * original input source, also known as the 'checksum' or 'digest'.
  *
- * @see AbstractHash
  * @see Md2Hash
  * @see Md5Hash
  * @see Sha1Hash
@@ -48,4 +47,21 @@ public interface Hash extends ByteSource
      * @since 1.1
      */
     String getAlgorithmName();
+
+    /**
+     * Returns a salt used to compute the hash or {@code null} if no salt was used.
+     *
+     * @return a salt used to compute the hash or {@code null} if no salt was used.
+     * @since 1.2
+     */
+    ByteSource getSalt();
+
+    /**
+     * Returns the number of hash iterations used to compute the hash.
+     *
+     * @return the number of hash iterations used to compute the hash.
+     * @since 1.2
+     */
+    int getIterations();
+
 }

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java?rev=1209662&r1=1209661&r2=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java Fri Dec  2 19:52:37 2011
@@ -21,33 +21,205 @@ package org.apache.shiro.crypto.hash;
 import org.apache.shiro.util.ByteSource;
 
 /**
- * A {@code HashRequest} is composed of data that will be used to create a hash by a {@link Hasher}.
+ * A {@code HashRequest} is composed of data that will be used by a {@link HashService} to compute a hash (aka
+ * 'digest').  While you can instantiate a concrete {@code HashRequest} class directly, most will find using the
+ * {@link HashRequest.Builder} more convenient.
  *
- * @see SimpleHashRequest
+ * @see HashRequest.Builder
  * @since 1.2
  */
 public interface HashRequest {
 
     /**
-     * Returns the source data that will be hashed by a {@link Hasher}.
+     * Returns the source data that will be hashed by a {@link HashService}. For example, this might be a
+     * {@code ByteSource} representation of a password, or file, etc.
      *
-     * @return the source data that will be hashed by a {@link Hasher}.
+     * @return the source data that will be hashed by a {@link HashService}.
      */
     ByteSource getSource();
 
     /**
-     * Returns a salt to be used by the {@link Hasher} during hash computation, or {@code null} if no salt is provided
-     * as part of the request.
+     * Returns a salt to be used by the {@link HashService} during hash computation, or {@code null} if no salt is
+     * provided as part of the request.
      * <p/>
-     * Note that a {@code null} return value does not necessarily mean a salt won't be used at all - it just
-     * means that the request didn't include one.  The servicing {@link Hasher} is free to provide a salting
+     * Note that a {@code null} value does not necessarily mean a salt won't be used at all - it just
+     * means that the request didn't include one.  The servicing {@link HashService} is free to provide a salting
      * strategy for a request, even if the request did not specify one.
+     *
+     * @return a salt to be used by the {@link HashService} during hash computation, or {@code null} if no salt is
+     *         provided as part of the request.
+     */
+    ByteSource getSalt();
+
+    /**
+     * Returns the number of requested hash iterations to be performed when computing the final {@code Hash} result.
+     * A non-positive (0 or less) indicates that the {@code HashService}'s default iteration configuration should
+     * be used.  A positive value overrides the {@code HashService}'s configuration for a single request.
      * <p/>
-     * <b>NOTE:</b> if
+     * Note that a {@code HashService} is free to ignore this number if it determines the number is not sufficient
+     * to meet a desired level of security.
      *
-     * @return a salt to be used by the {@link Hasher} during hash computation, or {@code null} if no salt is provided
-     *         as part of the request.
+     * @return the number of requested hash iterations to be performed when computing the final {@code Hash} result.
      */
+    int getIterations();
 
-    ByteSource getSalt();
+    /**
+     * Returns the name of the hash algorithm the {@code HashService} should use when computing the {@link Hash}, or
+     * {@code null} if the default algorithm configuration of the {@code HashService} should be used.  A non-null value
+     * overrides the {@code HashService}'s configuration for a single request.
+     * <p/>
+     * Note that a {@code HashService} is free to ignore this value if it determines that the algorithm is not
+     * sufficient to meet a desired level of security.
+     *
+     * @return the name of the hash algorithm the {@code HashService} should use when computing the {@link Hash}, or
+     *         {@code null} if the default algorithm configuration of the {@code HashService} should be used.
+     */
+    String getAlgorithmName();
+
+    /**
+     * A Builder class representing the Builder design pattern for constructing {@link HashRequest} instances.
+     *
+     * @see SimpleHashRequest
+     * @since 1.2
+     */
+    public static class Builder {
+
+        private ByteSource source;
+        private ByteSource salt;
+        private int iterations;
+        private String algorithmName;
+
+        /**
+         * Default no-arg constructor.
+         */
+        public Builder() {
+            this.iterations = 0;
+        }
+
+        /**
+         * Sets the source data that will be hashed by a {@link HashService}. For example, this might be a
+         * {@code ByteSource} representation of a password, or file, etc.
+         *
+         * @param source the source data that will be hashed by a {@link HashService}.
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getSource()
+         * @see #setSource(Object)
+         */
+        public Builder setSource(ByteSource source) {
+            this.source = source;
+            return this;
+        }
+
+        /**
+         * Sets the source data that will be hashed by a {@link HashService}.
+         * <p/>
+         * This is a convenience alternative to {@link #setSource(ByteSource)}: it will attempt to convert the
+         * argument into a {@link ByteSource} instance using Shiro's default conversion heuristics
+         * (as defined by {@link ByteSource.Util#isCompatible(Object) ByteSource.Util.isCompatible}.  If the object
+         * cannot be heuristically converted to a {@code ByteSource}, an {@code IllegalArgumentException} will be
+         * thrown.
+         *
+         * @param source the byte-backed source data that will be hashed by a {@link HashService}.
+         * @return this {@code Builder} instance for method chaining.
+         * @throws IllegalArgumentException if the argument cannot be heuristically converted to a {@link ByteSource}
+         *                                  instance.
+         * @see HashRequest#getSource()
+         * @see #setSource(ByteSource)
+         */
+        public Builder setSource(Object source) throws IllegalArgumentException {
+            this.source = ByteSource.Util.bytes(source);
+            return this;
+        }
+
+        /**
+         * Sets a salt to be used by the {@link HashService} during hash computation.
+         * <p/>
+         * <b>NOTE</b>: not calling this method does not necessarily mean a salt won't be used at all - it just
+         * means that the request didn't include a salt.  The servicing {@link HashService} is free to provide a salting
+         * strategy for a request, even if the request did not specify one.  You can always check the result
+         * {@code Hash} {@link Hash#getSalt() getSalt()} method to see what the actual
+         * salt was (if any), which may or may not match this request salt.
+         *
+         * @param salt a salt to be used by the {@link HashService} during hash computation
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getSalt()
+         */
+        public Builder setSalt(ByteSource salt) {
+            this.salt = salt;
+            return this;
+        }
+
+        /**
+         * Sets a salt to be used by the {@link HashService} during hash computation.
+         * <p/>
+         * This is a convenience alternative to {@link #setSalt(ByteSource)}: it will attempt to convert the
+         * argument into a {@link ByteSource} instance using Shiro's default conversion heuristics
+         * (as defined by {@link ByteSource.Util#isCompatible(Object) ByteSource.Util.isCompatible}.  If the object
+         * cannot be heuristically converted to a {@code ByteSource}, an {@code IllegalArgumentException} will be
+         * thrown.
+         *
+         * @param salt a salt to be used by the {@link HashService} during hash computation.
+         * @return this {@code Builder} instance for method chaining.
+         * @throws IllegalArgumentException if the argument cannot be heuristically converted to a {@link ByteSource}
+         *                                  instance.
+         * @see #setSalt(ByteSource)
+         * @see HashRequest#getSalt()
+         */
+        public Builder setSalt(Object salt) throws IllegalArgumentException {
+            this.salt = ByteSource.Util.bytes(salt);
+            return this;
+        }
+
+        /**
+         * Sets the number of requested hash iterations to be performed when computing the final {@code Hash} result.
+         * Not calling this method or setting a non-positive value (0 or less) indicates that the {@code HashService}'s
+         * default iteration configuration should be used.  A positive value overrides the {@code HashService}'s
+         * configuration for a single request.
+         * <p/>
+         * Note that a {@code HashService} is free to ignore this number if it determines the number is not sufficient
+         * to meet a desired level of security. You can always check the result
+         * {@code Hash} {@link Hash#getIterations() getIterations()} method to see what the actual
+         * number of iterations was, which may or may not match this request salt.
+         *
+         * @param iterations the number of requested hash iterations to be performed when computing the final
+         *                   {@code Hash} result.
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getIterations()
+         */
+        public Builder setIterations(int iterations) {
+            this.iterations = iterations;
+            return this;
+        }
+
+        /**
+         * Sets the name of the hash algorithm the {@code HashService} should use when computing the {@link Hash}.
+         * Not calling this method or setting it to {@code null} indicates the the default algorithm configuration of
+         * the {@code HashService} should be used.  A non-null value
+         * overrides the {@code HashService}'s configuration for a single request.
+         * <p/>
+         * Note that a {@code HashService} is free to ignore this value if it determines that the algorithm is not
+         * sufficient to meet a desired level of security. You can always check the result
+         * {@code Hash} {@link Hash#getAlgorithmName() getAlgorithmName()} method to see what the actual
+         * algorithm was, which may or may not match this request salt.
+         *
+         * @param algorithmName the name of the hash algorithm the {@code HashService} should use when computing the
+         *                      {@link Hash}, or {@code null} if the default algorithm configuration of the
+         *                      {@code HashService} should be used.
+         * @return this {@code Builder} instance for method chaining.
+         * @see HashRequest#getAlgorithmName()
+         */
+        public Builder setAlgorithmName(String algorithmName) {
+            this.algorithmName = algorithmName;
+            return this;
+        }
+
+        /**
+         * Builds a {@link HashRequest} instance reflecting the specified configuration.
+         *
+         * @return a {@link HashRequest} instance reflecting the specified configuration.
+         */
+        public HashRequest build() {
+            return new SimpleHashRequest(this.algorithmName, this.source, this.salt, this.iterations);
+        }
+    }
 }

Copied: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java (from r1201985, shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hasher.java)
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java?p2=shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java&p1=shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hasher.java&r1=1201985&r2=1209662&rev=1209662&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hasher.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashService.java Fri Dec  2 19:52:37 2011
@@ -19,9 +19,9 @@
 package org.apache.shiro.crypto.hash;
 
 /**
- * A {@code Hasher} hashes input sources utilizing a particular hashing strategy.
+ * A {@code HashService} hashes input sources utilizing a particular hashing strategy.
  * <p/>
- * A {@code Hasher} sits at a higher architectural level than Shiro's simple {@link Hash} classes:  it allows
+ * A {@code HashService} sits at a higher architectural level than Shiro's simple {@link Hash} classes:  it allows
  * for salting and iteration-related strategies to be configured and internalized in a
  * single component that can be re-used in multiple places in the application.
  * <p/>
@@ -32,36 +32,46 @@ package org.apache.shiro.crypto.hash;
  * quickly lead to copy-and-paste behavior.  For example, consider this logic which might need to repeated in an
  * application:
  * <pre>
- * byte[] applicationSalt = ...
- * byte[] randomSalt = {@link org.apache.shiro.crypto.RandomNumberGenerator randomNumberGenerator}.nextBytes().getBytes();
- * byte[] combined = combine(applicationSalt, randomSalt);
- * ByteSource hash = Sha512Hash(source, combined, numHashIterations);
- * ByteSource salt = new SimpleByteSource(combined);
- * save(hash, salt);
+ * int numHashIterations = ...
+ * ByteSource privateSalt = ...
+ * ByteSource randomSalt = {@link org.apache.shiro.crypto.RandomNumberGenerator randomNumberGenerator}.nextBytes();
+ * ByteSource combined = combine(privateSalt, randomSalt);
+ * Hash hash = Sha512Hash(source, combined, numHashIterations);
+ * save(hash);
  * </pre>
  * In this example, often only the input source will change during runtime, while the hashing strategy (how salts
  * are generated or acquired, how many hash iterations will be performed, etc) usually remain consistent.  A HashService
  * internalizes this logic so the above becomes simply this:
  * <pre>
- * HashResponse response = hasher.hash(source);
- * save(response.getHash(), response.getSalt());
+ * HashRequest request = new HashRequest.Builder().source(source).build();
+ * Hash result = hashService.hash(request);
+ * save(result);
  * </pre>
  *
  * @since 1.2
  */
-public interface Hasher {
+public interface HashService {
 
     /**
      * Computes a hash based on the given request.
+     *
+     * <h3>Salt Notice</h3>
+     *
+     * If a salt accompanies the return value
+     * (i.e. <code>returnedHash.{@link org.apache.shiro.crypto.hash.Hash#getSalt() getSalt()} != null</code>), this
+     * same exact salt <b><em>MUST</em></b> be presented back to the {@code HashService} if hash
+     * comparison/verification will be performed at a later time (for example, for password hash or file checksum
+     * comparison).
      * <p/>
-     * Note that the response data may not be the same as what would have been achieved by using a {@link Hash}
-     * implementation directly.  See the
-     * {@link org.apache.shiro.crypto.hash.HashResponse#getSalt() HashResponse.getSalt()} JavaDoc for more information.
+     * For additional security, the {@code HashService}'s internal implementation may use more complex salting
+     * strategies than what would be achieved by computing a {@code Hash} manually.
+     * <p/>
+     * In summary, if a {@link HashService} returns a salt in a returned Hash, it is expected that the same salt
+     * will be provided to the same {@code HashService} instance.
      *
      * @param request the request to process
-     * @return the hashed data as a {@code HashResponse}
-     * @see org.apache.shiro.crypto.hash.HashResponse#getSalt()
+     * @return the hashed data
+     * @see Hash#getSalt()
      */
-    HashResponse computeHash(HashRequest request);
-
+    Hash computeHash(HashRequest request);
 }



Mime
View raw message