You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2010/10/20 05:15:07 UTC

svn commit: r1024510 - in /shiro/trunk/core/src: main/java/org/apache/shiro/authc/ main/java/org/apache/shiro/authc/credential/ main/java/org/apache/shiro/crypto/ main/java/org/apache/shiro/crypto/hash/ main/java/org/apache/shiro/util/ test/java/org/ap...

Author: lhazlewood
Date: Wed Oct 20 03:15:06 2010
New Revision: 1024510

URL: http://svn.apache.org/viewvc?rev=1024510&view=rev
Log:
SHIRO-186 - initial implementation complete.  Up for peer review

Added:
    shiro/trunk/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java
    shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java   (contents, props changed)
      - copied, changed from r1022243, shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java
Modified:
    shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java
    shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java
    shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java
    shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hash.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/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java
    shiro/trunk/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java

Added: shiro/trunk/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java?rev=1024510&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/authc/SaltedAuthenticationInfo.java Wed Oct 20 03:15:06 2010
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * Interface representing account information that may use a salt when hashing credentials.  This interface
+ * exists primarily to support environments that hash user credentials (e.g. passwords).
+ * <p/>
+ * Salts should typically be generated from a secure pseudo-random number generator so they are effectively
+ * impossible to guess.  The salt value should be safely stored along side the account information to ensure
+ * it is maintained along with the account's credentials.
+ * <p/>
+ * This interface exists as a way for Shiro to acquire that salt so it can correctly perform
+ * {@link org.apache.shiro.authc.credential.CredentialsMatcher credentials matching} during login attempts.
+ * See the {@link org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher} JavaDoc for
+ * more information on hashing credentials with salts.
+ *
+ * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher
+ *
+ * @since 1.1
+ */
+public interface SaltedAuthenticationInfo extends AuthenticationInfo {
+
+    /**
+     * Returns the salt used to salt the account's credentials or {@code null} if no salt was used.
+     *
+     * @return the salt used to salt the account's credentials or {@code null} if no salt was used.
+     */
+    ByteSource getCredentialsSalt();
+}

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAccount.java Wed Oct 20 03:15:06 2010
@@ -22,6 +22,7 @@ import org.apache.shiro.authz.Permission
 import org.apache.shiro.authz.SimpleAuthorizationInfo;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.ByteSource;
 
 import java.io.Serializable;
 import java.util.Collection;
@@ -35,7 +36,7 @@ import java.util.Set;
  *
  * @since 0.1
  */
