001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.crypto.hash; 020 021import java.security.SecureRandom; 022import java.util.Map; 023import java.util.Optional; 024import java.util.Random; 025 026/** 027 * Default implementation of the {@link HashService} interface, supporting a customizable hash algorithm name. 028 * <h2>Hash Algorithm</h2> 029 * You may specify a hash algorithm via the {@link #setDefaultAlgorithmName(String)} property. Any algorithm name 030 * understood by the JDK 031 * {@link java.security.MessageDigest#getInstance(String) MessageDigest.getInstance(String algorithmName)} method 032 * will work, or any Hash algorithm implemented by any loadable {@link HashSpi}. The default is {@code argon2}. 033 * </p> 034 * A hash and the salt used to compute it are often stored together. If an attacker is ever able to access 035 * the hash (e.g. during password cracking) and it has the full salt value, the attacker has all of the input necessary 036 * to try to brute-force crack the hash (source + complete salt). 037 * <p/> 038 * However, if part of the salt is not available to the attacker (because it is not stored with the hash), it is 039 * <em>much</em> harder to crack the hash value since the attacker does not have the complete inputs necessary. 040 * <p/> 041 * 042 * @since 1.2 043 */ 044public class DefaultHashService implements ConfigurableHashService { 045 046 private final Random random; 047 048 /** 049 * The MessageDigest name of the hash algorithm to use for computing hashes. 050 */ 051 private String defaultAlgorithmName; 052 053 private Map<String, Object> parameters = Map.of(); 054 055 /** 056 * Constructs a new {@code DefaultHashService} instance with the following defaults: 057 * <ul> 058 * <li>{@link #setDefaultAlgorithmName(String) hashAlgorithmName} = {@code SHA-512}</li> 059 * </ul> 060 */ 061 public DefaultHashService() { 062 this.random = new SecureRandom(); 063 this.defaultAlgorithmName = "argon2"; 064 } 065 066 /** 067 * Computes and responds with a hash based on the specified request. 068 * <p/> 069 * This implementation functions as follows: 070 * <ul> 071 * <li>If the request's {@link org.apache.shiro.crypto.hash.HashRequest#getSalt() salt} is null: 072 * <p/> 073 * A salt will be generated and used to compute the hash. The salt is generated as follows: 074 * <ol> 075 * <li>Use the combined value as the salt used during hash computation</li> 076 * </ol> 077 * </li> 078 * <li> 079 * 080 * @param request the request to process 081 * @return the response containing the result of the hash computation, as well as any hash salt used that should be 082 * exposed to the caller. 083 */ 084 @Override 085 public Hash computeHash(HashRequest request) { 086 if (request == null || request.getSource() == null || request.getSource().isEmpty()) { 087 return null; 088 } 089 090 String algorithmName = getAlgorithmName(request); 091 092 Optional<HashSpi> kdfHash = HashProvider.getByAlgorithmName(algorithmName); 093 if (kdfHash.isPresent()) { 094 HashSpi hashSpi = kdfHash.get(); 095 096 return hashSpi.newHashFactory(random).generate(request); 097 } 098 099 throw new UnsupportedOperationException("Cannot create a hash with the given algorithm: " + algorithmName); 100 } 101 102 103 protected String getAlgorithmName(HashRequest request) { 104 return request.getAlgorithmName().orElseGet(this::getDefaultAlgorithmName); 105 } 106 107 @Override 108 public void setDefaultAlgorithmName(String name) { 109 this.defaultAlgorithmName = name; 110 } 111 112 @Override 113 public String getDefaultAlgorithmName() { 114 return this.defaultAlgorithmName; 115 } 116 117 @Override 118 public Map<String, Object> getParameters() { 119 return Map.copyOf(this.parameters); 120 } 121 122 @Override 123 public void setParameters(Map<String, Object> parameters) { 124 this.parameters = Map.copyOf(parameters); 125 } 126}