DefaultHashService.java
/*
* 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;
/**
* Default implementation of the {@link HashService} interface, supporting a customizable hash algorithm name,
* secure-random salt generation, multiple hash iterations and an optional internal
* {@link #setPrivateSalt(ByteSource) privateSalt}.
* <h2>Hash Algorithm</h2>
* You may specify a hash algorithm via the {@link #setHashAlgorithmName(String)} property. Any algorithm name
* understood by the JDK
* {@link java.security.MessageDigest#getInstance(String) MessageDigest.getInstance(String algorithmName)} method
* will work. The default is {@code SHA-512}.
* <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 (and potentially combined with the internal {@link #getPrivateSalt() privateSalt}) is a very strong
* salting strategy, as salts should ideally never be based on known/guessable data. The default instance is a
* {@link SecureRandomNumberGenerator}.
* <h2>Hash Iterations</h2>
* Secure hashing strategies often employ multiple hash iterations to slow down the hashing process. This technique
* is usually used for password hashing, since the longer it takes to compute a password hash, the longer it would
* take for an attacker to compromise a password. This
* <a href="http://www.stormpath.com/blog/strong-password-hashing-apache-shiro">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 if the {@code HashService} is intended to be used for password
* hashing. See the linked blog article for more info.
* <h2>Private Salt</h2>
* If using this implementation as part of a password hashing strategy, it might be desirable to configure a
* {@link #setPrivateSalt(ByteSource) private salt}:
* <p/>
* A hash and the salt used to compute it are often stored together. If an attacker is ever able to access
* the hash (e.g. during password cracking) and it has the full salt value, the attacker has all of the input necessary
* 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 #getPrivateSalt() privateSalt} property exists to satisfy this private-and-not-shared part of the salt.
* If you configure this attribute, you can obtain this additional very important safety feature.
* <p/>
* <b>*</b>By default, the {@link #getPrivateSalt() privateSalt} is null, since a sensible default cannot be used that
* isn't easily compromised (because Shiro is an open-source project and any default could be easily seen and used).
*
* @since 1.2
*/
public class DefaultHashService implements ConfigurableHashService {
/**
* 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 ByteSource privateSalt;
/**
* The number of hash iterations to perform when computing hashes.
*/
private int iterations;
/**
* Whether or not to generate public salts if a request does not provide one.
*/
private boolean generatePublicSalt;
/**
* Constructs a new {@code DefaultHashService} instance with the following defaults:
* <ul>
* <li>{@link #setHashAlgorithmName(String) hashAlgorithmName} = {@code SHA-512}</li>
* <li>{@link #setHashIterations(int) hashIterations} = {@code 1}</li>
* <li>{@link #setRandomNumberGenerator(org.apache.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} =
* new {@link SecureRandomNumberGenerator}()</li>
* <li>{@link #setGeneratePublicSalt(boolean) generatePublicSalt} = {@code false}</li>
* </ul>
* <p/>
* If this hashService will be used for password hashing it is recommended to set the
* {@link #setPrivateSalt(ByteSource) privateSalt} and significantly increase the number of
* {@link #setHashIterations(int) hashIterations}. See the class-level JavaDoc for more information.
*/
public DefaultHashService() {
this.algorithmName = "SHA-512";
this.iterations = 1;
this.generatePublicSalt = false;
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(ByteSource, ByteSource) combine} this random salt with any configured
* {@link #getPrivateSalt() privateSalt}
* </li>
* <li>Use the combined value as the salt used during hash computation</li>
* </ol>
* </li>
* <li>
* If the request 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(ByteSource, ByteSource) combined} with any
* configured {@link #getPrivateSalt() privateSalt} and used as the complete salt during hash computation.
* </li>
* </ul>
* <p/>
* The returned {@code Hash}'s {@link Hash#getSalt() salt} property
* will contain <em>only</em> the 'public' part of the salt and <em>NOT</em> the privateSalt. See the class-level
* JavaDoc explanation for more info.
*
* @param request the request to process
* @return the response containing the result of the hash computation, as well as any hash salt used that should be
* exposed to the caller.
*/
public Hash computeHash(HashRequest request) {
if (request == null || request.getSource() == null || request.getSource().isEmpty()) {
return null;
}
String algorithmName = getAlgorithmName(request);
ByteSource source = request.getSource();
int iterations = getIterations(request);
ByteSource publicSalt = getPublicSalt(request);
ByteSource privateSalt = getPrivateSalt();
ByteSource salt = combine(privateSalt, publicSalt);
Hash computed = new SimpleHash(algorithmName, source, salt, iterations);
SimpleHash result = new SimpleHash(algorithmName);
result.setBytes(computed.getBytes());
result.setIterations(iterations);
//Only expose the public salt - not the real/combined salt that might have been used:
result.setSalt(publicSalt);
return result;
}
protected String getAlgorithmName(HashRequest request) {
String name = request.getAlgorithmName();
if (name == null) {
name = getHashAlgorithmName();
}
return name;
}
protected int getIterations(HashRequest request) {
int iterations = Math.max(0, request.getIterations());
if (iterations < 1) {
iterations = Math.max(1, getHashIterations());
}
return iterations;
}
/**
* Returns the public salt that should be used to compute a hash based on the specified request or
* {@code null} if no public salt should be used.
* <p/>
* This implementation functions as follows:
* <ol>
* <li>If the request salt is not null and non-empty, this will be used, return it.</li>
* <li>If the request salt is null or empty:
* <ol>
* <li>If a private salt has been set <em>OR</em> {@link #isGeneratePublicSalt()} is {@code true},
* auto generate a random public salt via the configured
* {@link #getRandomNumberGenerator() randomNumberGenerator}.</li>
* <li>If a private salt has not been configured and {@link #isGeneratePublicSalt()} is {@code false},
* do nothing - return {@code null} to indicate a salt should not be used during hash computation.</li>
* </ol>
* </li>
* </ol>
*
* @param request request the request to process
* @return the public salt that should be used to compute a hash based on the specified request or
* {@code null} if no public salt should be used.
*/
protected ByteSource getPublicSalt(HashRequest request) {
ByteSource publicSalt = request.getSalt();
if (publicSalt != null && !publicSalt.isEmpty()) {
//a public salt was explicitly requested to be used - go ahead and use it:
return publicSalt;
}
publicSalt = null;
//check to see if we need to generate one:
ByteSource privateSalt = getPrivateSalt();
boolean privateSaltExists = privateSalt != null && !privateSalt.isEmpty();
//If a private salt exists, we must generate a public salt to protect the integrity of the private salt.
//Or generate it if the instance is explicitly configured to do so:
if (privateSaltExists || isGeneratePublicSalt()) {
publicSalt = getRandomNumberGenerator().nextBytes();
}
return publicSalt;
}
/**
* Combines the specified 'private' salt bytes with the specified additional extra bytes to use as the
* total salt during hash computation. {@code privateSaltBytes} will be {@code null} }if no private salt has been
* configured.
*
* @param privateSalt the (possibly {@code null}) 'private' salt to combine with the specified extra bytes
* @param publicSalt the extra bytes to use in addition to the given private salt.
* @return a combination of the specified private salt bytes and extra bytes that will be used as the total
* salt during hash computation.
*/
protected ByteSource combine(ByteSource privateSalt, ByteSource publicSalt) {
byte[] privateSaltBytes = privateSalt != null ? privateSalt.getBytes() : null;
int privateSaltLength = privateSaltBytes != null ? privateSaltBytes.length : 0;
byte[] publicSaltBytes = publicSalt != null ? publicSalt.getBytes() : null;
int extraBytesLength = publicSaltBytes != null ? publicSaltBytes.length : 0;
int length = privateSaltLength + extraBytesLength;
if (length <= 0) {
return null;
}
byte[] combined = new byte[length];
int i = 0;
for (int j = 0; j < privateSaltLength; j++) {
assert privateSaltBytes != null;
combined[i++] = privateSaltBytes[j];
}
for (int j = 0; j < extraBytesLength; j++) {
assert publicSaltBytes != null;
combined[i++] = publicSaltBytes[j];
}
return ByteSource.Util.bytes(combined);
}
public void setHashAlgorithmName(String name) {
this.algorithmName = name;
}
public String getHashAlgorithmName() {
return this.algorithmName;
}
public void setPrivateSalt(ByteSource privateSalt) {
this.privateSalt = privateSalt;
}
public ByteSource getPrivateSalt() {
return this.privateSalt;
}
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;
}
/**
* Returns {@code true} if a public salt should be randomly generated and used to compute a hash if a
* {@link HashRequest} does not specify a salt, {@code false} otherwise.
* <p/>
* The default value is {@code false} but should definitely be set to {@code true} if the
* {@code HashService} instance is being used for password hashing.
* <p/>
* <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured. If a
* private salt has been configured and a request does not provide a salt, a random salt will always be generated
* to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is,
* which is undesirable).
*
* @return {@code true} if a public salt should be randomly generated and used to compute a hash if a
* {@link HashRequest} does not specify a salt, {@code false} otherwise.
*/
public boolean isGeneratePublicSalt() {
return generatePublicSalt;
}
/**
* Sets whether or not a public salt should be randomly generated and used to compute a hash if a
* {@link HashRequest} does not specify a salt.
* <p/>
* The default value is {@code false} but should definitely be set to {@code true} if the
* {@code HashService} instance is being used for password hashing.
* <p/>
* <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured. If a
* private salt has been configured and a request does not provide a salt, a random salt will always be generated
* to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is,
* which is undesirable).
*
* @param generatePublicSalt whether or not a public salt should be randomly generated and used to compute a hash
* if a {@link HashRequest} does not specify a salt.
*/
public void setGeneratePublicSalt(boolean generatePublicSalt) {
this.generatePublicSalt = generatePublicSalt;
}
}