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.Hex; 24 import org.apache.shiro.crypto.UnknownAlgorithmException; 25 import org.apache.shiro.util.ByteSource; 26 import org.apache.shiro.util.StringUtils; 27 28 import java.security.MessageDigest; 29 import java.security.NoSuchAlgorithmException; 30 import java.util.Arrays; 31 32 /** 33 * A {@code Hash} implementation that allows any {@link java.security.MessageDigest MessageDigest} algorithm name to 34 * be used. This class is a less type-safe variant than the other {@code AbstractHash} subclasses 35 * (e.g. {@link Sha512Hash}, etc), but it does allow for any algorithm name to be specified in case the other subclass 36 * implementations do not represent an algorithm that you may want to use. 37 * <p/> 38 * As of Shiro 1.1, this class effectively replaces the (now-deprecated) {@link AbstractHash} class. It subclasses 39 * {@code AbstractHash} only to retain backwards-compatibility. 40 * 41 * @since 1.1 42 */ 43 public class SimpleHash extends AbstractHash { 44 45 private static final int DEFAULT_ITERATIONS = 1; 46 47 /** 48 * The {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash. 49 */ 50 private final String algorithmName; 51 52 /** 53 * The hashed data 54 */ 55 private byte[] bytes; 56 57 /** 58 * Supplied salt, if any. 59 */ 60 private ByteSource salt; 61 62 /** 63 * Number of hash iterations to perform. Defaults to 1 in the constructor. 64 */ 65 private int iterations; 66 67 /** 68 * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead. 69 */ 70 private transient String hexEncoded = null; 71 72 /** 73 * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead. 74 */ 75 private transient String base64Encoded = null; 76 77 /** 78 * Creates an new instance with only its {@code algorithmName} set - no hashing is performed. 79 * <p/> 80 * Because all other constructors in this class hash the {@code source} constructor argument, this 81 * constructor is useful in scenarios when you have a byte array that you know is already hashed and 82 * just want to set the bytes in their raw form directly on an instance. After using this constructor, 83 * you can then immediately call {@link #setBytes setBytes} to have a fully-initialized instance. 84 * <p/> 85 * <b>N.B.</b>The algorithm identified by the {@code algorithmName} parameter must be available on the JVM. If it 86 * is not, a {@link UnknownAlgorithmException} will be thrown when the hash is performed (not at instantiation). 87 * 88 * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when 89 * performing the hash. 90 * @see UnknownAlgorithmException 91 */ 92 public SimpleHash(String algorithmName) { 93 this.algorithmName = algorithmName; 94 this.iterations = DEFAULT_ITERATIONS; 95 } 96 97 /** 98 * Creates an {@code algorithmName}-specific hash of the specified {@code source} with no {@code salt} using a 99 * single hash iteration. 100 * <p/> 101 * This is a convenience constructor that merely executes <code>this( algorithmName, source, null, 1);</code>. 102 * <p/> 103 * Please see the 104 * {@link #SimpleHash(String algorithmName, Object source, Object salt, int numIterations) SimpleHashHash(algorithmName, Object,Object,int)} 105 * constructor for the types of Objects that may be passed into this constructor, as well as how to support further 106 * types. 107 * 108 * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when 109 * performing the hash. 110 * @param source the object to be hashed. 111 * @throws org.apache.shiro.codec.CodecException 112 * if the specified {@code source} cannot be converted into a byte array (byte[]). 113 * @throws UnknownAlgorithmException if the {@code algorithmName} is not available. 114 */ 115 public SimpleHash(String algorithmName, Object source) throws CodecException, UnknownAlgorithmException { 116 //noinspection NullableProblems 117 this(algorithmName, source, null, DEFAULT_ITERATIONS); 118 } 119 120 /** 121 * Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given {@code salt} 122 * using a single hash iteration. 123 * <p/> 124 * It is a convenience constructor that merely executes <code>this( algorithmName, source, salt, 1);</code>. 125 * <p/> 126 * Please see the 127 * {@link #SimpleHash(String algorithmName, Object source, Object salt, int numIterations) SimpleHashHash(algorithmName, Object,Object,int)} 128 * constructor for the types of Objects that may be passed into this constructor, as well as how to support further 129 * types. 130 * 131 * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when 132 * performing the hash. 133 * @param source the source object to be hashed. 134 * @param salt the salt to use for the hash 135 * @throws CodecException if either constructor argument cannot be converted into a byte array. 136 * @throws UnknownAlgorithmException if the {@code algorithmName} is not available. 137 */ 138 public SimpleHash(String algorithmName, Object source, Object salt) throws CodecException, UnknownAlgorithmException { 139 this(algorithmName, source, salt, DEFAULT_ITERATIONS); 140 } 141 142 /** 143 * Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given 144 * {@code salt} a total of {@code hashIterations} times. 145 * <p/> 146 * By default, this class only supports Object method arguments of 147 * type {@code byte[]}, {@code char[]}, {@link String}, {@link java.io.File File}, 148 * {@link java.io.InputStream InputStream} or {@link org.apache.shiro.util.ByteSource ByteSource}. If either 149 * argument is anything other than these types a {@link org.apache.shiro.codec.CodecException CodecException} 150 * will be thrown. 151 * <p/> 152 * If you want to be able to hash other object types, or use other salt types, you need to override the 153 * {@link #toBytes(Object) toBytes(Object)} method to support those specific types. Your other option is to 154 * convert your arguments to one of the default supported types first before passing them in to this 155 * constructor}. 156 * 157 * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when 158 * performing the hash. 159 * @param source the source object to be hashed. 160 * @param salt the salt to use for the hash 161 * @param hashIterations the number of times the {@code source} argument hashed for attack resiliency. 162 * @throws CodecException if either Object constructor argument cannot be converted into a byte array. 163 * @throws UnknownAlgorithmException if the {@code algorithmName} is not available. 164 */ 165 public SimpleHash(String algorithmName, Object source, Object salt, int hashIterations) 166 throws CodecException, UnknownAlgorithmException { 167 if (!StringUtils.hasText(algorithmName)) { 168 throw new NullPointerException("algorithmName argument cannot be null or empty."); 169 } 170 this.algorithmName = algorithmName; 171 this.iterations = Math.max(DEFAULT_ITERATIONS, hashIterations); 172 ByteSource saltBytes = null; 173 if (salt != null) { 174 saltBytes = convertSaltToBytes(salt); 175 this.salt = saltBytes; 176 } 177 ByteSource sourceBytes = convertSourceToBytes(source); 178 hash(sourceBytes, saltBytes, hashIterations); 179 } 180 181 /** 182 * Acquires the specified {@code source} argument's bytes and returns them in the form of a {@code ByteSource} instance. 183 * <p/> 184 * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic 185 * conversion. Can be overridden by subclasses for source-specific conversion. 186 * 187 * @param source the source object to be hashed. 188 * @return the source's bytes in the form of a {@code ByteSource} instance. 189 * @since 1.2 190 */ 191 protected ByteSource convertSourceToBytes(Object source) { 192 return toByteSource(source); 193 } 194 195 /** 196 * Acquires the specified {@code salt} argument's bytes and returns them in the form of a {@code ByteSource} instance. 197 * <p/> 198 * This implementation merely delegates to the convenience {@link #toByteSource(Object)} method for generic 199 * conversion. Can be overridden by subclasses for salt-specific conversion. 200 * 201 * @param salt the salt to be use for the hash. 202 * @return the salt's bytes in the form of a {@code ByteSource} instance. 203 * @since 1.2 204 */ 205 protected ByteSource convertSaltToBytes(Object salt) { 206 return toByteSource(salt); 207 } 208 209 /** 210 * Converts a given object into a {@code ByteSource} instance. Assumes the object can be converted to bytes. 211 * 212 * @param o the Object to convert into a {@code ByteSource} instance. 213 * @return the {@code ByteSource} representation of the specified object's bytes. 214 * @since 1.2 215 */ 216 protected ByteSource toByteSource(Object o) { 217 if (o == null) { 218 return null; 219 } 220 if (o instanceof ByteSource) { 221 return (ByteSource) o; 222 } 223 byte[] bytes = toBytes(o); 224 return ByteSource.Util.bytes(bytes); 225 } 226 227 private void hash(ByteSource source, ByteSource salt, int hashIterations) throws CodecException, UnknownAlgorithmException { 228 byte[] saltBytes = salt != null ? salt.getBytes() : null; 229 byte[] hashedBytes = hash(source.getBytes(), saltBytes, hashIterations); 230 setBytes(hashedBytes); 231 } 232 233 /** 234 * Returns the {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash. 235 * 236 * @return the {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash. 237 */ 238 public String getAlgorithmName() { 239 return this.algorithmName; 240 } 241 242 public ByteSource getSalt() { 243 return this.salt; 244 } 245 246 public int getIterations() { 247 return this.iterations; 248 } 249 250 public byte[] getBytes() { 251 return this.bytes; 252 } 253 254 /** 255 * Sets the raw bytes stored by this hash instance. 256 * <p/> 257 * The bytes are kept in raw form - they will not be hashed/changed. This is primarily a utility method for 258 * constructing a Hash instance when the hashed value is already known. 259 * 260 * @param alreadyHashedBytes the raw already-hashed bytes to store in this instance. 261 */ 262 public void setBytes(byte[] alreadyHashedBytes) { 263 this.bytes = alreadyHashedBytes; 264 this.hexEncoded = null; 265 this.base64Encoded = null; 266 } 267 268 /** 269 * Sets the iterations used to previously compute AN ALREADY GENERATED HASH. 270 * <p/> 271 * This is provided <em>ONLY</em> to reconstitute an already-created Hash instance. It should ONLY ever be 272 * invoked when re-constructing a hash instance from an already-hashed value. 273 * 274 * @param iterations the number of hash iterations used to previously create the hash/digest. 275 * @since 1.2 276 */ 277 public void setIterations(int iterations) { 278 this.iterations = Math.max(DEFAULT_ITERATIONS, iterations); 279 } 280 281 /** 282 * Sets the salt used to previously compute AN ALREADY GENERATED HASH. 283 * <p/> 284 * This is provided <em>ONLY</em> to reconstitute a Hash instance that has already been computed. It should ONLY 285 * ever be invoked when re-constructing a hash instance from an already-hashed value. 286 * 287 * @param salt the salt used to previously create the hash/digest. 288 * @since 1.2 289 */ 290 public void setSalt(ByteSource salt) { 291 this.salt = salt; 292 } 293 294 /** 295 * Returns the JDK MessageDigest instance to use for executing the hash. 296 * 297 * @param algorithmName the algorithm to use for the hash, provided by subclasses. 298 * @return the MessageDigest object for the specified {@code algorithm}. 299 * @throws UnknownAlgorithmException if the specified algorithm name is not available. 300 */ 301 protected MessageDigest getDigest(String algorithmName) throws UnknownAlgorithmException { 302 try { 303 return MessageDigest.getInstance(algorithmName); 304 } catch (NoSuchAlgorithmException e) { 305 String msg = "No native '" + algorithmName + "' MessageDigest instance available on the current JVM."; 306 throw new UnknownAlgorithmException(msg, e); 307 } 308 } 309 310 /** 311 * Hashes the specified byte array without a salt for a single iteration. 312 * 313 * @param bytes the bytes to hash. 314 * @return the hashed bytes. 315 * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available. 316 */ 317 protected byte[] hash(byte[] bytes) throws UnknownAlgorithmException { 318 return hash(bytes, null, DEFAULT_ITERATIONS); 319 } 320 321 /** 322 * Hashes the specified byte array using the given {@code salt} for a single iteration. 323 * 324 * @param bytes the bytes to hash 325 * @param salt the salt to use for the initial hash 326 * @return the hashed bytes 327 * @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available. 328 */ 329 protected byte[] hash(byte[] bytes, byte[] salt) throws UnknownAlgorithmException { 330 return hash(bytes, salt, DEFAULT_ITERATIONS); 331 } 332 333 /** 334 * Hashes the specified byte array using the given {@code salt} for the specified number of iterations. 335 * 336 * @param bytes the bytes to hash 337 * @param salt the salt to use for the initial hash 338 * @param hashIterations the number of times the the {@code bytes} will be hashed (for attack resiliency). 339 * @return the hashed bytes. 340 * @throws UnknownAlgorithmException if the {@link #getAlgorithmName() algorithmName} is not available. 341 */ 342 protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException { 343 MessageDigest digest = getDigest(getAlgorithmName()); 344 if (salt != null) { 345 digest.reset(); 346 digest.update(salt); 347 } 348 byte[] hashed = digest.digest(bytes); 349 int iterations = hashIterations - DEFAULT_ITERATIONS; //already hashed once above 350 //iterate remaining number: 351 for (int i = 0; i < iterations; i++) { 352 digest.reset(); 353 hashed = digest.digest(hashed); 354 } 355 return hashed; 356 } 357 358 public boolean isEmpty() { 359 return this.bytes == null || this.bytes.length == 0; 360 } 361 362 /** 363 * Returns a hex-encoded string of the underlying {@link #getBytes byte array}. 364 * <p/> 365 * This implementation caches the resulting hex string so multiple calls to this method remain efficient. 366 * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the 367 * next time this method is called. 368 * 369 * @return a hex-encoded string of the underlying {@link #getBytes byte array}. 370 */ 371 public String toHex() { 372 if (this.hexEncoded == null) { 373 this.hexEncoded = Hex.encodeToString(getBytes()); 374 } 375 return this.hexEncoded; 376 } 377 378 /** 379 * Returns a Base64-encoded string of the underlying {@link #getBytes byte array}. 380 * <p/> 381 * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient. 382 * However, calling {@link #setBytes setBytes} will null the cached value, forcing it to be recalculated the 383 * next time this method is called. 384 * 385 * @return a Base64-encoded string of the underlying {@link #getBytes byte array}. 386 */ 387 public String toBase64() { 388 if (this.base64Encoded == null) { 389 //cache result in case this method is called multiple times. 390 this.base64Encoded = Base64.encodeToString(getBytes()); 391 } 392 return this.base64Encoded; 393 } 394 395 /** 396 * Simple implementation that merely returns {@link #toHex() toHex()}. 397 * 398 * @return the {@link #toHex() toHex()} value. 399 */ 400 public String toString() { 401 return toHex(); 402 } 403 404 /** 405 * Returns {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to 406 * this Hash's byte array, {@code false} otherwise. 407 * 408 * @param o the object (Hash) to check for equality. 409 * @return {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to 410 * this Hash's byte array, {@code false} otherwise. 411 */ 412 public boolean equals(Object o) { 413 if (o instanceof Hash) { 414 Hash other = (Hash) o; 415 return Arrays.equals(getBytes(), other.getBytes()); 416 } 417 return false; 418 } 419 420 /** 421 * Simply returns toHex().hashCode(); 422 * 423 * @return toHex().hashCode() 424 */ 425 public int hashCode() { 426 if (this.bytes == null || this.bytes.length == 0) { 427 return 0; 428 } 429 return Arrays.hashCode(this.bytes); 430 } 431 }