Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractHash |
|
| 2.6315789473684212;2.632 |
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 | 135 | 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 | 135 | 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 | 135 | 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 | 135 | public AbstractHash() { |
73 | 135 | } |
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 | 0 | this(source, null, 1); |
90 | 0 | } |
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 | 0 | this(source, salt, 1); |
108 | 0 | } |
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 | 0 | public AbstractHash(Object source, Object salt, int hashIterations) throws CodecException { |
130 | 0 | byte[] sourceBytes = toBytes(source); |
131 | 0 | byte[] saltBytes = null; |
132 | 0 | if (salt != null) { |
133 | 0 | saltBytes = toBytes(salt); |
134 | } | |
135 | 0 | byte[] hashedBytes = hash(sourceBytes, saltBytes, hashIterations); |
136 | 0 | setBytes(hashedBytes); |
137 | 0 | } |
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 | 0 | 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 | 0 | this.bytes = alreadyHashedBytes; |
161 | 0 | this.hexEncoded = null; |
162 | 0 | this.base64Encoded = null; |
163 | 0 | } |
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 | 0 | return MessageDigest.getInstance(algorithmName); |
175 | 0 | } catch (NoSuchAlgorithmException e) { |
176 | 0 | String msg = "No native '" + algorithmName + "' MessageDigest instance available on the current JVM."; |
177 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | MessageDigest digest = getDigest(getAlgorithmName()); |
213 | 0 | if (salt != null) { |
214 | 0 | digest.reset(); |
215 | 0 | digest.update(salt); |
216 | } | |
217 | 0 | byte[] hashed = digest.digest(bytes); |
218 | 0 | int iterations = hashIterations - 1; //already hashed once above |
219 | //iterate remaining number: | |
220 | 0 | for (int i = 0; i < iterations; i++) { |
221 | 0 | digest.reset(); |
222 | 0 | hashed = digest.digest(hashed); |
223 | } | |
224 | 0 | 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 | 0 | if (this.hexEncoded == null) { |
238 | 0 | this.hexEncoded = Hex.encodeToString(getBytes()); |
239 | } | |
240 | 0 | 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 | 0 | if (this.base64Encoded == null) { |
254 | //cache result in case this method is called multiple times. | |
255 | 0 | this.base64Encoded = Base64.encodeToString(getBytes()); |
256 | } | |
257 | 0 | 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 | 0 | 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 | 0 | if (o instanceof Hash) { |
279 | 0 | Hash other = (Hash) o; |
280 | 0 | return Arrays.equals(getBytes(), other.getBytes()); |
281 | } | |
282 | 0 | return false; |
283 | } | |
284 | ||
285 | /** | |
286 | * Simply returns toHex().hashCode(); | |
287 | * | |
288 | * @return toHex().hashCode() | |
289 | */ | |
290 | public int hashCode() { | |
291 | 0 | if (this.bytes == null || this.bytes.length == 0) { |
292 | 0 | return 0; |
293 | } | |
294 | 0 | return Arrays.hashCode(this.bytes); |
295 | } | |
296 | ||
297 | private static void printMainUsage(Class<? extends AbstractHash> clazz, String type) { | |
298 | 0 | System.out.println("Prints an " + type + " hash value."); |
299 | 0 | System.out.println("Usage: java " + clazz.getName() + " [-base64] [-salt <saltValue>] [-times <N>] <valueToHash>"); |
300 | 0 | System.out.println("Options:"); |
301 | 0 | System.out.println("\t-base64\t\tPrints the hash value as a base64 String instead of the default hex."); |
302 | 0 | System.out.println("\t-salt\t\tSalts the hash with the specified <saltValue>"); |
303 | 0 | System.out.println("\t-times\t\tHashes the input <N> number of times"); |
304 | 0 | } |
305 | ||
306 | private static boolean isReserved(String arg) { | |
307 | 0 | return "-base64".equals(arg) || "-times".equals(arg) || "-salt".equals(arg); |
308 | } | |
309 | ||
310 | static int doMain(Class<? extends AbstractHash> clazz, String[] args) { | |
311 | 0 | String simple = clazz.getSimpleName(); |
312 | 0 | int index = simple.indexOf("Hash"); |
313 | 0 | String type = simple.substring(0, index).toUpperCase(); |
314 | ||
315 | 0 | if (args == null || args.length < 1 || args.length > 7) { |
316 | 0 | printMainUsage(clazz, type); |
317 | 0 | return -1; |
318 | } | |
319 | 0 | boolean hex = true; |
320 | 0 | String salt = null; |
321 | 0 | int times = 1; |
322 | 0 | String text = args[args.length - 1]; |
323 | 0 | for (int i = 0; i < args.length; i++) { |
324 | 0 | String arg = args[i]; |
325 | 0 | if (arg.equals("-base64")) { |
326 | 0 | hex = false; |
327 | 0 | } else if (arg.equals("-salt")) { |
328 | 0 | if ((i + 1) >= (args.length - 1)) { |
329 | 0 | String msg = "Salt argument must be followed by a salt value. The final argument is " + |
330 | "reserved for the value to hash."; | |
331 | 0 | System.out.println(msg); |
332 | 0 | printMainUsage(clazz, type); |
333 | 0 | return -1; |
334 | } | |
335 | 0 | salt = args[i + 1]; |
336 | 0 | } else if (arg.equals("-times")) { |
337 | 0 | if ((i + 1) >= (args.length - 1)) { |
338 | 0 | String msg = "Times argument must be followed by an integer value. The final argument is " + |
339 | "reserved for the value to hash"; | |
340 | 0 | System.out.println(msg); |
341 | 0 | printMainUsage(clazz, type); |
342 | 0 | return -1; |
343 | } | |
344 | try { | |
345 | 0 | times = Integer.valueOf(args[i + 1]); |
346 | 0 | } catch (NumberFormatException e) { |
347 | 0 | String msg = "Times argument must be followed by an integer value."; |
348 | 0 | System.out.println(msg); |
349 | 0 | printMainUsage(clazz, type); |
350 | 0 | return -1; |
351 | 0 | } |
352 | } | |
353 | } | |
354 | ||
355 | 0 | Hash hash = new Md2Hash(text, salt, times); |
356 | 0 | String hashed = hex ? hash.toHex() : hash.toBase64(); |
357 | 0 | System.out.print(hex ? "Hex: " : "Base64: "); |
358 | 0 | System.out.println(hashed); |
359 | 0 | return 0; |
360 | } | |
361 | } |