Coverage Report - org.apache.shiro.crypto.hash.AbstractHash
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractHash
5%
5/99
0%
0/44
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  
 }