All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.shiro.crypto.hash.DefaultHashService Maven / Gradle / Ivy

There is a newer version: 3.9
Show newest version
/*
 * 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}.
 * 

Hash Algorithm

* 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}. *

Random Salts

* 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}. *

Hash Iterations

* 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 * Katasoft blog article * explains in greater detail why this is useful, as well as information on how many iterations is 'enough'. *

* 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. *

Private Salt

* If using this implementation as part of a password hashing strategy, it might be desirable to configure a * {@link #setPrivateSalt(ByteSource) private salt}: *

* 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). *

* However, if part of the salt is not available to the attacker (because it is not stored with the hash), it is * much harder to crack the hash value since the attacker does not have the complete inputs necessary. *

* 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. *

* *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: *

    *
  • {@link #setHashAlgorithmName(String) hashAlgorithmName} = {@code SHA-512}
  • *
  • {@link #setHashIterations(int) hashIterations} = {@code 1}
  • *
  • {@link #setRandomNumberGenerator(org.apache.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} = * new {@link SecureRandomNumberGenerator}()
  • *
  • {@link #setGeneratePublicSalt(boolean) generatePublicSalt} = {@code false}
  • *
*

* 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. *

* This implementation functions as follows: *

    *
  • If the request's {@link org.apache.shiro.crypto.hash.HashRequest#getSalt() salt} is null: *

    * A salt will be generated and used to compute the hash. The salt is generated as follows: *

      *
    1. Use the {@link #getRandomNumberGenerator() randomNumberGenerator} to generate a new random number.
    2. *
    3. {@link #combine(ByteSource, ByteSource) combine} this random salt with any configured * {@link #getPrivateSalt() privateSalt} *
    4. *
    5. Use the combined value as the salt used during hash computation
    6. *
    *
  • *
  • * If the request salt is not null: *

    * 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. *

  • *
*

* The returned {@code Hash}'s {@link Hash#getSalt() salt} property * will contain only the 'public' part of the salt and NOT 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. *

* This implementation functions as follows: *

    *
  1. If the request salt is not null and non-empty, this will be used, return it.
  2. *
  3. If the request salt is null or empty: *
      *
    1. If a private salt has been set OR {@link #isGeneratePublicSalt()} is {@code true}, * auto generate a random public salt via the configured * {@link #getRandomNumberGenerator() randomNumberGenerator}.
    2. *
    3. 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.
    4. *
    *
  4. *
* * @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. *

* 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. *

* NOTE: 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. *

* 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. *

* NOTE: 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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy