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.SimpleHash;
20 
21 import hunt.shiro.crypto.hash.AbstractHash;
22 import hunt.shiro.crypto.hash.Hash;
23 
24 import hunt.shiro.Exceptions;
25 import hunt.shiro.codec.Base64;
26 import hunt.shiro.codec.Hex;
27 import hunt.shiro.util.ByteSource;
28 import hunt.shiro.util.SimpleByteSource;
29 // import hunt.shiro.util.StringUtils;
30 
31 // import hunt.security.MessageDigest;
32 // import java.security.NoSuchAlgorithmException;
33 
34 import hunt.Exceptions;
35 import hunt.util.ArrayHelper;
36 
37 import std.algorithm;
38 import std.array;
39 
40 /**
41  * A {@code Hash} implementation that allows any {@link java.security.MessageDigest MessageDigest} algorithm name to
42  * be used.  This class is a less type-safe variant than the other {@code AbstractHash} subclasses
43  * (e.g. {@link Sha512Hash}, etc), but it does allow for any algorithm name to be specified in case the other subclass
44  * implementations do not represent an algorithm that you may want to use.
45  * <p/>
46  * As of Shiro 1.1, this class effectively replaces the (now-deprecated) {@link AbstractHash} class.  It subclasses
47  * {@code AbstractHash} only to retain backwards-compatibility.
48  *
49  */
50 class SimpleHash : AbstractHash {
51 
52     private enum int DEFAULT_ITERATIONS = 1;
53 
54     /**
55      * The {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
56      */
57     private string algorithmName;
58 
59     /**
60      * The hashed data
61      */
62    private byte[] bytes;
63 
64     /**
65      * Supplied salt, if any.
66      */
67     private ByteSource salt;
68 
69     /**
70      * Number of hash iterations to perform.  Defaults to 1 in the constructor.
71      */
72     private int iterations;
73 
74     /**
75      * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead.
76      */
77     private string hexEncoded = null;
78 
79     /**
80      * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead.
81      */
82     private string base64Encoded = null;
83 
84     /**
85      * Creates an new instance with only its {@code algorithmName} set - no hashing is performed.
86      * <p/>
87      * Because all other constructors in this class hash the {@code source} constructor argument, this
88      * constructor is useful in scenarios when you have a byte array that you know is already hashed and
89      * just want to set the bytes in their raw form directly on an instance.  After using this constructor,
90      * you can then immediately call {@link #setBytes setBytes} to have a fully-initialized instance.
91      * <p/>
92      * <b>N.B.</b>The algorithm identified by the {@code algorithmName} parameter must be available on the JVM.  If it
93      * is not, a {@link UnknownAlgorithmException} will be thrown when the hash is performed (not at instantiation).
94      *
95      * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
96      *                      performing the hash.
97      * @see UnknownAlgorithmException
98      */
99     this(string algorithmName) {
100         this.algorithmName = algorithmName;
101         this.iterations = DEFAULT_ITERATIONS;
102     }
103 
104     /**
105      * Creates an {@code algorithmName}-specific hash of the specified {@code source} with no {@code salt} using a
106      * single hash iteration.
107      * <p/>
108      * This is a convenience constructor that merely executes <code>this( algorithmName, source, null, 1);</code>.
109      * <p/>
110      * Please see the
111      * {@link #SimpleHash(string algorithmName, Object source, Object salt, int numIterations) SimpleHashHash(algorithmName, Object,Object,int)}
112      * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
113      * types.
114      *
115      * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
116      *                      performing the hash.
117      * @param source        the object to be hashed.
118      * @throws hunt.shiro.codec.CodecException
119      *                                   if the specified {@code source} cannot be converted into a byte array (byte[]).
120      * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
121      */
122     this(string algorithmName, Object source) {
123         //noinspection NullableProblems
124         this(algorithmName, source, null, DEFAULT_ITERATIONS);
125     }
126 
127     /**
128      * Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given {@code salt}
129      * using a single hash iteration.
130      * <p/>
131      * It is a convenience constructor that merely executes <code>this( algorithmName, source, salt, 1);</code>.
132      * <p/>
133      * Please see the
134      * {@link #SimpleHash(string algorithmName, Object source, Object salt, int numIterations) SimpleHashHash(algorithmName, Object,Object,int)}
135      * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
136      * types.
137      *
138      * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
139      *                      performing the hash.
140      * @param source        the source object to be hashed.
141      * @param salt          the salt to use for the hash
142      * @throws CodecException            if either constructor argument cannot be converted into a byte array.
143      * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
144      */
145     this(string algorithmName, Object source, Object salt) {
146         this(algorithmName, source, salt, DEFAULT_ITERATIONS);
147     }
148 
149     /**
150      * Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given
151      * {@code salt} a total of {@code hashIterations} times.
152      * <p/>
153      * By default, this class only supports Object method arguments of
154      * type {@code byte[]}, {@code char[]}, {@link string}, {@link java.io.File File},
155      * {@link java.io.InputStream InputStream} or {@link hunt.shiro.util.ByteSource ByteSource}.  If either
156      * argument is anything other than these types a {@link hunt.shiro.codec.CodecException CodecException}
157      * will be thrown.
158      * <p/>
159      * If you want to be able to hash other object types, or use other salt types, you need to override the
160      * {@link #toBytes(Object) toBytes(Object)} method to support those specific types.  Your other option is to
161      * convert your arguments to one of the default supported types first before passing them in to this
162      * constructor}.
163      *
164      * @param algorithmName  the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
165      *                       performing the hash.
166      * @param source         the source object to be hashed.
167      * @param salt           the salt to use for the hash
168      * @param hashIterations the number of times the {@code source} argument hashed for attack resiliency.
169      * @throws CodecException            if either Object constructor argument cannot be converted into a byte array.
170      * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
171      */
172     this(string algorithmName, Object source, Object salt, int hashIterations) {
173         if (!algorithmName.empty()) {
174             throw new NullPointerException("algorithmName argument cannot be null or empty.");
175         }
176         this.algorithmName = algorithmName;
177         this.iterations = max(DEFAULT_ITERATIONS, hashIterations);
178         ByteSource saltBytes = null;
179         if (salt !is null) {
180             saltBytes = convertSaltToBytes(salt);
181             this.salt = saltBytes;
182         }
183         ByteSource sourceBytes = convertSourceToBytes(source);
184         hash(sourceBytes, saltBytes, hashIterations);
185     }
186 
187     /**
188      * Acquires the specified {@code source} argument's bytes and returns them in the form of a {@code ByteSource} instance.
189      * <p/>
190      * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic
191      * conversion.  Can be overridden by subclasses for source-specific conversion.
192      *
193      * @param source the source object to be hashed.
194      * @return the source's bytes in the form of a {@code ByteSource} instance.
195      */
196     protected ByteSource convertSourceToBytes(Object source) {
197         return toByteSource(source);
198     }
199 
200     /**
201      * Acquires the specified {@code salt} argument's bytes and returns them in the form of a {@code ByteSource} instance.
202      * <p/>
203      * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic
204      * conversion.  Can be overridden by subclasses for salt-specific conversion.
205      *
206      * @param salt the salt to be use for the hash.
207      * @return the salt's bytes in the form of a {@code ByteSource} instance.
208      */
209     protected ByteSource convertSaltToBytes(Object salt) {
210         return toByteSource(salt);
211     }
212 
213     /**
214      * Converts a given object into a {@code ByteSource} instance.  Assumes the object can be converted to bytes.
215      *
216      * @param o the Object to convert into a {@code ByteSource} instance.
217      * @return the {@code ByteSource} representation of the specified object's bytes.
218      */
219     protected ByteSource toByteSource(Object o) {
220         if (o  is null) {
221             return null;
222         }
223         auto oCast = cast(ByteSource)o;
224         if (oCast !is null) {
225             return oCast;
226         }
227         byte[] bytes = toBytes(o);
228         return ByteSourceUtil.bytes(bytes);
229     }
230 
231     private void hash(ByteSource source, ByteSource salt, int hashIterations) {
232         byte[] saltBytes = salt !is null ? salt.getBytes() : null;
233         byte[] hashedBytes = hash(source.getBytes(), saltBytes, hashIterations);
234         setBytes(hashedBytes);
235     }
236 
237     /**
238      * Returns the {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
239      *
240      * @return the {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
241      */
242     string getAlgorithmName() {
243         return this.algorithmName;
244     }
245 
246     ByteSource getSalt() {
247         return this.salt;
248     }
249 
250     int getIterations() {
251         return this.iterations;
252     }
253 
254     byte[] getBytes() {
255         return this.bytes;
256     }
257 
258     /**
259      * Sets the raw bytes stored by this hash instance.
260      * <p/>
261      * The bytes are kept in raw form - they will not be hashed/changed.  This is primarily a utility method for
262      * constructing a Hash instance when the hashed value is already known.
263      *
264      * @param alreadyHashedBytes the raw already-hashed bytes to store in this instance.
265      */
266     void setBytes(byte[] alreadyHashedBytes) {
267         this.bytes = alreadyHashedBytes;
268         this.hexEncoded = null;
269         this.base64Encoded = null;
270     }
271 
272     /**
273      * Sets the iterations used to previously compute AN ALREADY GENERATED HASH.
274      * <p/>
275      * This is provided <em>ONLY</em> to reconstitute an already-created Hash instance.  It should ONLY ever be
276      * invoked when re-constructing a hash instance from an already-hashed value.
277      *
278      * @param iterations the number of hash iterations used to previously create the hash/digest.
279      */
280     void setIterations(int iterations) {
281         this.iterations = max(DEFAULT_ITERATIONS, iterations);
282     }
283 
284     /**
285      * Sets the salt used to previously compute AN ALREADY GENERATED HASH.
286      * <p/>
287      * This is provided <em>ONLY</em> to reconstitute a Hash instance that has already been computed.  It should ONLY
288      * ever be invoked when re-constructing a hash instance from an already-hashed value.
289      *
290      * @param salt the salt used to previously create the hash/digest.
291      */
292     void setSalt(ByteSource salt) {
293         this.salt = salt;
294     }
295 
296     /**
297      * Returns the JDK MessageDigest instance to use for executing the hash.
298      *
299      * @param algorithmName the algorithm to use for the hash, provided by subclasses.
300      * @return the MessageDigest object for the specified {@code algorithm}.
301      * @throws UnknownAlgorithmException if the specified algorithm name is not available.
302      */
303     // protected MessageDigest getDigest(string algorithmName) {
304     //     try {
305     //         return MessageDigest.getInstance(algorithmName);
306     //     } catch (NoSuchAlgorithmException e) {
307     //         string msg = "No native '" ~ algorithmName ~ "' MessageDigest instance available on the current JVM.";
308     //         throw new UnknownAlgorithmException(msg, e);
309     //     }
310     // }
311 
312     /**
313      * Hashes the specified byte array without a salt for a single iteration.
314      *
315      * @param bytes the bytes to hash.
316      * @return the hashed bytes.
317      * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
318      */
319    protected byte[] hash(byte[] bytes) {
320         return hash(bytes, null, DEFAULT_ITERATIONS);
321     }
322 
323     /**
324      * Hashes the specified byte array using the given {@code salt} for a single iteration.
325      *
326      * @param bytes the bytes to hash
327      * @param salt  the salt to use for the initial hash
328      * @return the hashed bytes
329      * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
330      */
331    protected byte[] hash(byte[] bytes, byte[] salt) {
332         return hash(bytes, salt, DEFAULT_ITERATIONS);
333     }
334 
335     /**
336      * Hashes the specified byte array using the given {@code salt} for the specified number of iterations.
337      *
338      * @param bytes          the bytes to hash
339      * @param salt           the salt to use for the initial hash
340      * @param hashIterations the number of times the the {@code bytes} will be hashed (for attack resiliency).
341      * @return the hashed bytes.
342      * @throws UnknownAlgorithmException if the {@link #getAlgorithmName() algorithmName} is not available.
343      */
344    protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) {
345         // MessageDigest digest = getDigest(getAlgorithmName());
346         // if (salt !is null) {
347         //     digest.reset();
348         //     digest.update(salt);
349         // }
350         // byte[] hashed = digest.digest(bytes);
351         // int iterations = hashIterations - 1; //already hashed once above
352         // //iterate remaining number:
353         // for (int i = 0; i < iterations; i++) {
354         //     digest.reset();
355         //     hashed = digest.digest(hashed);
356         // }
357         // return hashed;
358         implementationMissing(false);
359         return null;
360     }
361 
362     bool isEmpty() {
363         return this.bytes  is null || this.bytes.length == 0;
364     }
365 
366     /**
367      * Returns a hex-encoded string of the underlying {@link #getBytes byte array}.
368      * <p/>
369      * This implementation caches the resulting hex string so multiple calls to this method remain efficient.
370      * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
371      * next time this method is called.
372      *
373      * @return a hex-encoded string of the underlying {@link #getBytes byte array}.
374      */
375     string toHex() {
376         if (this.hexEncoded  is null) {
377             this.hexEncoded = Hex.encodeToString(getBytes());
378         }
379         return this.hexEncoded;
380     }
381 
382     /**
383      * Returns a Base64-encoded string of the underlying {@link #getBytes byte array}.
384      * <p/>
385      * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient.
386      * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
387      * next time this method is called.
388      *
389      * @return a Base64-encoded string of the underlying {@link #getBytes byte array}.
390      */
391     string toBase64() {
392         if (this.base64Encoded  is null) {
393             //cache result in case this method is called multiple times.
394             this.base64Encoded = Base64.encodeToString(getBytes());
395         }
396         return this.base64Encoded;
397     }
398 
399     /**
400      * Simple implementation that merely returns {@link #toHex() toHex()}.
401      *
402      * @return the {@link #toHex() toHex()} value.
403      */
404     override string toString() {
405         return toHex();
406     }
407 
408     /**
409      * Returns {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
410      * this Hash's byte array, {@code false} otherwise.
411      *
412      * @param o the object (Hash) to check for equality.
413      * @return {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
414      *         this Hash's byte array, {@code false} otherwise.
415      */
416     override bool opEquals(Object o) {
417         auto oCast = cast(Hash) o;
418         if (oCast !is null) {
419             Hash other = oCast;
420             // return MessageDigest.isEqual(getBytes(), other.getBytes());
421             implementationMissing(false);
422             return false;
423         }
424         return false;
425     }
426 
427     /**
428      * Simply returns toHex().hashCode();
429      *
430      * @return toHex().hashCode()
431      */
432     override size_t toHash() @trusted nothrow {
433         if (this.bytes  is null || this.bytes.length == 0) {
434             return 0;
435         }
436         return hashOf(this.bytes);
437     }
438 }