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    import org.apache.shiro.crypto.UnknownAlgorithmException;
026    
027    import java.io.Serializable;
028    import java.security.MessageDigest;
029    import java.security.NoSuchAlgorithmException;
030    import java.util.Arrays;
031    
032    /**
033     * Provides a base for all Shiro Hash algorithms with support for salts and multiple hash iterations.
034     * <p/>
035     * Read
036     * <a href="http://www.owasp.org/index.php/Hashing_Java" target="blank">http://www.owasp.org/index.php/Hashing_Java</a>
037     * for a good article on the benefits of hashing, including what a 'salt' is as well as why it and multiple hash
038     * iterations can be useful.
039     * <p/>
040     * This class and its subclasses support hashing with additional capabilities of salting and multiple iterations via
041     * overloaded constructors.
042     *
043     * @since 0.9
044     * @deprecated in Shiro 1.1 in favor of using the concrete {@link SimpleHash} implementation directly.
045     */
046    @Deprecated
047    public abstract class AbstractHash extends CodecSupport implements Hash, Serializable {
048    
049        /**
050         * The hashed data
051         */
052        private byte[] bytes = null;
053    
054        /**
055         * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead.
056         */
057        private transient String hexEncoded = null;
058        /**
059         * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead.
060         */
061        private transient String base64Encoded = null;
062    
063        /**
064         * Creates an new instance without any of its properties set (no hashing is performed).
065         * <p/>
066         * Because all constructors in this class (except this one) hash the {@code source} constructor argument, this
067         * default, no-arg constructor is useful in scenarios when you have a byte array that you know is already hashed and
068         * just want to set the bytes in their raw form directly on an instance.  After instantiating the instance with
069         * this default, no-arg constructor, you can then immediately call {@link #setBytes setBytes} to have a
070         * fully-initialized instance.
071         */
072        public AbstractHash() {
073        }
074    
075        /**
076         * Creates a hash of the specified {@code source} with no {@code salt} using a single hash iteration.
077         * <p/>
078         * It is a convenience constructor that merely executes <code>this( source, null, 1);</code>.
079         * <p/>
080         * Please see the
081         * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
082         * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
083         * types.
084         *
085         * @param source the object to be hashed.
086         * @throws CodecException if the specified {@code source} cannot be converted into a byte array (byte[]).
087         */
088        public AbstractHash(Object source) throws CodecException {
089            this(source, null, 1);
090        }
091    
092        /**
093         * Creates a hash of the specified {@code source} using the given {@code salt} using a single hash iteration.
094         * <p/>
095         * It is a convenience constructor that merely executes <code>this( source, salt, 1);</code>.
096         * <p/>
097         * Please see the
098         * {@link #AbstractHash(Object source, Object salt, int numIterations) AbstractHash(Object,Object,int)}
099         * 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 Arrays.equals(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    }