View Javadoc

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