1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module hunt.shiro.crypto.hash.DefaultHashService; 20 21 import hunt.shiro.crypto.hash.ConfigurableHashService; 22 import hunt.shiro.crypto.hash.Hash; 23 import hunt.shiro.crypto.hash.HashRequest; 24 import hunt.shiro.crypto.hash.SimpleHash; 25 26 27 28 import hunt.shiro.crypto.RandomNumberGenerator; 29 import hunt.shiro.crypto.SecureRandomNumberGenerator; 30 import hunt.shiro.util.ByteSource; 31 import hunt.shiro.util.SimpleByteSource; 32 33 import std.algorithm; 34 35 /** 36 * Default implementation of the {@link HashService} interface, supporting a customizable hash algorithm name, 37 * secure-random salt generation, multiple hash iterations and an optional internal 38 * {@link #setPrivateSalt(ByteSource) privateSalt}. 39 * <h2>Hash Algorithm</h2> 40 * You may specify a hash algorithm via the {@link #setHashAlgorithmName(string)} property. Any algorithm name 41 * understood by the JDK 42 * {@link java.security.MessageDigest#getInstance(string) MessageDigest.getInstance(string algorithmName)} method 43 * will work. The default is {@code SHA-512}. 44 * <h2>Random Salts</h2> 45 * When a salt is not specified in a request, this implementation generates secure random salts via its 46 * {@link #setRandomNumberGenerator(hunt.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} property. 47 * Random salts (and potentially combined with the internal {@link #getPrivateSalt() privateSalt}) is a very strong 48 * salting strategy, as salts should ideally never be based on known/guessable data. The default instance is a 49 * {@link SecureRandomNumberGenerator}. 50 * <h2>Hash Iterations</h2> 51 * Secure hashing strategies often employ multiple hash iterations to slow down the hashing process. This technique 52 * is usually used for password hashing, since the longer it takes to compute a password hash, the longer it would 53 * take for an attacker to compromise a password. This 54 * <a href="http://www.stormpath.com/blog/strong-password-hashing-apache-shiro">blog article</a> 55 * explains in greater detail why this is useful, as well as information on how many iterations is 'enough'. 56 * <p/> 57 * You may set the number of hash iterations via the {@link #setHashIterations(int)} property. The default is 58 * {@code 1}, but should be increased significantly if the {@code HashService} is intended to be used for password 59 * hashing. See the linked blog article for more info. 60 * <h2>Private Salt</h2> 61 * If using this implementation as part of a password hashing strategy, it might be desirable to configure a 62 * {@link #setPrivateSalt(ByteSource) private salt}: 63 * <p/> 64 * A hash and the salt used to compute it are often stored together. If an attacker is ever able to access 65 * the hash (e.g. during password cracking) and it has the full salt value, the attacker has all of the input necessary 66 * to try to brute-force crack the hash (source + complete salt). 67 * <p/> 68 * However, if part of the salt is not available to the attacker (because it is not stored with the hash), it is 69 * <em>much</em> harder to crack the hash value since the attacker does not have the complete inputs necessary. 70 * <p/> 71 * The {@link #getPrivateSalt() privateSalt} property exists to satisfy this private-and-not-shared part of the salt. 72 * If you configure this attribute, you can obtain this additional very important safety feature. 73 * <p/> 74 * <b>*</b>By default, the {@link #getPrivateSalt() privateSalt} is null, since a sensible default cannot be used that 75 * isn't easily compromised (because Shiro is an open-source project and any default could be easily seen and used). 76 * 77 */ 78 class DefaultHashService : ConfigurableHashService { 79 80 /** 81 * The RandomNumberGenerator to use to randomly generate the public part of the hash salt. 82 */ 83 private RandomNumberGenerator rng; 84 85 /** 86 * The MessageDigest name of the hash algorithm to use for computing hashes. 87 */ 88 private string algorithmName; 89 90 /** 91 * The 'private' part of the hash salt. 92 */ 93 private ByteSource privateSalt; 94 95 /** 96 * The number of hash iterations to perform when computing hashes. 97 */ 98 private int iterations; 99 100 /** 101 * Whether or not to generate public salts if a request does not provide one. 102 */ 103 private bool generatePublicSalt; 104 105 /** 106 * Constructs a new {@code DefaultHashService} instance with the following defaults: 107 * <ul> 108 * <li>{@link #setHashAlgorithmName(string) hashAlgorithmName} = {@code SHA-512}</li> 109 * <li>{@link #setHashIterations(int) hashIterations} = {@code 1}</li> 110 * <li>{@link #setRandomNumberGenerator(hunt.shiro.crypto.RandomNumberGenerator) randomNumberGenerator} = 111 * new {@link SecureRandomNumberGenerator}()</li> 112 * <li>{@link #setGeneratePublicSalt(bool) generatePublicSalt} = {@code false}</li> 113 * </ul> 114 * <p/> 115 * If this hashService will be used for password hashing it is recommended to set the 116 * {@link #setPrivateSalt(ByteSource) privateSalt} and significantly increase the number of 117 * {@link #setHashIterations(int) hashIterations}. See the class-level JavaDoc for more information. 118 */ 119 this() { 120 this.algorithmName = "SHA-512"; 121 this.iterations = 1; 122 this.generatePublicSalt = false; 123 this.rng = new SecureRandomNumberGenerator(); 124 } 125 126 /** 127 * Computes and responds with a hash based on the specified request. 128 * <p/> 129 * This implementation functions as follows: 130 * <ul> 131 * <li>If the request's {@link hunt.shiro.crypto.hash.HashRequest#getSalt() salt} is null: 132 * <p/> 133 * A salt will be generated and used to compute the hash. The salt is generated as follows: 134 * <ol> 135 * <li>Use the {@link #getRandomNumberGenerator() randomNumberGenerator} to generate a new random number.</li> 136 * <li>{@link #combine(ByteSource, ByteSource) combine} this random salt with any configured 137 * {@link #getPrivateSalt() privateSalt} 138 * </li> 139 * <li>Use the combined value as the salt used during hash computation</li> 140 * </ol> 141 * </li> 142 * <li> 143 * If the request salt is not null: 144 * <p/> 145 * This indicates that the hash computation is for comparison purposes (of a 146 * previously computed hash). The request salt will be {@link #combine(ByteSource, ByteSource) combined} with any 147 * configured {@link #getPrivateSalt() privateSalt} and used as the complete salt during hash computation. 148 * </li> 149 * </ul> 150 * <p/> 151 * The returned {@code Hash}'s {@link Hash#getSalt() salt} property 152 * will contain <em>only</em> the 'public' part of the salt and <em>NOT</em> the privateSalt. See the class-level 153 * JavaDoc explanation for more info. 154 * 155 * @param request the request to process 156 * @return the response containing the result of the hash computation, as well as any hash salt used that should be 157 * exposed to the caller. 158 */ 159 Hash computeHash(HashRequest request) { 160 if (request is null || request.getSource() is null || request.getSource().isEmpty()) { 161 return null; 162 } 163 164 string algorithmName = getAlgorithmName(request); 165 ByteSource source = request.getSource(); 166 int iterations = getIterations(request); 167 168 ByteSource publicSalt = getPublicSalt(request); 169 ByteSource privateSalt = getPrivateSalt(); 170 ByteSource salt = combine(privateSalt, publicSalt); 171 172 Hash computed = new SimpleHash(algorithmName, cast(Object)source, cast(Object)salt, iterations); 173 174 SimpleHash result = new SimpleHash(algorithmName); 175 result.setBytes(computed.getBytes()); 176 result.setIterations(iterations); 177 //Only expose the public salt - not the real/combined salt that might have been used: 178 result.setSalt(publicSalt); 179 180 return result; 181 } 182 183 protected string getAlgorithmName(HashRequest request) { 184 string name = request.getAlgorithmName(); 185 if (name is null) { 186 name = getHashAlgorithmName(); 187 } 188 return name; 189 } 190 191 protected int getIterations(HashRequest request) { 192 int iterations = max(0, request.getIterations()); 193 if (iterations < 1) { 194 iterations = max(1, getHashIterations()); 195 } 196 return iterations; 197 } 198 199 /** 200 * Returns the public salt that should be used to compute a hash based on the specified request or 201 * {@code null} if no public salt should be used. 202 * <p/> 203 * This implementation functions as follows: 204 * <ol> 205 * <li>If the request salt is not null and non-empty, this will be used, return it.</li> 206 * <li>If the request salt is null or empty: 207 * <ol> 208 * <li>If a private salt has been set <em>OR</em> {@link #isGeneratePublicSalt()} is {@code true}, 209 * auto generate a random public salt via the configured 210 * {@link #getRandomNumberGenerator() randomNumberGenerator}.</li> 211 * <li>If a private salt has not been configured and {@link #isGeneratePublicSalt()} is {@code false}, 212 * do nothing - return {@code null} to indicate a salt should not be used during hash computation.</li> 213 * </ol> 214 * </li> 215 * </ol> 216 * 217 * @param request request the request to process 218 * @return the public salt that should be used to compute a hash based on the specified request or 219 * {@code null} if no public salt should be used. 220 */ 221 protected ByteSource getPublicSalt(HashRequest request) { 222 223 ByteSource publicSalt = request.getSalt(); 224 225 if (publicSalt !is null && !publicSalt.isEmpty()) { 226 //a public salt was explicitly requested to be used - go ahead and use it: 227 return publicSalt; 228 } 229 230 publicSalt = null; 231 232 //check to see if we need to generate one: 233 ByteSource privateSalt = getPrivateSalt(); 234 bool privateSaltExists = privateSalt !is null && !privateSalt.isEmpty(); 235 236 //If a private salt exists, we must generate a public salt to protect the integrity of the private salt. 237 //Or generate it if the instance is explicitly configured to do so: 238 if (privateSaltExists || isGeneratePublicSalt()) { 239 publicSalt = getRandomNumberGenerator().nextBytes(); 240 } 241 242 return publicSalt; 243 } 244 245 /** 246 * Combines the specified 'private' salt bytes with the specified additional extra bytes to use as the 247 * total salt during hash computation. {@code privateSaltBytes} will be {@code null} }if no private salt has been 248 * configured. 249 * 250 * @param privateSalt the (possibly {@code null}) 'private' salt to combine with the specified extra bytes 251 * @param publicSalt the extra bytes to use in addition to the given private salt. 252 * @return a combination of the specified private salt bytes and extra bytes that will be used as the total 253 * salt during hash computation. 254 */ 255 protected ByteSource combine(ByteSource privateSalt, ByteSource publicSalt) { 256 257 byte[] privateSaltBytes = privateSalt !is null ? privateSalt.getBytes() : null; 258 int privateSaltLength = privateSaltBytes !is null ? cast(int)privateSaltBytes.length : 0; 259 260 byte[] publicSaltBytes = publicSalt !is null ? publicSalt.getBytes() : null; 261 int extraBytesLength = publicSaltBytes !is null ? cast(int)publicSaltBytes.length : 0; 262 263 int length = privateSaltLength + extraBytesLength; 264 265 if (length <= 0) { 266 return null; 267 } 268 269 byte[] combined = new byte[length]; 270 271 int i = 0; 272 for (int j = 0; j < privateSaltLength; j++) { 273 assert(privateSaltBytes !is null); 274 combined[i++] = privateSaltBytes[j]; 275 } 276 for (int j = 0; j < extraBytesLength; j++) { 277 assert(publicSaltBytes !is null); 278 combined[i++] = publicSaltBytes[j]; 279 } 280 281 return ByteSourceUtil.bytes(combined); 282 } 283 284 void setHashAlgorithmName(string name) { 285 this.algorithmName = name; 286 } 287 288 string getHashAlgorithmName() { 289 return this.algorithmName; 290 } 291 292 void setPrivateSalt(ByteSource privateSalt) { 293 this.privateSalt = privateSalt; 294 } 295 296 ByteSource getPrivateSalt() { 297 return this.privateSalt; 298 } 299 300 void setHashIterations(int count) { 301 this.iterations = count; 302 } 303 304 int getHashIterations() { 305 return this.iterations; 306 } 307 308 void setRandomNumberGenerator(RandomNumberGenerator rng) { 309 this.rng = rng; 310 } 311 312 RandomNumberGenerator getRandomNumberGenerator() { 313 return this.rng; 314 } 315 316 /** 317 * Returns {@code true} if a public salt should be randomly generated and used to compute a hash if a 318 * {@link HashRequest} does not specify a salt, {@code false} otherwise. 319 * <p/> 320 * The default value is {@code false} but should definitely be set to {@code true} if the 321 * {@code HashService} instance is being used for password hashing. 322 * <p/> 323 * <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured. If a 324 * private salt has been configured and a request does not provide a salt, a random salt will always be generated 325 * to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is, 326 * which is undesirable). 327 * 328 * @return {@code true} if a public salt should be randomly generated and used to compute a hash if a 329 * {@link HashRequest} does not specify a salt, {@code false} otherwise. 330 */ 331 bool isGeneratePublicSalt() { 332 return generatePublicSalt; 333 } 334 335 /** 336 * Sets whether or not a public salt should be randomly generated and used to compute a hash if a 337 * {@link HashRequest} does not specify a salt. 338 * <p/> 339 * The default value is {@code false} but should definitely be set to {@code true} if the 340 * {@code HashService} instance is being used for password hashing. 341 * <p/> 342 * <b>NOTE:</b> this property only has an effect if a {@link #getPrivateSalt() privateSalt} is NOT configured. If a 343 * private salt has been configured and a request does not provide a salt, a random salt will always be generated 344 * to protect the integrity of the private salt (without a public salt, the private salt would be exposed as-is, 345 * which is undesirable). 346 * 347 * @param generatePublicSalt whether or not a public salt should be randomly generated and used to compute a hash 348 * if a {@link HashRequest} does not specify a salt. 349 */ 350 void setGeneratePublicSalt(bool generatePublicSalt) { 351 this.generatePublicSalt = generatePublicSalt; 352 } 353 }