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 }