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.CodecSupport;
24  import org.apache.shiro.codec.Hex;
25  import org.apache.shiro.crypto.UnknownAlgorithmException;
26  
27  import java.io.Serializable;
28  import java.security.MessageDigest;
29  import java.security.NoSuchAlgorithmException;
30  import java.util.Arrays;
31  
32  /**
33   * Provides a base for all Shiro Hash algorithms with support for salts and multiple hash iterations.
34   * <p/>
35   * Read
36   * <a href="http://www.owasp.org/index.php/Hashing_Java" target="blank">http://www.owasp.org/index.php/Hashing_Java</a>
37   * for a good article on the benefits of hashing, including what a 'salt' is as well as why it and multiple hash
38   * iterations can be useful.
39   * <p/>
40   * This class and its subclasses support hashing with additional capabilities of salting and multiple iterations via
41   * overloaded constructors.
42   *
43   * @since 0.9
44   * @deprecated in Shiro 1.1 in favor of using the concrete {@link SimpleHash} implementation directly.
45   */
46  @Deprecated
47  public abstract class AbstractHash extends CodecSupport implements Hash, Serializable {
48  
49      /**
50       * The hashed data
51       */
52      private byte[] bytes = null;
53  
54      /**
55       * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead.
56       */
57      private transient String hexEncoded = null;
58      /**
59       * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead.
60       */
61      private transient String base64Encoded = null;
62  
63      /**
64       * Creates an new instance without any of its properties set (no hashing is performed).
65       * <p/>
66       * Because all constructors in this class (except this one) hash the {@code source} constructor argument, this
67       * default, no-arg constructor is useful in scenarios when you have a byte array that you know is already hashed and
68       * just want to set the bytes in their raw form directly on an instance.  After instantiating the instance with
69       * this default, no-arg constructor, you can then immediately call {@link #setBytes setBytes} to have a
70       * fully-initialized instance.
71       */
72      public AbstractHash() {
73      }
74  
75      /**
76       * Creates a hash of the specified {@code source} with no {@code salt} using a single hash iteration.
77       * <p/>
78       * It is a convenience constructor that merely executes <code>this( source, null, 1);</code>.
79       * <p/>
80       * Please see the
81       * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
82       * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
83       * types.
84       *
85       * @param source the object to be hashed.
86       * @throws CodecException if the specified {@code source} cannot be converted into a byte array (byte[]).
87       */
88      public AbstractHash(Object source) throws CodecException {
89          this(source, null, 1);
90      }
91  
92      /**
93       * Creates a hash of the specified {@code source} using the given {@code salt} using a single hash iteration.
94       * <p/>
95       * It is a convenience constructor that merely executes <code>this( source, salt, 1);</code>.
96       * <p/>
97       * Please see the
98       * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
99       * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
100      * types.
101      *
102      * @param source the source object to be hashed.
103      * @param salt   the salt to use for the hash
104      * @throws CodecException if either constructor argument cannot be converted into a byte array.
105      */
106     public AbstractHash(Object source, Object salt) throws CodecException {
107         this(source, salt, 1);
108     }
109 
110     /**
111      * Creates a hash of the specified {@code source} using the given {@code salt} a total of
112      * {@code hashIterations} times.
113      * <p/>
114      * By default, this class only supports Object method arguments of
115      * type {@code byte[]}, {@code char[]}, {@link String}, {@link java.io.File File}, or
116      * {@link java.io.InputStream InputStream}.  If either argument is anything other than these
117      * types a {@link org.apache.shiro.codec.CodecException CodecException} will be thrown.
118      * <p/>
119      * If you want to be able to hash other object types, or use other salt types, you need to override the
120      * {@link #toBytes(Object) toBytes(Object)} method to support those specific types.  Your other option is to
121      * convert your arguments to one of the default three supported types first before passing them in to this
122      * constructor}.
123      *
124      * @param source         the source object to be hashed.
125      * @param salt           the salt to use for the hash
126      * @param hashIterations the number of times the {@code source} argument hashed for attack resiliency.
127      * @throws CodecException if either Object constructor argument cannot be converted into a byte array.
128      */
129     public AbstractHash(Object source, Object salt, int hashIterations) throws CodecException {
130         byte[] sourceBytes = toBytes(source);
131         byte[] saltBytes = null;
132         if (salt != null) {
133             saltBytes = toBytes(salt);
134         }
135         byte[] hashedBytes = hash(sourceBytes, saltBytes, hashIterations);
136         setBytes(hashedBytes);
137     }
138 
139     /**
140      * Implemented by subclasses, this specifies the {@link MessageDigest MessageDigest} algorithm name 
141      * to use when performing the hash.
142      *
143      * @return the {@link MessageDigest MessageDigest} algorithm name to use when performing the hash.
144      */
145     public abstract String getAlgorithmName();
146 
147     public byte[] getBytes() {
148         return this.bytes;
149     }
150 
151     /**
152      * Sets the raw bytes stored by this hash instance.
153      * <p/>
154      * The bytes are kept in raw form - they will not be hashed/changed.  This is primarily a utility method for
155      * constructing a Hash instance when the hashed value is already known.
156      *
157      * @param alreadyHashedBytes the raw already-hashed bytes to store in this instance.
158      */
159     public void setBytes(byte[] alreadyHashedBytes) {
160         this.bytes = alreadyHashedBytes;
161         this.hexEncoded = null;
162         this.base64Encoded = null;
163     }
164 
165     /**
166      * Returns the JDK MessageDigest instance to use for executing the hash.
167      *
168      * @param algorithmName the algorithm to use for the hash, provided by subclasses.
169      * @return the MessageDigest object for the specified {@code algorithm}.
170      * @throws UnknownAlgorithmException if the specified algorithm name is not available.
171      */
172     protected MessageDigest getDigest(String algorithmName) throws UnknownAlgorithmException {
173         try {
174             return MessageDigest.getInstance(algorithmName);
175         } catch (NoSuchAlgorithmException e) {
176             String msg = "No native '" + algorithmName + "' MessageDigest instance available on the current JVM.";
177             throw new UnknownAlgorithmException(msg, e);
178         }
179     }
180 
181     /**
182      * Hashes the specified byte array without a salt for a single iteration.
183      *
184      * @param bytes the bytes to hash.
185      * @return the hashed bytes.
186      */
187     protected byte[] hash(byte[] bytes) {
188         return hash(bytes, null, 1);
189     }
190 
191     /**
192      * Hashes the specified byte array using the given {@code salt} for a single iteration.
193      *
194      * @param bytes the bytes to hash
195      * @param salt  the salt to use for the initial hash
196      * @return the hashed bytes
197      */
198     protected byte[] hash(byte[] bytes, byte[] salt) {
199         return hash(bytes, salt, 1);
200     }
201 
202     /**
203      * Hashes the specified byte array using the given {@code salt} for the specified number of iterations.
204      *
205      * @param bytes          the bytes to hash
206      * @param salt           the salt to use for the initial hash
207      * @param hashIterations the number of times the the {@code bytes} will be hashed (for attack resiliency).
208      * @return the hashed bytes.
209      * @throws UnknownAlgorithmException if the {@link #getAlgorithmName() algorithmName} is not available.
210      */
211     protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
212         MessageDigest digest = getDigest(getAlgorithmName());
213         if (salt != null) {
214             digest.reset();
215             digest.update(salt);
216         }
217         byte[] hashed = digest.digest(bytes);
218         int iterations = hashIterations - 1; //already hashed once above
219         //iterate remaining number:
220         for (int i = 0; i < iterations; i++) {
221             digest.reset();
222             hashed = digest.digest(hashed);
223         }
224         return hashed;
225     }
226 
227     /**
228      * Returns a hex-encoded string of the underlying {@link #getBytes byte array}.
229      * <p/>
230      * This implementation caches the resulting hex string so multiple calls to this method remain efficient.
231      * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
232      * next time this method is called.
233      *
234      * @return a hex-encoded string of the underlying {@link #getBytes byte array}.
235      */
236     public String toHex() {
237         if (this.hexEncoded == null) {
238             this.hexEncoded = Hex.encodeToString(getBytes());
239         }
240         return this.hexEncoded;
241     }
242 
243     /**
244      * Returns a Base64-encoded string of the underlying {@link #getBytes byte array}.
245      * <p/>
246      * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient.
247      * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
248      * next time this method is called.
249      *
250      * @return a Base64-encoded string of the underlying {@link #getBytes byte array}.
251      */
252     public String toBase64() {
253         if (this.base64Encoded == null) {
254             //cache result in case this method is called multiple times.
255             this.base64Encoded = Base64.encodeToString(getBytes());
256         }
257         return this.base64Encoded;
258     }
259 
260     /**
261      * Simple implementation that merely returns {@link #toHex() toHex()}.
262      *
263      * @return the {@link #toHex() toHex()} value.
264      */
265     public String toString() {
266         return toHex();
267     }
268 
269     /**
270      * Returns {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
271      * this Hash's byte array, {@code false} otherwise.
272      *
273      * @param o the object (Hash) to check for equality.
274      * @return {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
275      *         this Hash's byte array, {@code false} otherwise.
276      */
277     public boolean equals(Object o) {
278         if (o instanceof Hash) {
279             Hash other = (Hash) o;
280             return MessageDigest.isEqual(getBytes(), other.getBytes());
281         }
282         return false;
283     }
284 
285     /**
286      * Simply returns toHex().hashCode();
287      *
288      * @return toHex().hashCode()
289      */
290     public int hashCode() {
291         if (this.bytes == null || this.bytes.length == 0) {
292             return 0;
293         }
294         return Arrays.hashCode(this.bytes);
295     }
296 
297     private static void printMainUsage(Class<? extends AbstractHash> clazz, String type) {
298         System.out.println("Prints an " + type + " hash value.");
299         System.out.println("Usage: java " + clazz.getName() + " [-base64] [-salt <saltValue>] [-times <N>] <valueToHash>");
300         System.out.println("Options:");
301         System.out.println("\t-base64\t\tPrints the hash value as a base64 String instead of the default hex.");
302         System.out.println("\t-salt\t\tSalts the hash with the specified <saltValue>");
303         System.out.println("\t-times\t\tHashes the input <N> number of times");
304     }
305 
306     private static boolean isReserved(String arg) {
307         return "-base64".equals(arg) || "-times".equals(arg) || "-salt".equals(arg);
308     }
309 
310     static int doMain(Class<? extends AbstractHash> clazz, String[] args) {
311         String simple = clazz.getSimpleName();
312         int index = simple.indexOf("Hash");
313         String type = simple.substring(0, index).toUpperCase();
314 
315         if (args == null || args.length < 1 || args.length > 7) {
316             printMainUsage(clazz, type);
317             return -1;
318         }
319         boolean hex = true;
320         String salt = null;
321         int times = 1;
322         String text = args[args.length - 1];
323         for (int i = 0; i < args.length; i++) {
324             String arg = args[i];
325             if (arg.equals("-base64")) {
326                 hex = false;
327             } else if (arg.equals("-salt")) {
328                 if ((i + 1) >= (args.length - 1)) {
329                     String msg = "Salt argument must be followed by a salt value.  The final argument is " +
330                             "reserved for the value to hash.";
331                     System.out.println(msg);
332                     printMainUsage(clazz, type);
333                     return -1;
334                 }
335                 salt = args[i + 1];
336             } else if (arg.equals("-times")) {
337                 if ((i + 1) >= (args.length - 1)) {
338                     String msg = "Times argument must be followed by an integer value.  The final argument is " +
339                             "reserved for the value to hash";
340                     System.out.println(msg);
341                     printMainUsage(clazz, type);
342                     return -1;
343                 }
344                 try {
345                     times = Integer.valueOf(args[i + 1]);
346                 } catch (NumberFormatException e) {
347                     String msg = "Times argument must be followed by an integer value.";
348                     System.out.println(msg);
349                     printMainUsage(clazz, type);
350                     return -1;
351                 }
352             }
353         }
354 
355         Hash hash = new Md2Hash(text, salt, times);
356         String hashed = hex ? hash.toHex() : hash.toBase64();
357         System.out.print(hex ? "Hex: " : "Base64: ");
358         System.out.println(hashed);
359         return 0;
360     }
361 }