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