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