-public class SimpleAccount implements Account, MergableAuthenticationInfo, Serializable {
+public class SimpleAccount implements Account, MergableAuthenticationInfo, SaltedAuthenticationInfo, Serializable {
 
     /*--------------------------------------------
     |    I N S T A N C E   V A R I A B L E S    |
@@ -65,6 +66,7 @@ public class SimpleAccount implements Ac
     /*--------------------------------------------
     |         C O N S T R U C T O R S           |
     ============================================*/
+
     /**
      * Default no-argument constructor.
      */
@@ -83,6 +85,22 @@ public class SimpleAccount implements Ac
     }
 
     /**
+     * Constructs a SimpleAccount instance for the specified realm with the given principals, hashedCredentials and
+     * credentials salt used when hashing the credentials.
+     *
+     * @param principal         the 'primary' identifying attribute of the account, for example, a user id or username.
+     * @param hashedCredentials the credentials that verify identity for the account
+     * @param credentialsSalt   the salt used when hashing the credentials
+     * @param realmName         the name of the realm that accesses this account data
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAccount(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
+        this(principal instanceof PrincipalCollection ? (PrincipalCollection) principal : new SimplePrincipalCollection(principal, realmName),
+                hashedCredentials, credentialsSalt);
+    }
+
+    /**
      * Constructs a SimpleAccount instance for the specified realm with the given principals and credentials.
      *
      * @param principals  the identifying attributes of the account, at least one of which should be considered the
@@ -107,6 +125,21 @@ public class SimpleAccount implements Ac
     }
 
     /**
+     * Constructs a SimpleAccount instance for the specified principals and credentials.
+     *
+     * @param principals        the identifying attributes of the account, at least one of which should be considered the
+     *                          account's 'primary' identifying attribute, for example, a user id or username.
+     * @param hashedCredentials the hashed credentials that verify identity for the account
+     * @param credentialsSalt   the salt used when hashing the credentials
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAccount(PrincipalCollection principals, Object hashedCredentials, ByteSource credentialsSalt) {
+        this.authcInfo = new SimpleAuthenticationInfo(principals, hashedCredentials, credentialsSalt);
+        this.authzInfo = new SimpleAuthorizationInfo();
+    }
+
+    /**
      * Constructs a SimpleAccount instance for the specified principals and credentials, with the assigned roles.
      *
      * @param principals  the identifying attributes of the account, at least one of which should be considered the
@@ -216,6 +249,30 @@ public class SimpleAccount implements Ac
     }
 
     /**
+     * Returns the salt used to hash this Account's credentials (eg for password hashing), or {@code null} if no salt
+     * was used or credentials were not hashed at all.
+     *
+     * @return the salt used to hash this Account's credentials (eg for password hashing), or {@code null} if no salt
+     *         was used or credentials were not hashed at all.
+     * @since 1.1
+     */
+    public ByteSource getCredentialsSalt() {
+        return this.authcInfo.getCredentialsSalt();
+    }
+
+    /**
+     * Sets the salt to use to hash this Account's credentials (eg for password hashing), or {@code null} if no salt
+     * is used or credentials are not hashed at all.
+     *
+     * @param salt the salt to use to hash this Account's credentials (eg for password hashing), or {@code null} if no
+     *             salt is used or credentials are not hashed at all.
+     * @since 1.1
+     */
+    public void setSalt(ByteSource salt) {
+        this.authcInfo.setSalt(salt);
+    }
+
+    /**
      * Returns <code>this.authzInfo.getRoles();</code>
      *
      * @return the Account's assigned roles.

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/authc/SimpleAuthenticationInfo.java Wed Oct 20 03:15:06 2010
@@ -21,6 +21,7 @@ package org.apache.shiro.authc;
 import org.apache.shiro.subject.MutablePrincipalCollection;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.ByteSource;
 
 import java.util.Collection;
 import java.util.HashSet;
@@ -34,7 +35,7 @@ import java.util.Set;
  * @see org.apache.shiro.realm.AuthenticatingRealm
  * @since 0.9
  */
-public class SimpleAuthenticationInfo implements MergableAuthenticationInfo {
+public class SimpleAuthenticationInfo implements MergableAuthenticationInfo, SaltedAuthenticationInfo {
 
     /**
      * The principals identifying the account associated with this AuthenticationInfo instance.
@@ -46,6 +47,13 @@ public class SimpleAuthenticationInfo im
     protected Object credentials;
 
     /**
+     * Any salt used in hashing the credentials.
+     *
+     * @since 1.1
+     */
+    protected ByteSource salt;
+
+    /**
      * Default no-argument constructor.
      */
     public SimpleAuthenticationInfo() {
@@ -56,7 +64,7 @@ public class SimpleAuthenticationInfo im
      * associated with the specified realm.
      * <p/>
      * This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based
-     * on the <code>principal</code> and <code>realmName</code> argument.
+     * on the {@code principal} and {@code realmName} argument.
      *
      * @param principal   the 'primary' principal associated with the specified realm.
      * @param credentials the credentials that verify the given principal.
@@ -68,6 +76,26 @@ public class SimpleAuthenticationInfo im
     }
 
     /**
+     * Constructor that takes in a single 'primary' principal of the account, its corresponding hashed credentials,
+     * the salt used to hash the credentials, and the name of the realm to associate with the principals.
+     * <p/>
+     * This is a convenience constructor and will construct a {@link PrincipalCollection PrincipalCollection} based
+     * on the <code>principal</code> and <code>realmName</code> argument.
+     *
+     * @param principal         the 'primary' principal associated with the specified realm.
+     * @param hashedCredentials the hashed credentials that verify the given principal.
+     * @param credentialsSalt   the salt used when hashing the given hashedCredentials
+     * @param realmName         the realm from where the principal and credentials were acquired.
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
+        this.principals = new SimplePrincipalCollection(principal, realmName);
+        this.credentials = hashedCredentials;
+        this.salt = credentialsSalt;
+    }
+
+    /**
      * Constructor that takes in an account's identifying principal(s) and its corresponding credentials that verify
      * the principals.
      *
@@ -79,6 +107,22 @@ public class SimpleAuthenticationInfo im
         this.credentials = credentials;
     }
 
+    /**
+     * Constructor that takes in an account's identifying principal(s), hashed credentials used to verify the
+     * principals, and the salt used when hashing the credentials.
+     *
+     * @param principals        a Realm's account's identifying principal(s)
+     * @param hashedCredentials the hashed credentials that verify the principals.
+     * @param credentialsSalt   the salt used when hashing the hashedCredentials.
+     * @see org.apache.shiro.authc.credential.HashedCredentialsMatcher HashedCredentialsMatcher
+     * @since 1.1
+     */
+    public SimpleAuthenticationInfo(PrincipalCollection principals, Object hashedCredentials, ByteSource credentialsSalt) {
+        this.principals = new SimplePrincipalCollection(principals);
+        this.credentials = hashedCredentials;
+        this.salt = credentialsSalt;
+    }
+
 
     public PrincipalCollection getPrincipals() {
         return principals;
@@ -107,6 +151,40 @@ public class SimpleAuthenticationInfo im
     }
 
     /**
+     * Returns the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     * hashed at all.
+     * <p/>
+     * Note that this attribute is <em>NOT</em> handled in the
+     * {@link #merge(AuthenticationInfo) merge} method - a hash salt is only useful within a single realm (as each
+     * realm will perform it's own Credentials Matching logic), and once finished in that realm, Shiro has no further
+     * use for salts.  Therefore it doesn't make sense to 'merge' salts in a multi-realm scenario.
+     *
+     * @return the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     *         hashed at all.
+     * @since 1.1
+     */
+    public ByteSource getCredentialsSalt() {
+        return salt;
+    }
+
+    /**
+     * Sets the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     * hashed at all.
+     * <p/>
+     * Note that this attribute is <em>NOT</em> handled in the
+     * {@link #merge(AuthenticationInfo) merge} method - a hash salt is only useful within a single realm (as each
+     * realm will perform it's own Credentials Matching logic), and once finished in that realm, Shiro has no further
+     * use for salts.  Therefore it doesn't make sense to 'merge' salts in a multi-realm scenario.
+     *
+     * @param salt the salt used to hash the credentials, or {@code null} if no salt was used or credentials were not
+     *             hashed at all.
+     * @since 1.1
+     */
+    public void setSalt(ByteSource salt) {
+        this.salt = salt;
+    }
+
+    /**
      * Takes the specified <code>info</code> argument and adds its principals and credentials into this instance.
      *
      * @param info the <code>AuthenticationInfo</code> to add into this instance.
@@ -124,7 +202,17 @@ public class SimpleAuthenticationInfo im
                 this.principals = new SimplePrincipalCollection(this.principals);
             }
             ((MutablePrincipalCollection) this.principals).addAll(info.getPrincipals());
-        }         
+        }
+
+        //only mess with a salt value if we don't have one yet.  It doesn't make snese
+        //to merge salt values from different realms because a salt is used only within
+        //the realm's credential matching process.  But if the current instance's salt
+        //is null, then it can't hurt to pull in a non-null value if one exists.
+        //
+        //since 1.1:
+        if (this.salt == null && info instanceof SaltedAuthenticationInfo) {
+            this.salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
+        }
 
         Object thisCredentials = getCredentials();
         Object otherCredentials = info.getCredentials();

Modified: shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java (original)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/HashedCredentialsMatcher.java Wed Oct 20 03:15:06 2010
@@ -20,6 +20,7 @@ package org.apache.shiro.authc.credentia
 
 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.Hex;
 import org.apache.shiro.crypto.hash.AbstractHash;
@@ -34,36 +35,76 @@ import org.apache.shiro.crypto.hash.Hash
  * anyone, so they often hash the users' credentials before they are saved in the data store.
  * <p/>
  * This class (and its subclasses) function as follows:
- * <p/>
- * It first hashes the {@code AuthenticationToken} credentials supplied by the user during their login.  It then
- * compares this hashed value directly with the {@code AuthenticationInfo} credentials stored in the system.  The stored account
- * credentials are expected to already be in hashed form.  If these two values are equal, the submitted credentials
- * match.
- * <h3>Salting and Multiple Hash Iterations</h3>
- * Because simple hashing is sometimes not good enough for many applications, this class also supports 'salting'
+ * <ol>
+ * <li>Hash the {@code AuthenticationToken} credentials supplied by the user during their login.</li>
+ * <li>Compare this hashed value directly with the {@code AuthenticationInfo} credentials stored in the system
+ * (the stored account credentials are expected to already be in hashed form).</li>
+ * <li>If these two values are {@link #equals(Object, Object) equal}, the submitted credentials match, otherwise
+ * they do not.</li>
+ * </ol>
+ * <h2>Salting and Multiple Hash Iterations</h2>
+ * Because simple hashing is usually not good enough for secure applications, this class also supports 'salting'
  * and multiple hash iterations.  Please read this excellent
  * <a href="http://www.owasp.org/index.php/Hashing_Java" _target="blank">Hashing Java article</a> to learn about
  * salting and multiple iterations and why you might want to use them. (Note of sections 5
- * &quot;Why add salt?&quot; and 6 "Hardening against the attacker's attack").
+ * &quot;Why add salt?&quot; and 6 "Hardening against the attacker's attack").   We should also note here that all of
+ * Shiro's Hash implementations (for example, {@link org.apache.shiro.crypto.hash.Md5Hash Md5Hash},
+ * {@link org.apache.shiro.crypto.hash.Sha1Hash Sha1Hash}, etc) support salting and multiple hash iterations via
+ * overloaded constructors.
+ * <h4>Real World Case Study</h4>
+ * In April 2010, some public Atlassian Jira and Confluence
+ * installations (Apache Software Foundation, Codehaus, etc) were the target of account attacks and user accounts
+ * were compromised.  The reason?  Jira and Confluence at the time did not salt user passwords and attackers were
+ * able to use dictionary attacks to compromise user accounts (Atlassian has since
+ * <a href="http://blogs.atlassian.com/news/2010/04/oh_man_what_a_day_an_update_on_our_security_breach.html">
+ * fixed the problem</a> of course).
  * <p/>
- * We should also note here that all of Shiro's Hash implementations (for example,
- * {@link org.apache.shiro.crypto.hash.Md5Hash Md5Hash}, {@link org.apache.shiro.crypto.hash.Sha1Hash Sha1Hash}, etc)
- * support salting and multiple hash iterations via overloaded constructors.
- * <h4>Salting</h4>
- * Salting of the authentication token's credentials hash is disabled by default, but you may enable it by setting
- * {@link #setHashSalted hashSalted} to
- * {@code true}.  If you do enable it, the value used to salt the hash will be
- * obtained from {@link #getSalt(org.apache.shiro.authc.AuthenticationToken) getSalt(authenticationToken)}.
- * <p/>
- * The default {@code getSalt} implementation merely returns
- * {@code token.getPrincipal()}, effectively using the user's identity as the salt, a most common
- * technique.  If you wish to provide the authentication token's salt another way, you may override this
- * {@code getSalt} method.
- * <h4>Multiple Hash Iterations</h4>
- * If you hash your users' credentials multiple times before persisting to the data store, you will also need to
- * set this class's {@link #setHashIterations(int) hashIterations} property.
+ * The lesson?
+ * <p/>
+ * <b>ALWAYS, ALWAYS, ALWAYS SALT USER PASSWORDS!</b>
+ * <p/>
+ * <h3>Salting</h3>
+ * Prior to Shiro 1.1, salts could be obtained based on the end-user submitted
+ * {@link AuthenticationToken AuthenticationToken} via the now-deprecated
+ * {@link #getSalt(org.apache.shiro.authc.AuthenticationToken) getSalt(AuthenticationToken)} method.  This however
+ * could constitute a security hole since ideally salts should never be obtained based on what a user can submit.
+ * User-submitted salt mechanisms are <em>much</em> more susceptible to dictionary attacks and <b>SHOULD NOT</b> be
+ * used in secure systems.  Instead salts should ideally be a secure randomly-generated number that is generated when
+ * the user account is created.  The secure number should never be disseminated to the user and always kept private
+ * by the application.
+ * <h4>Shiro 1.1</h4>
+ * As of Shiro 1.1, it is expected that any salt used to hash the submitted credentials will be obtained from the
+ * stored account information (represented as an {@link AuthenticationInfo AuthenticationInfo} instance).  This is much
+ * more secure because the salt value remains private to the application (Shiro will never store this value).
+ * <p/>
+ * To enable this, {@code Realm}s should return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances
+ * during authentication.  {@code HashedCredentialsMatcher} implementations will then use the provided
+ * {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt credentialsSalt} for hashing.  To avoid
+ * security risks,
+ * <b>it is highly recommended that any existing {@code Realm} implementations that support hashed credentials are
+ * updated to return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances as soon as possible</b>.
+ * <h4>Shiro 1.0 Backwards Compatibility</h4>
+ * Because of the identified security risk, {@code Realm} implementations that support credentials hashing should
+ * be updated to return {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} instances as
+ * soon as possible.
  * <p/>
- * <b>Note:</b> <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> and
+ * If this is not possible for some reason, this class will retain 1.0 backwards-compatible behavior of obtaining
+ * the salt via the now-deprecated {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)} method.  This
+ * method will only be invoked if a {@code Realm} <em>does not</em> return
+ * {@link SaltedAuthenticationInfo SaltedAutenticationInfo} instances and {@link #isHashSalted() hashSalted} is
+ * {@code true}.
+ * But please note that the {@link #isHashSalted() hashSalted} property and the
+ * {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)} methods will be removed before the Shiro 2.0
+ * release.
+ * <h3>Multiple Hash Iterations</h3>
+ * If you hash your users' credentials multiple times before persisting to the data store, you will also need to
+ * set this class's {@link #setHashIterations(int) hashIterations} property.  See the
+ * <a href="http://www.owasp.org/index.php/Hashing_Java" _target="blank">Hashing Java article</a>'s
+ * <a href="http://www.owasp.org/index.php/Hashing_Java#Hardening_against_the_attacker.27s_attack">
+ * &quot;Hardening against the attacker's attack&quot;</a> section to learn more about why you might want to use
+ * multiple hash iterations.
+ * <h2>MD5 &amp; SHA-1 Notice</h2>
+ * <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> and
  * <a href="http://en.wikipedia.org/wiki/SHA_hash_functions">SHA-1</a> algorithms are now known to be vulnerable to
  * compromise and/or collisions (read the linked pages for more).  While most applications are ok with either of these
  * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their
@@ -122,7 +163,20 @@ public abstract class HashedCredentialsM
      *
      * @return {@code true} if a submitted {@code AuthenticationToken}'s credentials should be salted when hashing,
      *         {@code false} if it should not be salted.
+     * @deprecated since Shiro 1.1.  Hash salting is now expected to be based on if the {@link AuthenticationInfo}
+     *             returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its
+     *             {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value.
+     *             This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return
+     *             {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations
+     *             that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo}
+     *             instances as soon as possible</b>.
+     *             <p/>
+     *             This is because salts should always be obtained from the stored account information and
+     *             never be interpreted based on user/Subject-entered data.  User-entered data is easier to compromise for
+     *             attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user
+     *             are almost impossible to break.  This method will be removed in Shiro 2.0.
      */
+    @Deprecated
     public boolean isHashSalted() {
         return hashSalted;
     }
@@ -130,12 +184,25 @@ public abstract class HashedCredentialsM
     /**
      * Sets whether or not to salt a submitted {@code AuthenticationToken}'s credentials when hashing.
      * <p/>
-     * If enabled, the salt used will be obtained via the {@link #getSalt(org.apache.shiro.authc.AuthenticationToken) getSalt} method.
+     * If enabled, the salt used will be obtained via the {@link #getSalt(org.apache.shiro.authc.AuthenticationToken) getCredentialsSalt} method.
      * </p>
      * The default value is {@code false}.
      *
      * @param hashSalted whether or not to salt a submitted {@code AuthenticationToken}'s credentials when hashing.
+     * @deprecated since Shiro 1.1.  Hash salting is now expected to be based on if the {@link AuthenticationInfo}
+     *             returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its
+     *             {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value.
+     *             This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return
+     *             {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations
+     *             that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo}
+     *             instances as soon as possible</b>.
+     *             <p/>
+     *             This is because salts should always be obtained from the stored account information and
+     *             never be interpreted based on user/Subject-entered data.  User-entered data is easier to compromise for
+     *             attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user
+     *             are almost impossible to break.  This method will be removed in Shiro 2.0.
      */
+    @Deprecated
     public void setHashSalted(boolean hashSalted) {
         this.hashSalted = hashSalted;
     }
@@ -181,29 +248,24 @@ public abstract class HashedCredentialsM
      *
      * @param token the AuthenticationToken submitted during the authentication attempt.
      * @return a salt value to use to hash the authentication token's credentials.
+     * @deprecated since Shiro 1.1.  Hash salting is now expected to be based on if the {@link AuthenticationInfo}
+     *             returned from the {@code Realm} is a {@link SaltedAuthenticationInfo} instance and its
+     *             {@link org.apache.shiro.authc.SaltedAuthenticationInfo#getCredentialsSalt() getCredentialsSalt()} method returns a non-null value.
+     *             This method and the 1.0 behavior still exists for backwards compatibility if the {@code Realm} does not return
+     *             {@code SaltedAuthenticationInfo} instances, but <b>it is highly recommended that {@code Realm} implementations
+     *             that support hashed credentials start returning {@link SaltedAuthenticationInfo SaltedAuthenticationInfo}
+     *             instances as soon as possible</b>.<p/>
+     *             This is because salts should always be obtained from the stored account information and
+     *             never be interpreted based on user/Subject-entered data.  User-entered data is easier to compromise for
+     *             attackers, whereas account-unique (and secure randomly-generated) salts never disseminated to the end-user
+     *             are almost impossible to break.  This method will be removed in Shiro 2.0.
      */
+    @Deprecated
     protected Object getSalt(AuthenticationToken token) {
         return token.getPrincipal();
     }
 
     /**
-     * As this is a HashedCredentialMatcher, this method overrides the parent method by returning a hashed value
-     * of the submitted token's credentials.
-     * <p/>
-     * Based on this class's configuration, the return value may be salted and/or
-     * hashed multiple times (see the class-level JavaDoc for more information on salting and
-     * multiple hash iterations).
-     *
-     * @param token the authentication token submitted during the authentication attempt.
-     * @return the hashed value of the authentication token's credentials.
-     */
-    protected Object getCredentials(AuthenticationToken token) {
-        Object credentials = token.getCredentials();
-        Object salt = isHashSalted() ? getSalt(token) : null;
-        return hashProvidedCredentials(credentials, salt, getHashIterations());
-    }
-
-    /**
      * Returns a {@link Hash Hash} instance representing the already-hashed AuthenticationInfo credentials stored in the system.
      * <p/>
      * This method reconstructs a {@link Hash Hash} instance based on a {@code info.getCredentials} call,
@@ -242,6 +304,58 @@ public abstract class HashedCredentialsM
     }
 
     /**
+     * This implementation first hashes the {@code token}'s credentials, potentially using a
+     * {@code salt} if the {@code info} argument is a
+     * {@link org.apache.shiro.authc.SaltedAuthenticationInfo SaltedAuthenticationInfo}.  It then compares the hash
+     * against the {@code AuthenticationInfo}'s
+     * {@link #getCredentials(org.apache.shiro.authc.AuthenticationInfo) already-hashed credentials}.  This method
+     * returns {@code true} if those two values are {@link #equals(Object, Object) equal}, {@code false} otherwise.
+     *
+     * @param token the {@code AuthenticationToken} submitted during the authentication attempt.
+     * @param info  the {@code AuthenticationInfo} stored in the system matching the token principal
+     * @return {@code true} if the provided token credentials hash match to the stored account credentials hash,
+     *         {@code false} otherwise
+     * @since 1.1
+     */
+    @Override
+    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+        Object tokenHashedCredentials = hashProvidedCredentials(token, info);
+        Object accountCredentials = getCredentials(info);
+        return equals(tokenHashedCredentials, accountCredentials);
+    }
+
+    /**
+     * Hash the provided {@code token}'s credentials using the salt stored with the account if the
+     * {@code info} instance is an {@code instanceof} {@link SaltedAuthenticationInfo SaltedAuthenticationInfo} (see
+     * the class-level JavaDoc for why this is the preferred approach).
+     * <p/>
+     * If the {@code info} instance is <em>not</em>
+     * an {@code instanceof} {@code SaltedAuthenticationInfo}, the logic will fall back to Shiro 1.0
+     * backwards-compatible logic:  it will first check to see {@link #isHashSalted() isHashSalted} and if so, will try
+     * to acquire the salt from {@link #getSalt(AuthenticationToken) getSalt(AuthenticationToken)}.  See the class-level
+     * JavaDoc for why this is not recommended.  This 'fallback' logic exists only for backwards-compatibility.
+     * {@code Realm}s should be updated as soon as possible to return {@code SaltedAuthenticationInfo} instances
+     * if account credentials salting is enabled (highly recommended for password-based systems).
+     *
+     * @param token the submitted authentication token from which its credentials will be hashed
+     * @param info  the stored account data, potentially used to acquire a salt
+     * @return the token credentials hash
+     * @since 1.1
+     */
+    protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
+        Object salt = null;
+        if (info instanceof SaltedAuthenticationInfo) {
+            salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
+        } else {
+            //retain 1.0 backwards compatibility:
+            if (isHashSalted()) {
+                salt = getSalt(token);
+            }
+        }
+        return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
+    }
+
+    /**
      * Hashes the provided credentials a total of {@code hashIterations} times, using the given salt.  The hash
      * implementation/algorithm used is left to subclasses.
      *

Added: shiro/trunk/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java?rev=1024510&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/RandomNumberGenerator.java Wed Oct 20 03:15:06 2010
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A component that can generate random number/byte values as needed.  Useful in cryptography or security scenarios
+ * where random byte arrays are needed, such as for password salts, nonces, initialization vectors and other seeds.
+ * <p/>
+ * This is essentially the same as a {@link java.security.SecureRandom SecureRandom}, and indeed implementations
+ * of this interface will probably all use {@link java.security.SecureRandom SecureRandom} instances, but this
+ * interface provides a few additional benefits to end-users:
+ * <ul>
+ * <li>It is an interface rather than the JDK's {@code SecureRandom} concrete implementation.  Implementation details
+ * can be customized as necessary based on the application's needs</li>
+ * <li>Default per-instance behavior can be customized on implementations, typically via JavaBeans mutators.</li>
+ * <li>Perhaps most important for Shiro end-users, tt can more easily be used as a source of cryptographic seed data,
+ * and the data returned is already in a more {@link ByteSource ByteSource} format in case that data needs to be
+ * {@link org.apache.shiro.util.ByteSource#toHex() hex} or
+ * {@link org.apache.shiro.util.ByteSource#toBase64() base64}-encoded.</li>
+ * </ul>
+ * For example, consider the following example generating password salts for new user accounts:
+ *
+ * <pre>
+ * RandomNumberGenerator saltGenerator = new {@link org.apache.shiro.crypto.SecureRandomNumberGenerator SecureRandomNumberGenerator}();
+ * User user = new User();
+ * user.setPasswordSalt(saltGenerator.nextBytes().toBase64());
+ * userDAO.save(user);
+ * </pre>
+ *
+ * @since 1.1
+ */
+public interface RandomNumberGenerator {
+
+    /**
+     * Generates a byte array of fixed length filled with random data, often useful for generating salts,
+     * initialization vectors or other seed data.  The length is specified as a configuration
+     * value on the underlying implementation.
+     *
+     * @return a byte array of fixed length filled with random data.
+     */
+    ByteSource nextBytes();
+
+    /**
+     * Generates a byte array of the specified length filled with random data.
+     * @param numBytes the number of bytes to be populated with random data.
+     * @return a byte array of the specified length filled with random data.
+     */
+    ByteSource nextBytes(int numBytes);
+}

Added: 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=1024510&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java (added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/SecureRandomNumberGenerator.java Wed Oct 20 03:15:06 2010
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.SimpleByteSource;
+
+import java.security.SecureRandom;
+
+/**
+ * Default implementation of the {@link RandomNumberGenerator RandomNumberGenerator} interface, backed by a
+ * {@link SecureRandom SecureRandom} instance.
+ * <p/>
+ * This class is a little easier to use than using the JDK's {@code SecureRandom} class directly.  It also
+ * allows for JavaBeans-style of customization, convenient for Shiro's INI configuration or other IoC configuration
+ * mechanism.
+ *
+ * @since 1.1
+ */
+public class SecureRandomNumberGenerator implements RandomNumberGenerator {
+
+    protected static final int DEFAULT_NEXT_BYTES_SIZE = 128;
+
+    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}.
+     */
+    public SecureRandomNumberGenerator() {
+        this.defaultNextBytesSize = DEFAULT_NEXT_BYTES_SIZE;
+        this.secureRandom = new SecureRandom();
+    }
+
+    /**
+     * Seeds the backing {@link SecureRandom SecureRandom} instance with additional seed data.
+     *
+     * @param bytes the seed bytes
+     * @see SecureRandom#setSeed(byte[])
+     */
+    public void setSeed(byte[] bytes) {
+        this.secureRandom.setSeed(bytes);
+    }
+
+    /**
+     * Returns the {@link SecureRandom SecureRandom} backing this instance.
+     *
+     * @return the {@link SecureRandom SecureRandom} backing this instance.
+     */
+    public SecureRandom getSecureRandom() {
+        return secureRandom;
+    }
+
+    /**
+     * Sets the {@link SecureRandom SecureRandom} to back this instance.
+     *
+     * @param random the {@link SecureRandom SecureRandom} to back this instance.
+     * @throws NullPointerException if the method argument is null
+     */
+    public void setSecureRandom(SecureRandom random) throws NullPointerException {
+        if (random == null) {
+            throw new NullPointerException("SecureRandom argument cannot be null.");
+        }
+        this.secureRandom = random;
+    }
+
+    /**
+     * 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.
+     *
+     * @return the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.
+     */
+    public int getDefaultNextBytesSize() {
+        return defaultNextBytesSize;
+    }
+
+    /**
+     * 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.
+     *
+     * @param defaultNextBytesSize the size of the generated byte array for calls to {@link #nextBytes() nextBytes()}.
+     * @throws IllegalArgumentException if the argument is 0 or negative
+     */
+    public void setDefaultNextBytesSize(int defaultNextBytesSize) throws IllegalArgumentException {
+        if ( defaultNextBytesSize <= 0) {
+            throw new IllegalArgumentException("size value must be a positive integer (1 or larger)");
+        }
+        this.defaultNextBytesSize = defaultNextBytesSize;
+    }
+
+    public ByteSource nextBytes() {
+        return nextBytes(getDefaultNextBytesSize());
+    }
+
+    public ByteSource nextBytes(int numBytes) {
+        if (numBytes <= 0) {
+            throw new IllegalArgumentException("numBytes argument must be a positive integer (1 or larger)");
+        }
+        byte[] bytes = new byte[numBytes];
+        this.secureRandom.nextBytes(bytes);
+        return new SimpleByteSource(bytes);
+    }
+}

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=1024510&r1=1024509&r2=1024510&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 Wed Oct 20 03:15:06 2010
@@ -18,6 +18,8 @@
  */
 package org.apache.shiro.crypto.hash;
 
+import org.apache.shiro.util.ByteSource;
+
 /**
  * A Cryptographic {@code Hash} represents a one-way conversion algorithm that transforms an input source to an
  * underlying byte array.
@@ -31,7 +33,7 @@ package org.apache.shiro.crypto.hash;
  * @see Sha512Hash
  * @since 0.9
  */
-public interface Hash {
+public interface Hash extends ByteSource {
 
     /**
      * Returns this Hash's byte array, that is, the hashed value of the original input source.

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=1024510&r1=1024509&r2=1024510&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 Wed Oct 20 03:15:06 2010
@@ -39,7 +39,6 @@ public interface ByteSource {
      * @return the <a href="http://en.wikipedia.org/wiki/Hexadecimal">Hex</a>-formatted String representation of the
      *         underlying wrapped byte array.
      */
-    @SuppressWarnings({"UnusedDeclaration"})
     public String toHex();
 
     /**

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=1024510&r1=1024509&r2=1024510&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 Wed Oct 20 03:15:06 2010
@@ -19,35 +19,107 @@
 package org.apache.shiro.util;
 
 import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.CodecSupport;
 import org.apache.shiro.codec.Hex;
 
+import java.io.*;
 import java.util.Arrays;
 
 /**
  * Very simple {@link ByteSource ByteSource} implementation that maintains an internal {@code byte[]} array and uses the
  * {@link Hex Hex} and {@link Base64 Base64} codec classes to support the
  * {@link #toHex() toHex()} and {@link #toBase64() toBase64()} implementations.
+ * <p/>
+ * The constructors on this class accept the following implicit byte-backed data types and will convert them to
+ * a byte-array automatically:
+ * <ul>
+ * <li>byte[]</li>
+ * <li>char[]</li>
+ * <li>String</li>
+ * <li>{@link ByteSource ByteSource}</li>
+ * <li>{@link File File}</li>
+ * <li>{@link InputStream InputStream}</li>
+ * </ul>
  *
  * @since 1.0
  */
 public class SimpleByteSource implements ByteSource {
 
     private final byte[] bytes;
+    private String cachedHex;
+    private String cachedBase64;
 
     public SimpleByteSource(byte[] bytes) {
         this.bytes = bytes;
     }
 
+    /**
+     * Creates an instance by converting the characters to a byte array (assumes UTF-8 encoding).
+     *
+     * @param chars the source characters to use to create the underlying byte array.
+     * @since 1.1
+     */
+    public SimpleByteSource(char[] chars) {
+        this.bytes = CodecSupport.toBytes(chars);
+    }
+
+    /**
+     * Creates an instance by converting the String to a byte array (assumes UTF-8 encoding).
+     *
+     * @param string the source string to convert to a byte array (assumes UTF-8 encoding).
+     * @since 1.1
+     */
+    public SimpleByteSource(String string) {
+        this.bytes = CodecSupport.toBytes(string);
+    }
+
+    /**
+     * Creates an instance using the sources bytes directly - it does not create a copy of the
+     * argument's byte array.
+     *
+     * @param source the source to use to populate the underlying byte array.
+     * @since 1.1
+     */
+    public SimpleByteSource(ByteSource source) {
+        this.bytes = source.getBytes();
+    }
+
+    /**
+     * Creates an instance by converting the file to a byte array.
+     *
+     * @param file the file from which to acquire bytes.
+     * @since 1.1
+     */
+    public SimpleByteSource(File file) {
+        this.bytes = new BytesHelper().getBytes(file);
+    }
+
+    /**
+     * Creates an instance by converting the stream to a byte array.
+     *
+     * @param stream the stream from which to acquire bytes.
+     * @since 1.1
+     */
+    public SimpleByteSource(InputStream stream) {
+        this.bytes = new BytesHelper().getBytes(stream);
+    }
+
     public byte[] getBytes() {
         return this.bytes;
     }
 
     public String toHex() {
-        return Hex.encodeToString(getBytes());
+        if ( this.cachedHex == null ) {
+            this.cachedHex = Hex.encodeToString(getBytes());
+        }
+        return this.cachedHex;
     }
 
     public String toBase64() {
-        return Base64.encodeToString(getBytes());
+        if ( this.cachedBase64 == null ) {
+            this.cachedBase64 = Base64.encodeToString(getBytes());
+        }
+        return this.cachedBase64;
     }
 
     public String toString() {
@@ -55,17 +127,29 @@ public class SimpleByteSource implements
     }
 
     public int hashCode() {
-        return toBase64().hashCode();
+        return toString().hashCode();
     }
 
     public boolean equals(Object o) {
         if (o == this) {
             return true;
         }
-        if (o instanceof SimpleByteSource) {
-            SimpleByteSource bs = (SimpleByteSource) o;
+        if (o instanceof ByteSource) {
+            ByteSource bs = (ByteSource) o;
             return Arrays.equals(getBytes(), bs.getBytes());
         }
         return false;
     }
+
+    //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(File file) {
+            return toBytes(file);
+        }
+
+        public byte[] getBytes(InputStream stream) {
+            return toBytes(stream);
+        }
+    }
 }

Copied: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java (from r1022243, shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java)
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java?p2=shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java&p1=shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java&r1=1022243&r2=1024510&rev=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -28,11 +28,10 @@ import org.apache.shiro.authc.UsernamePa
 import org.apache.shiro.crypto.hash.AbstractHash;
 import org.apache.shiro.util.ClassUtils;
 
-
 /**
  * @since Jun 10, 2008 4:47:09 PM
  */
-public abstract class HashedCredentialsMatcherTest extends TestCase {
+public abstract class AbstractHashedCredentialsMatcherTest extends TestCase {
 
     public abstract Class<? extends HashedCredentialsMatcher> getMatcherClass();
 

Propchange: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/AbstractHashedCredentialsMatcherTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java?rev=1024510&view=auto
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java (added)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/HashedCredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -0,0 +1,117 @@
+/*
+ * 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.*;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.hash.Sha1Hash;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.util.ByteSource;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the {@link org.apache.shiro.authc.credential.HashedCredentialsMatcher} class.
+ */
+public class HashedCredentialsMatcherTest {
+
+    /**
+     * Test new Shiro 1.1 functionality, where the salt is obtained from the stored account information, as it
+     * should be.  See <a href="https://issues.apache.org/jira/browse/SHIRO-186">SHIRO-186</a>
+     */
+    @Test
+    public void testSaltedAuthenticationInfo() {
+        //use SHA-1 hashing in this test:
+        HashedCredentialsMatcher matcher = new Sha1CredentialsMatcher();
+
+        //simulate a user account with a SHA-1 hashed and salted password:
+        ByteSource salt = new SecureRandomNumberGenerator().nextBytes();
+        Object hashedPassword = new Sha1Hash("password", salt);
+        SimpleAuthenticationInfo account = new SimpleAuthenticationInfo("username", hashedPassword, salt, "realmName");
+
+        //simulate a username/password (plaintext) token created in response to a login attempt:
+        AuthenticationToken token = new UsernamePasswordToken("username", "password");
+
+        //verify the hashed token matches what is in the account:
+        assertTrue(matcher.doCredentialsMatch(token, account));
+    }
+
+    /**
+     * Test backwards compatibility of unsalted credentials before
+     * <a href="https://issues.apache.org/jira/browse/SHIRO-186">SHIRO-186</a> edits.
+     */
+    @Test
+    public void testBackwardsCompatibleUnsaltedAuthenticationInfo() {
+        HashedCredentialsMatcher matcher = new Sha1CredentialsMatcher();
+
+        //simulate an account with SHA-1 hashed password (no salt)
+        final String username = "username";
+        final String password = "password";
+        final Object hashedPassword = new Sha1Hash(password).getBytes();
+        AuthenticationInfo account = new AuthenticationInfo() {
+            public PrincipalCollection getPrincipals() {
+                return new SimplePrincipalCollection(username, "realmName");
+            }
+
+            public Object getCredentials() {
+                return hashedPassword;
+            }
+        };
+
+        //simulate a username/password (plaintext) token created in response to a login attempt:
+        AuthenticationToken token = new UsernamePasswordToken("username", "password");
+
+        //verify the hashed token matches what is in the account:
+        assertTrue(matcher.doCredentialsMatch(token, account));
+    }
+
+    /**
+     * Test backwards compatibility of salted credentials before
+     * <a href="https://issues.apache.org/jira/browse/SHIRO-186">SHIRO-186</a> edits.
+     */
+    @Test
+    public void testBackwardsCompatibleSaltedAuthenticationInfo() {
+        HashedCredentialsMatcher matcher = new Sha1CredentialsMatcher();
+        //enable this for Shiro 1.0 backwards compatibility:
+        matcher.setHashSalted(true);
+
+        //simulate an account with SHA-1 hashed password, using the username as the salt
+        //(BAD IDEA, but backwards-compatible):
+        final String username = "username";
+        final String password = "password";
+        final Object hashedPassword = new Sha1Hash(password, username).getBytes();
+        AuthenticationInfo account = new AuthenticationInfo() {
+            public PrincipalCollection getPrincipals() {
+                return new SimplePrincipalCollection(username, "realmName");
+            }
+
+            public Object getCredentials() {
+                return hashedPassword;
+            }
+        };
+
+        //simulate a username/password (plaintext) token created in response to a login attempt:
+        AuthenticationToken token = new UsernamePasswordToken("username", "password");
+
+        //verify the hashed token matches what is in the account:
+        assertTrue(matcher.doCredentialsMatch(token, account));
+    }
+}

Modified: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md2CredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -25,7 +25,7 @@ import org.apache.shiro.crypto.hash.Md2H
 /**
  * @since Jun 10, 2008 4:38:16 PM
  */
-public class Md2CredentialsMatcherTest extends HashedCredentialsMatcherTest {
+public class Md2CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
 
     public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
         return Md2CredentialsMatcher.class;

Modified: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Md5CredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -25,7 +25,7 @@ import org.apache.shiro.crypto.hash.Md5H
 /**
  * @since Jun 10, 2008 4:59:36 PM
  */
-public class Md5CredentialsMatcherTest extends HashedCredentialsMatcherTest {
+public class Md5CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
 
     public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
         return Md5CredentialsMatcher.class;

Modified: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -25,7 +25,7 @@ import org.apache.shiro.crypto.hash.Sha1
 /**
  * @since Jun 10, 2008 5:00:30 PM
  */
-public class Sha1CredentialsMatcherTest extends HashedCredentialsMatcherTest {
+public class Sha1CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
 
     public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
         return Sha1CredentialsMatcher.class;

Modified: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha256CredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -25,7 +25,7 @@ import org.apache.shiro.crypto.hash.Sha2
 /**
  * @since Jun 10, 2008 5:01:00 PM
  */
-public class Sha256CredentialsMatcherTest extends HashedCredentialsMatcherTest {
+public class Sha256CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
 
     public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
         return Sha256CredentialsMatcher.class;

Modified: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha384CredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -25,7 +25,7 @@ import org.apache.shiro.crypto.hash.Sha3
 /**
  * @since Jun 10, 2008 5:02:27 PM
  */
-public class Sha384CredentialsMatcherTest extends HashedCredentialsMatcherTest {
+public class Sha384CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
 
     public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
         return Sha384CredentialsMatcher.class;

Modified: shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/authc/credential/Sha512CredentialsMatcherTest.java Wed Oct 20 03:15:06 2010
@@ -25,7 +25,7 @@ import org.apache.shiro.crypto.hash.Sha5
 /**
  * @since Jun 10, 2008 5:02:58 PM
  */
-public class Sha512CredentialsMatcherTest extends HashedCredentialsMatcherTest {
+public class Sha512CredentialsMatcherTest extends AbstractHashedCredentialsMatcherTest {
 
     public Class<? extends HashedCredentialsMatcher> getMatcherClass() {
         return Sha512CredentialsMatcher.class;

Modified: shiro/trunk/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java?rev=1024510&r1=1024509&r2=1024510&view=diff
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java (original)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/crypto/JcaCipherServiceTest.java Wed Oct 20 03:15:06 2010
@@ -1,3 +1,21 @@
+/*
+ * 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;
 
 import static org.junit.Assert.*;
@@ -6,18 +24,13 @@ import org.junit.Test;
 
 public class JcaCipherServiceTest {
 
-    @Test
+    @Test(expected = CryptoException.class)
     public void testDecrypt() {
-	JcaCipherService cipherService = new JcaCipherService("AES") {};
-	String ciphertext = "iv_helloword";
-	String key = "somekey";
-	try {
-	    // This should cause ArrayIndexOutOfBoundsException, at least currently that's what we want
-	    cipherService.decrypt(ciphertext.getBytes(), key.getBytes());
-	} catch (CryptoException e) {
-	    return;
-	}
-        fail("CryptoException was expected to be thrown");
+        JcaCipherService cipherService = new JcaCipherService("AES") {
+        };
+        String ciphertext = "iv_helloword";
+        String key = "somekey";
+        cipherService.decrypt(ciphertext.getBytes(), key.getBytes());
     }
 
 }

Added: shiro/trunk/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java
URL: http://svn.apache.org/viewvc/shiro/trunk/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java?rev=1024510&view=auto
==============================================================================
--- shiro/trunk/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java (added)
+++ shiro/trunk/core/src/test/java/org/apache/shiro/crypto/SecureRandomNumberGeneratorTest.java Wed Oct 20 03:15:06 2010
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import org.apache.shiro.util.ByteSource;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the {@link SecureRandomNumberGenerator} class.
+ *
+ * @since 1.1
+ */
+public class SecureRandomNumberGeneratorTest {
+
+    @Test
+    public void testDefaultNextBytesSize() {
+        SecureRandomNumberGenerator rng = new SecureRandomNumberGenerator();
+        boolean negativeThrown = false;
+        boolean zeroThrown = false;
+        try {
+            rng.setDefaultNextBytesSize(-1);
+        } catch (IllegalArgumentException e) {
+            negativeThrown = true;
+        }
+
+        try {
+            rng.setDefaultNextBytesSize(0);
+        } catch (IllegalArgumentException e) {
+            zeroThrown = true;
+        }
+
+        assertTrue(negativeThrown);
+        assertTrue(zeroThrown);
+
+        ByteSource bs = rng.nextBytes();
+        assertNotNull(bs);
+        assertNotNull(bs.getBytes());
+        assertEquals(SecureRandomNumberGenerator.DEFAULT_NEXT_BYTES_SIZE, bs.getBytes().length);
+
+        rng.setDefaultNextBytesSize(64);
+        assertNotNull(bs);
+        bs = rng.nextBytes();
+        assertNotNull(bs.getBytes());
+        assertEquals(64, bs.getBytes().length);
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void testInvalidSecureRandomProperty() {
+        SecureRandomNumberGenerator rng = new SecureRandomNumberGenerator();
+        rng.setSecureRandom(null);
+    }
+
+    @Test
+    public void testNextBytesWithSize() {
+        SecureRandomNumberGenerator rng = new SecureRandomNumberGenerator();
+        boolean negativeThrown = false;
+        boolean zeroThrown = false;
+        try {
+            rng.nextBytes(-1);
+        } catch (IllegalArgumentException e) {
+            negativeThrown = true;
+        }
+
+        try {
+            rng.nextBytes(0);
+        } catch (IllegalArgumentException e) {
+            zeroThrown = true;
+        }
+
+        assertTrue(negativeThrown);
+        assertTrue(zeroThrown);
+
+        ByteSource bs = rng.nextBytes(8);
+        assertNotNull(bs);
+        assertNotNull(bs.getBytes());
+        assertEquals(8, bs.getBytes().length);
+    }
+}