Author: lhazlewood
Date: Tue Apr  5 01:47:41 2011
New Revision: 1088844

URL: http://svn.apache.org/viewvc?rev=1088844&view=rev
Log:
SHIRO-280: created an initial implementation - still need to flush out some 
test cases

Added:
    
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/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/HashRequest.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/SimpleHashRequest.java
    
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashResponse.java
    shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/
    shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/
    
shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHasherTest.groovy

Added: 
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=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
 (added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
 Tue Apr  5 01:47:41 2011
@@ -0,0 +1,161 @@
+/*
+ * 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.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.util.ByteSource;
+import org.apache.shiro.util.SimpleByteSource;
+
+import java.util.Arrays;
+
+/**
+ * Default implementation of the {@link PasswordService} interface.  Delegates 
to an internal (configurable)
+ * {@link Hasher} instance.
+ *
+ * @since 1.2
+ */
+public class DefaultPasswordService implements PasswordService {
+
+    private ConfigurableHasher hasher;
+
+    private String storedCredentialsEncoding = "base64";
+
+    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);
+    }
+
+    public HashResponse hashPassword(ByteSource plaintextPassword) {
+        byte[] plaintextBytes = plaintextPassword != null ? 
plaintextPassword.getBytes() : null;
+        if (plaintextBytes == null || plaintextBytes.length == 0) {
+            return null;
+        }
+
+        return this.hasher.computeHash(new 
SimpleHashRequest(plaintextPassword));
+    }
+
+    public boolean doCredentialsMatch(AuthenticationToken token, 
AuthenticationInfo info) {
+
+        ByteSource publicSalt = null;
+        if (info instanceof SaltedAuthenticationInfo) {
+            publicSalt = ((SaltedAuthenticationInfo) 
info).getCredentialsSalt();
+        }
+
+        Hash tokenCredentialsHash = hashProvidedCredentials(token, publicSalt);
+        byte[] storedCredentialsBytes = getCredentialsBytes(info);
+
+        return Arrays.equals(tokenCredentialsHash.getBytes(), 
storedCredentialsBytes);
+    }
+
+    protected byte[] getCredentialsBytes(AuthenticationInfo info) {
+        Object credentials = info.getCredentials();
+
+        byte[] bytes = new BytesHelper().getBytes(credentials);
+
+        if (this.storedCredentialsEncoding != null &&
+                (credentials instanceof String || credentials instanceof 
char[])) {
+            assertEncodingSupported(this.storedCredentialsEncoding);
+            bytes = decode(bytes, this.storedCredentialsEncoding);
+        }
+
+        return bytes;
+    }
+
+    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);
+        }
+        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 = new 
SimpleByteSource(credentialsBytes);
+
+        HashRequest request = new SimpleHashRequest(credentialsByteSource, 
salt);
+
+        HashResponse response = this.hasher.computeHash(request);
+
+        return response.getHash();
+    }
+
+    /**
+     * 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);
+    }
+
+
+    protected void assertEncodingSupported(String encodingName) throws 
IllegalArgumentException {
+        if (!isEncodingSupported(encodingName)) {
+            String msg = "Unsupported encoding '" + encodingName + "'.  Please 
check for typos.";
+            throw new IllegalArgumentException(msg);
+        }
+    }
+
+    public ConfigurableHasher getHasher() {
+        return hasher;
+    }
+
+    public void setHasher(ConfigurableHasher hasher) {
+        this.hasher = hasher;
+    }
+
+    public void setStoredCredentialsEncoding(String storedCredentialsEncoding) 
{
+        if (storedCredentialsEncoding != null) {
+            assertEncodingSupported(storedCredentialsEncoding);
+        }
+        this.storedCredentialsEncoding = storedCredentialsEncoding;
+    }
+
+    //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);
+        }
+    }
+}

Added: 
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=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java
 (added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/authc/credential/PasswordService.java
 Tue Apr  5 01:47:41 2011
@@ -0,0 +1,97 @@
+/*
+ * 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.crypto.hash.HashResponse;
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A {@code PasswordService} supports common use cases when using passwords as 
a credentials mechanism.
+ * <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}:
+ * <pre>
+ * [main]
+ * ...
+ * passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
+ * </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:
+ * <pre>
+ * ...
+ * 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>
+ * ...
+ * myRealm.credentialsMatcher = $passwordService
+ * ...
+ * </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.
+ *
+ * @since 1.2
+ */
+public interface PasswordService extends CredentialsMatcher {
+
+    /**
+     * 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).
+     *
+     * @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.
+     */
+    HashResponse hashPassword(ByteSource plaintextPassword);
+
+
+}

