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); */
+ }
+}