001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.crypto.hash;
020
021 import org.apache.shiro.codec.Base64;
022 import org.apache.shiro.codec.CodecException;
023 import org.apache.shiro.codec.CodecSupport;
024 import org.apache.shiro.codec.Hex;
025
026 import java.io.Serializable;
027 import java.security.MessageDigest;
028 import java.security.NoSuchAlgorithmException;
029 import java.util.Arrays;
030
031 /**
032 * Provides a base for all Shiro Hash algorithms with support for salts and multiple hash iterations.
033 * <p/>
034 * Read
035 * <a href="http://www.owasp.org/index.php/Hashing_Java" target="blank">http://www.owasp.org/index.php/Hashing_Java</a>
036 * for a good article on the benefits of hashing, including what a 'salt' is as well as why it and multiple hash
037 * iterations can be useful.
038 * <p/>
039 * This class and its subclasses support hashing with additional capabilities of salting and multiple iterations via
040 * overloaded constructors.
041 *
042 * @author Les Hazlewood
043 * @since 0.9
044 */
045 public abstract class AbstractHash extends CodecSupport implements Hash, Serializable {
046
047 /**
048 * The hashed data
049 */
050 private byte[] bytes = null;
051
052 /**
053 * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead.
054 */
055 private transient String hexEncoded = null;
056 /**
057 * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead.
058 */
059 private transient String base64Encoded = null;
060
061 /**
062 * Creates an new instance without any of its properties set (no hashing is performed).
063 * <p/>
064 * Because all constructors in this class (except this one) hash the {@code source} constructor argument, this
065 * default, no-arg constructor is useful in scenarios when you have a byte array that you know is already hashed and
066 * just want to set the bytes in their raw form directly on an instance. After instantiating the instance with
067 * this default, no-arg constructor, you can then immediately call {@link #setBytes setBytes} to have a
068 * fully-initialized instance.
069 */
070 public AbstractHash() {
071 }
072
073 /**
074 * Creates a hash of the specified {@code source} with no {@code salt} using a single hash iteration.
075 * <p/>
076 * It is a convenience constructor that merely executes <code>this( source, null, 1);</code>.
077 * <p/>
078 * Please see the
079 * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
080 * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
081 * types.
082 *
083 * @param source the object to be hashed.
084 * @throws CodecException if the specified {@code source} cannot be converted into a byte array (byte[]).
085 */
086 public AbstractHash(Object source) throws CodecException {
087 this(source, null, 1);
088 }
089
090 /**
091 * Creates a hash of the specified {@code source} using the given {@code salt} using a single hash iteration.
092 * <p/>
093 * It is a convenience constructor that merely executes <code>this( source, salt, 1);</code>.
094 * <p/>
095 * Please see the
096 * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
097 * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
098 * types.
099 *
100 * @param source the source object to be hashed.
101 * @param salt the salt to use for the hash
102 * @throws CodecException if either constructor argument cannot be converted into a byte array.
103 */
104 public AbstractHash(Object source, Object salt) throws CodecException {
105 this(source, salt, 1);
106 }
107
108 /**
109 * Creates a hash of the specified {@code source} using the given {@code salt} a total of
110 * {@code hashIterations} times.
111 * <p/>
112 * By default, this class only supports Object method arguments of
113 * type {@code byte[]}, {@code char[]}, {@link String}, {@link java.io.File File}, or
114 * {@link java.io.InputStream InputStream}. If either argument is anything other than these
115 * types a {@link org.apache.shiro.codec.CodecException CodecException} will be thrown.
116 * <p/>
117 * If you want to be able to hash other object types, or use other salt types, you need to override the
118 * {@link #toBytes(Object) toBytes(Object)} method to support those specific types. Your other option is to
119 * convert your arguments to one of the default three supported types first before passing them in to this
120 * constructor}.
121 *
122 * @param source the source object to be hashed.
123 * @param salt the salt to use for the hash
124 * @param hashIterations the number of times the {@code source} argument hashed for attack resiliency.
125 * @throws CodecException if either Object constructor argument cannot be converted into a byte array.
126 */
127 public AbstractHash(Object source, Object salt, int hashIterations) throws CodecException {
128 byte[] sourceBytes = toBytes(source);
129 byte[] saltBytes = null;
130 if (salt != null) {
131 saltBytes = toBytes(salt);
132 }
133 byte[] hashedBytes = hash(sourceBytes, saltBytes, hashIterations);
134 setBytes(hashedBytes);
135 }
136
137 /**
138 * Implemented by subclasses, this specifies the name of the {@link MessageDigest MessageDigest} algorithm
139 * to use when performing the hash.
140 *
141 * @return the {@link MessageDigest MessageDigest} algorithm to use when performing the hash.
142 */
143 protected abstract String getAlgorithmName();
144
145 public byte[] getBytes() {
146 return this.bytes;
147 }
148
149 /**
150 * Sets the raw bytes stored by this hash instance.
151 * <p/>
152 * The bytes are kept in raw form - they will not be hashed/changed. This is primarily a utility method for
153 * constructing a Hash instance when the hashed value is already known.
154 *
155 * @param alreadyHashedBytes the raw already-hashed bytes to store in this instance.
156 */
157 public void setBytes(byte[] alreadyHashedBytes) {
158 this.bytes = alreadyHashedBytes;
159 this.hexEncoded = null;
160 this.base64Encoded = null;
161 }
162
163 /**
164 * Returns the JDK MessageDigest instance to use for executing the hash.
165 *
166 * @param algorithmName the algorithm to use for the hash, provided by subclasses.
167 * @return the MessageDigest object for the specified {@code algorithm}.
168 */
169 protected MessageDigest getDigest(String algorithmName) {
170 try {
171 return MessageDigest.getInstance(algorithmName);
172 } catch (NoSuchAlgorithmException e) {
173 String msg = "No native '" + algorithmName + "' MessageDigest instance available on the current JVM.";
174 throw new IllegalStateException(msg, e);
175 }
176 }
177
178 /**
179 * Hashes the specified byte array without a salt for a single iteration.
180 *
181 * @param bytes the bytes to hash.
182 * @return the hashed bytes.
183 */
184 protected byte[] hash(byte[] bytes) {
185 return hash(bytes, null, 1);
186 }
187
188 /**
189 * Hashes the specified byte array using the given {@code salt} for a single iteration.
190 *
191 * @param bytes the bytes to hash
192 * @param salt the salt to use for the initial hash
193 * @return the hashed bytes
194 */
195 protected byte[] hash(byte[] bytes, byte[] salt) {
196 return hash(bytes, salt, 1);
197 }
198
199 /**
200 * Hashes the specified byte array using the given {@code salt} for the specified number of iterations.
201 *
202 * @param bytes the bytes to hash
203 * @param salt the salt to use for the initial hash
204 * @param hashIterations the number of times the the {@code bytes} will be hashed (for attack resiliency).
205 * @return the hashed bytes.
206 */
207 protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) {
208 MessageDigest digest = getDigest(getAlgorithmName());
209 if (salt != null) {
210 digest.reset();
211 digest.update(salt);
212 }
213 byte[] hashed = digest.digest(bytes);
214 int iterations = hashIterations - 1; //already hashed once above
215 //iterate remaining number:
216 for (int i = 0; i < iterations; i++) {
217 digest.reset();
218 hashed = digest.digest(hashed);
219 }
220 return hashed;
221 }
222
223 /**
224 * Returns a hex-encoded string of the underlying {@link #getBytes byte array}.
225 * <p/>
226 * This implementation caches the resulting hex string so multiple calls to this method remain efficient.
227 * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
228 * next time this method is called.
229 *
230 * @return a hex-encoded string of the underlying {@link #getBytes byte array}.
231 */
232 public String toHex() {
233 if (this.hexEncoded == null) {
234 this.hexEncoded = Hex.encodeToString(getBytes());
235 }
236 return this.hexEncoded;
237 }
238
239 /**
240 * Returns a Base64-encoded string of the underlying {@link #getBytes byte array}.
241 * <p/>
242 * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient.
243 * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the
244 * next time this method is called.
245 *
246 * @return a Base64-encoded string of the underlying {@link #getBytes byte array}.
247 */
248 public String toBase64() {
249 if (this.base64Encoded == null) {
250 //cache result in case this method is called multiple times.
251 this.base64Encoded = Base64.encodeToString(getBytes());
252 }
253 return this.base64Encoded;
254 }
255
256 /**
257 * Simple implementation that merely returns {@link #toHex() toHex()}.
258 *
259 * @return the {@link #toHex() toHex()} value.
260 */
261 public String toString() {
262 return toHex();
263 }
264
265 /**
266 * Returns {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
267 * this Hash's byte array, {@code false} otherwise.
268 *
269 * @param o the object (Hash) to check for equality.
270 * @return {@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 public boolean equals(Object o) {
274 if (o instanceof Hash) {
275 Hash other = (Hash) o;
276 return Arrays.equals(getBytes(), other.getBytes());
277 }
278 return false;
279 }
280
281 /**
282 * Simply returns toHex().hashCode();
283 *
284 * @return toHex().hashCode()
285 */
286 public int hashCode() {
287 return toHex().hashCode();
288 }
289
290 private static void printMainUsage(Class<? extends AbstractHash> clazz, String type) {
291 System.out.println("Prints an " + type + " hash value.");
292 System.out.println("Usage: java " + clazz.getName() + " [-base64] [-salt <saltValue>] [-times <N>] <valueToHash>");
293 System.out.println("Options:");
294 System.out.println("\t-base64\t\tPrints the hash value as a base64 String instead of the default hex.");
295 System.out.println("\t-salt\t\tSalts the hash with the specified <saltValue>");
296 System.out.println("\t-times\t\tHashes the input <N> number of times");
297 }
298
299 private static boolean isReserved(String arg) {
300 return "-base64".equals(arg) || "-times".equals(arg) || "-salt".equals(arg);
301 }
302
303 static int doMain(Class<? extends AbstractHash> clazz, String[] args) {
304 String simple = clazz.getSimpleName();
305 int index = simple.indexOf("Hash");
306 String type = simple.substring(0, index).toUpperCase();
307
308 if (args == null || args.length < 1 || args.length > 7) {
309 printMainUsage(clazz, type);
310 return -1;
311 }
312 boolean hex = true;
313 String salt = null;
314 int times = 1;
315 String text = args[args.length - 1];
316 for (int i = 0; i < args.length; i++) {
317 String arg = args[i];
318 if (arg.equals("-base64")) {
319 hex = false;
320 } else if (arg.equals("-salt")) {
321 if ((i + 1) >= (args.length - 1)) {
322 String msg = "Salt argument must be followed by a salt value. The final argument is " +
323 "reserved for the value to hash.";
324 System.out.println(msg);
325 printMainUsage(clazz, type);
326 return -1;
327 }
328 salt = args[i + 1];
329 } else if (arg.equals("-times")) {
330 if ((i + 1) >= (args.length - 1)) {
331 String msg = "Times argument must be followed by an integer value. The final argument is " +
332 "reserved for the value to hash";
333 System.out.println(msg);
334 printMainUsage(clazz, type);
335 return -1;
336 }
337 try {
338 times = Integer.valueOf(args[i + 1]);
339 } catch (NumberFormatException e) {
340 String msg = "Times argument must be followed by an integer value.";
341 System.out.println(msg);
342 printMainUsage(clazz, type);
343 return -1;
344 }
345 }
346 }
347
348 Hash hash = new Md2Hash(text, salt, times);
349 String hashed = hex ? hash.toHex() : hash.toBase64();
350 System.out.print(hex ? "Hex: " : "Base64: ");
351 System.out.println(hashed);
352 return 0;
353 }
354 }