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 */
019package org.apache.shiro.crypto.hash;
020
021import org.apache.shiro.codec.Base64;
022import org.apache.shiro.codec.CodecException;
023import org.apache.shiro.codec.CodecSupport;
024import org.apache.shiro.codec.Hex;
025import org.apache.shiro.crypto.UnknownAlgorithmException;
026
027import java.io.Serializable;
028import java.security.MessageDigest;
029import java.security.NoSuchAlgorithmException;
030import 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
047public 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}