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