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    }