Added: 
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/ConfigurableHasher.java?rev=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHasher.java
 (added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/ConfigurableHasher.java
 Tue Apr  5 01:47:41 2011
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.crypto.RandomNumberGenerator;
+
+/**
+ * A {@code Hasher} that allows configuration of its strategy via 
JavaBeans-compatible setter methods.
+ *
+ * @since 1.2
+ */
+public interface ConfigurableHasher extends Hasher {
+
+    /**
+     * Sets the 'private' base 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.
+     */
+    void setBaseSalt(byte[] baseSalt);
+
+    /**
+     * Sets the number of hash iterations that will be performed during hash 
computation.
+     *
+     * @param iterations the number of hash iterations that will be performed 
during hash computation.
+     */
+    void setHashIterations(int iterations);
+
+    /**
+     * 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.
+     */
+    void setHashAlgorithmName(String name);
+
+    /**
+     * Sets a source of randomness used to generate public salts that will in 
turn be used during hash computation.
+     *
+     * @param rng a source of randomness used to generate public salts that 
will in turn be used during hash computation.
+     */
+    void setRandomNumberGenerator(RandomNumberGenerator rng);
+}

Added: 
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/DefaultHasher.java?rev=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java 
(added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/DefaultHasher.java 
Tue Apr  5 01:47:41 2011
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.crypto.RandomNumberGenerator;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.SimpleByteSource;
+
+/**
+ * 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:
+ * <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
+ * to try to brute-force crack the hash (source + complete salt).
+ * <p/>
+ * 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.
+ * <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.
+ * <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
+ * {@link SecureRandomNumberGenerator}.
+ * <h2>Password Hash Iterations</h2>
+ * The most secure hashing strategy employs 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.
+ * <h2>Hash Algorithm</h2>
+ * You may specify a hash algorithm via the {@link 
#setHashAlgorithmName(String)} property.  The default is
+ * {@code SHA-512}.
+ *
+ * @since 1.2
+ */
+public class DefaultHasher implements ConfigurableHasher {
+
+    /**
+     * The RandomNumberGenerator to use to randomly generate the public part 
of the hash salt.
+     */
+    private RandomNumberGenerator rng;
+
+    /**
+     * The MessageDigest name of the hash algorithm to use for computing 
hashes.
+     */
+    private String algorithmName;
+
+    /**
+     * The 'private' part of the hash salt.
+     */
+    private byte[] baseSalt;
+
+    /**
+     * The number of hash iterations to perform when computing hashes.
+     */
+    private int iterations;
+
+    /**
+     * Constructs a new {@code DefaultHasher} 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>
+     * </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
+     * {@link #setHashIterations(int) hashIterations}.  See the class-level 
JavaDoc for more information.
+     */
+    public DefaultHasher() {
+        this.algorithmName = "SHA-512";
+        this.iterations = 1;
+        this.rng = new SecureRandomNumberGenerator();
+    }
+
+    /**
+     * Computes and responds with a hash based on the specified request.
+     * <p/>
+     * This implementation functions as follows:
+     * <ul>
+     * <li>If the request's {@link 
org.apache.shiro.crypto.hash.HashRequest#getSalt() salt} is null:
+     * <p/>
+     * 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>
+     * <li>Use the combined value as the salt used during hash computation</li>
+     * </ol>
+     * </li>
+     * <li>
+     * If the request's 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.
+     * </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.
+     *
+     * @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) {
+        if (request == null) {
+            return null;
+        }
+        ByteSource source = request.getSource();
+
+        byte[] sourceBytes = source != null ? source.getBytes() : null;
+        if (sourceBytes == null || sourceBytes.length == 0) {
+            return null;
+        }
+
+        ByteSource requestSalt = request.getSalt();
+        byte[] publicSaltBytes = requestSalt != null ? requestSalt.getBytes() 
: null;
+        if (publicSaltBytes != null && publicSaltBytes.length == 0) {
+            publicSaltBytes = null;
+        }
+        if (publicSaltBytes == null) {
+            getRandomNumberGenerator().nextBytes().getBytes();
+        }
+
+        String algorithmName = getHashAlgorithmName();
+        byte[] baseSalt = getBaseSalt();
+        byte[] saltBytes = combine(baseSalt, publicSaltBytes);
+        int iterations = Math.max(1, getHashIterations());
+
+        Hash result = new SimpleHash(algorithmName, sourceBytes, saltBytes, 
iterations);
+        ByteSource publicSalt = new SimpleByteSource(publicSaltBytes);
+
+        return new SimpleHashResponse(result, publicSalt);
+    }
+
+    /**
+     * 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.
+     *
+     * @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;
+
+        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 < randomBytesLength; j++) {
+            assert extraBytes != null;
+            combined[i++] = extraBytes[j];
+        }
+
+        return combined;
+    }
+
+
+    public void setHashAlgorithmName(String name) {
+        this.algorithmName = name;
+    }
+
+    public String getHashAlgorithmName() {
+        return this.algorithmName;
+    }
+
+    public void setBaseSalt(byte[] baseSalt) {
+        this.baseSalt = baseSalt;
+    }
+
+    public byte[] getBaseSalt() {
+        return this.baseSalt;
+    }
+
+    public void setHashIterations(int count) {
+        this.iterations = count;
+    }
+
+    public int getHashIterations() {
+        return this.iterations;
+    }
+
+    public void setRandomNumberGenerator(RandomNumberGenerator rng) {
+        this.rng = rng;
+    }
+
+    public RandomNumberGenerator getRandomNumberGenerator() {
+        return this.rng;
+    }
+}

Added: 
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=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java 
(added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashRequest.java 
Tue Apr  5 01:47:41 2011
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+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}.
+ *
+ * @see SimpleHashRequest
+ * @since 1.2
+ */
+public interface HashRequest {
+
+    /**
+     * Returns the source data that will be hashed by a {@link Hasher}.
+     *
+     * @return the source data that will be hashed by a {@link Hasher}.
+     */
+    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.
+     * <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
+     * strategy for a request, even if the request did not specify one.
+     * <p/>
+     * <b>NOTE:</b> if
+     *
+     * @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.
+     */
+
+    ByteSource getSalt();
+}

Added: 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashResponse.java
URL: 
http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashResponse.java?rev=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashResponse.java 
(added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/HashResponse.java 
Tue Apr  5 01:47:41 2011
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * A {@code HashResponse} represents the data returned from a {@link Hasher} 
after hashing an input source.
+ * <p/>
+ * Note that a {@code HashResposne} may not represent identical output 
compared to using Shiro's {@link Hash}
+ * implementations directly.  See the {@link #getSalt() getHashSalt()} JavaDoc 
for further explanation.
+ *
+ * @since 1.2
+ */
+public interface HashResponse {
+
+    /**
+     * Returns the hashed data returned by the {@link Hasher}.
+     *
+     * @return the hashed data returned by the {@link Hasher}.
+     */
+    Hash getHash();
+
+    /**
+     * Returns a salt used by the servicing {@link Hasher} when hashing the 
input source.  This same salt must be
+     * presented back to the {@code Hasher} if hash comparison/verification 
will be performed (for example, for
+     * password hash or file checksum comparisons).
+     * <p/>
+     * Note that the salt returned from this method <em>MAY NOT</em> be the 
exact same salt used to compute the
+     * {@link #getHash() hash}.  Such a thing is common when performing 
password hashes for example: if the
+     * {@code Hasher} uses internal/private salt data in addition to a 
specified or random salt, the complete salt
+     * should not be accessible with the password hash.  If it was, brute 
force attacks could more easily
+     * compromise passwords.  If part of the salt was not accessible to an 
attacker (because it is not stored with the
+     * password), brute-force attacks are <em>much</em> harder to execute.
+     * </p>
+     * This scenario emphasizes that any salt returned from this method should 
be re-supplied to the same {@code Hasher}
+     * that computed the original hash if performing comparison/verification.  
The alternative of, say, using a
+     * Shiro {@link Hash} implementation directly to perform hash comparisons 
will likely fail.
+     * <p/>
+     * In summary, if a {@link Hasher} returns a salt in a response, it is 
expected that the same salt
+     * will be provided to the same {@code Hasher} instance.
+     *
+     * @return salt a salt used by the {@link Hasher} when hashing the input 
source.
+     */
+    ByteSource getSalt();
+
+}

Added: 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/Hasher.java?rev=1088844&view=auto
==============================================================================
--- shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hasher.java 
(added)
+++ shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hasher.java Tue 
Apr  5 01:47:41 2011
@@ -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.hash;
+
+/**
+ * A {@code Hasher} 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
+ * 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/>
+ * For example, for the most secure hashes, it is highly recommended to use a 
randomly generated salt, potentially
+ * paired with an configuration-specific private salt, in addition to using 
multiple hash iterations.
+ * <p/>
+ * While one can do this easily enough using Shiro's {@link Hash} 
implementations directly, this direct approach could
+ * 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);
+ * </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());
+ * </pre>
+ *
+ * @since 1.2
+ */
+public interface Hasher {
+
+    /**
+     * Computes a hash based on the given request.
+     * <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.
+     *
+     * @param request the request to process
+     * @return the hashed data as a {@code HashResponse}
+     * @see org.apache.shiro.crypto.hash.HashResponse#getSalt()
+     */
+    HashResponse computeHash(HashRequest request);
+
+}

Added: 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java
URL: 
http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java?rev=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java
 (added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashRequest.java
 Tue Apr  5 01:47:41 2011
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * Simple implementation of {@link HashRequest} that retains the {@link 
#getSource source} and
+ * {@link #getSalt salt} properties as private attributes.
+ *
+ * @since 1.2
+ */
+public class SimpleHashRequest implements HashRequest {
+
+    private final ByteSource source;
+    private final ByteSource salt;
+
+    /**
+     * Creates a new {@code SimpleHashRequest} with the specified source to be 
hashed.
+     *
+     * @param source the source data to be hashed
+     * @throws NullPointerException if the specified {@code source} argument 
is {@code null}.
+     */
+    public SimpleHashRequest(ByteSource source) throws NullPointerException {
+        this(source, null);
+    }
+
+    /**
+     * Creates a new {@code SimpleHashRequest} with the specified source and 
salt.
+     *
+     * @param source the source data to be hashed
+     * @param salt   a salt a salt to be used by the {@link Hasher} during 
hash computation.
+     * @throws NullPointerException if the specified {@code source} argument 
is {@code null}.
+     */
+    public SimpleHashRequest(ByteSource source, ByteSource salt) throws 
NullPointerException {
+        this.source = source;
+        this.salt = salt;
+        if (source == null) {
+            throw new NullPointerException("source argument cannot be null.");
+        }
+    }
+
+    public ByteSource getSource() {
+        return this.source;
+    }
+
+    public ByteSource getSalt() {
+        return this.salt;
+    }
+}

Added: 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashResponse.java
URL: 
http://svn.apache.org/viewvc/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashResponse.java?rev=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashResponse.java
 (added)
+++ 
shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/SimpleHashResponse.java
 Tue Apr  5 01:47:41 2011
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.util.ByteSource;
+
+/**
+ * Simple implementation of {@link HashResponse} that retains the {@link 
#getHash hash} and
+ * {@link #getSalt hashSalt} properties as private attributes.
+ *
+ * @since 1.2
+ */
+public class SimpleHashResponse implements HashResponse {
+
+    private final Hash hash;
+    private final ByteSource salt;
+
+    /**
+     * Constructs a new instance with the specified hash and salt.
+     *
+     * @param hash the hash to respond with.
+     * @param salt the public salt associated with the specified hash.
+     */
+    public SimpleHashResponse(Hash hash, ByteSource salt) {
+        this.hash = hash;
+        this.salt = salt;
+    }
+
+    public Hash getHash() {
+        return this.hash;
+    }
+
+    public ByteSource getSalt() {
+        return this.salt;
+    }
+}

Added: 
shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHasherTest.groovy
URL: 
http://svn.apache.org/viewvc/shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHasherTest.groovy?rev=1088844&view=auto
==============================================================================
--- 
shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHasherTest.groovy
 (added)
+++ 
shiro/trunk/core/src/test/groovy/org/apache/shiro/crypto/hash/DefaultHasherTest.groovy
 Tue Apr  5 01:47:41 2011
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto.hash
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: lhazlewood
+ * Date: 4/3/11
+ * Time: 3:21 AM
+ * To change this template use File | Settings | File Templates.
+ */
+class DefaultHasherTest extends GroovyTestCase {
+
+    void testDefault() {
+        /*DefaultHasher hasher = new DefaultHasher()
+        SecureRandomNumberGenerator rng = new SecureRandomNumberGenerator();
+
+        long n = 200000
+        def times = [n]
+        for (int i = 0; i < n; i++) {
+            long start = System.currentTimeMillis()
+            hasher.computeHash new SimpleByteSource(rng.nextBytes(16))
+            long stop = System.currentTimeMillis()
+            times << stop - start
+
+        }
+
+        long total = 0;
+        for( long l : times) {
+            total += l
+        }
+        System.out.println(total / n); */
+    }
+}


Reply via email to