001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.common.util; 017 018import java.io.BufferedInputStream; 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.Reader; 024import java.nio.CharBuffer; 025import java.nio.charset.Charset; 026import java.nio.charset.CharsetEncoder; 027import java.security.MessageDigest; 028import java.security.NoSuchAlgorithmException; 029import org.modeshape.common.SystemFailureException; 030import org.modeshape.common.annotation.Immutable; 031 032/** 033 * A simple utility to generate various kinds of secure hashes. 034 */ 035@Immutable 036public class SecureHash { 037 038 /** 039 * Commonly-used hashing algorithms. 040 */ 041 @Immutable 042 public enum Algorithm { 043 MD2("MD2", 128, "The MD2 message digest algorithm as defined in RFC 1319"), 044 MD5("MD5", 128, "The MD5 message digest algorithm as defined in RFC 1321"), 045 SHA_1("SHA-1", 160, "The Secure Hash Algorithm, as defined in Secure Hash Standard, NIST FIPS 180-1"), 046 SHA_256( 047 "SHA-256", 048 256, 049 "New hash algorithms for which the draft Federal Information Processing Standard 180-2, " 050 + "Secure Hash Standard (SHS) is now available. SHA-256 is a 256-bit hash function intended to provide 128 bits of " 051 + "security against collision attacks."), 052 SHA_384( 053 "SHA-384", 054 384, 055 "New hash algorithms for which the draft Federal Information Processing Standard 180-2, " 056 + "Secure Hash Standard (SHS) is now available. A 384-bit hash may be obtained by truncating the SHA-512 output."), 057 SHA_512( 058 "SHA-512", 059 512, 060 "New hash algorithms for which the draft Federal Information Processing Standard 180-2, " 061 + "Secure Hash Standard (SHS) is now available. SHA-512 is a 512-bit hash function intended to provide 256 bits of security."); 062 private final String name; 063 private final String description; 064 private final int numberOfBits; 065 private final int numberOfBytes; 066 private final int numberOfHexChars; 067 068 private Algorithm( String name, 069 int numberOfBits, 070 String description ) { 071 assert numberOfBits % 8 == 0; 072 this.name = name; 073 this.description = description; 074 this.numberOfBits = numberOfBits; 075 this.numberOfBytes = this.numberOfBits / 8; 076 this.numberOfHexChars = this.numberOfBits / 4; 077 } 078 079 public String digestName() { 080 return this.name; 081 } 082 083 public String description() { 084 return this.description; 085 } 086 087 /** 088 * Get the length of the hexadecimal representation. 089 * 090 * @return the number of hexadecimal characters 091 */ 092 public int getHexadecimalStringLength() { 093 return numberOfHexChars; 094 } 095 096 /** 097 * Determine whether the supplied string is of the correct format to contain a hexadecimal representation of this 098 * algorithm. 099 * 100 * @param string the string; may not be null 101 * @return true if the string might contain a hexadecimal representation of this algorithm, or false otherwise 102 */ 103 public boolean isHexadecimal( String string ) { 104 return string.length() == getHexadecimalStringLength() && StringUtil.isHexString(string); 105 } 106 107 /** 108 * Get the length of the hexadecimal representation. 109 * 110 * @return the number of hexadecimal characters 111 */ 112 public int getNumberOfBytes() { 113 return numberOfBytes; 114 } 115 116 /** 117 * Get the number of bits that make up a digest. 118 * 119 * @return the number of bits 120 */ 121 public int getNumberOfBits() { 122 return numberOfBits; 123 } 124 125 @Override 126 public String toString() { 127 return digestName(); 128 } 129 } 130 131 /** 132 * Get the hash of the supplied content, using the supplied digest algorithm. 133 * 134 * @param algorithm the hashing function algorithm that should be used 135 * @param content the content to be hashed; may not be null 136 * @return the hash of the contents as a byte array 137 * @throws NoSuchAlgorithmException if the supplied algorithm could not be found 138 * @throws IllegalArgumentException if the algorithm is null 139 */ 140 public static byte[] getHash( Algorithm algorithm, 141 byte[] content ) throws NoSuchAlgorithmException { 142 CheckArg.isNotNull(algorithm, "algorithm"); 143 return getHash(algorithm.digestName(), content); 144 } 145 146 /** 147 * Get the hash of the supplied content, using the supplied digest algorithm. 148 * 149 * @param algorithm the hashing function algorithm that should be used 150 * @param file the file containing the content to be hashed; may not be null 151 * @return the hash of the contents as a byte array 152 * @throws NoSuchAlgorithmException if the supplied algorithm could not be found 153 * @throws IllegalArgumentException if the algorithm is null 154 * @throws IOException if there is an error reading the file 155 */ 156 public static byte[] getHash( Algorithm algorithm, 157 File file ) throws NoSuchAlgorithmException, IOException { 158 CheckArg.isNotNull(algorithm, "algorithm"); 159 return getHash(algorithm.digestName(), file); 160 } 161 162 /** 163 * Get the hash of the supplied content, using the supplied digest algorithm. 164 * 165 * @param algorithm the hashing function algorithm that should be used 166 * @param stream the stream containing the content to be hashed; may not be null 167 * @return the hash of the contents as a byte array 168 * @throws NoSuchAlgorithmException if the supplied algorithm could not be found 169 * @throws IllegalArgumentException if the algorithm is null 170 * @throws IOException if there is an error reading the stream 171 */ 172 public static byte[] getHash( Algorithm algorithm, 173 InputStream stream ) throws NoSuchAlgorithmException, IOException { 174 CheckArg.isNotNull(algorithm, "algorithm"); 175 return getHash(algorithm.digestName(), stream); 176 } 177 178 /** 179 * Get the hash of the supplied content, using the digest identified by the supplied name. 180 * 181 * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used 182 * @param content the content to be hashed; may not be null 183 * @return the hash of the contents as a byte array 184 * @throws NoSuchAlgorithmException if the supplied algorithm could not be found 185 */ 186 public static byte[] getHash( String digestName, 187 byte[] content ) throws NoSuchAlgorithmException { 188 MessageDigest digest = MessageDigest.getInstance(digestName); 189 assert digest != null; 190 return digest.digest(content); 191 } 192 193 /** 194 * Get the hash of the supplied content, using the digest identified by the supplied name. 195 * 196 * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used 197 * @param file the file whose content is to be hashed; may not be null 198 * @return the hash of the contents as a byte array 199 * @throws NoSuchAlgorithmException if the supplied algorithm could not be found 200 * @throws IOException if there is an error reading the file 201 */ 202 public static byte[] getHash( String digestName, 203 File file ) throws NoSuchAlgorithmException, IOException { 204 CheckArg.isNotNull(file, "file"); 205 MessageDigest digest = MessageDigest.getInstance(digestName); 206 assert digest != null; 207 InputStream in = new BufferedInputStream(new FileInputStream(file)); 208 boolean error = false; 209 try { 210 int bufSize = 1024; 211 byte[] buffer = new byte[bufSize]; 212 int n = in.read(buffer, 0, bufSize); 213 while (n != -1) { 214 digest.update(buffer, 0, n); 215 n = in.read(buffer, 0, bufSize); 216 } 217 } catch (IOException e) { 218 error = true; 219 throw e; 220 } finally { 221 try { 222 in.close(); 223 } catch (IOException e) { 224 if (!error) throw e; 225 } 226 } 227 return digest.digest(); 228 } 229 230 /** 231 * Get the hash of the supplied content, using the digest identified by the supplied name. Note that this method never closes 232 * the supplied stream. 233 * 234 * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used 235 * @param stream the stream containing the content to be hashed; may not be null 236 * @return the hash of the contents as a byte array 237 * @throws NoSuchAlgorithmException if the supplied algorithm could not be found 238 * @throws IOException if there is an error reading the stream 239 */ 240 public static byte[] getHash( String digestName, 241 InputStream stream ) throws NoSuchAlgorithmException, IOException { 242 CheckArg.isNotNull(stream, "stream"); 243 MessageDigest digest = MessageDigest.getInstance(digestName); 244 assert digest != null; 245 int bufSize = 1024; 246 byte[] buffer = new byte[bufSize]; 247 int n = stream.read(buffer, 0, bufSize); 248 while (n != -1) { 249 digest.update(buffer, 0, n); 250 n = stream.read(buffer, 0, bufSize); 251 } 252 return digest.digest(); 253 } 254 255 /** 256 * Create an InputStream instance that wraps another stream and that computes the secure hash (using the algorithm with the 257 * supplied name) as the returned stream is used. This can be used to compute the hash of a stream while the stream is being 258 * processed by another reader, and saves from having to process the same stream twice. 259 * 260 * @param algorithm the hashing function algorithm that should be used 261 * @param inputStream the stream containing the content that is to be hashed 262 * @return the hash of the contents as a byte array 263 * @throws NoSuchAlgorithmException 264 */ 265 public static HashingInputStream createHashingStream( Algorithm algorithm, 266 InputStream inputStream ) throws NoSuchAlgorithmException { 267 return createHashingStream(algorithm.digestName(), inputStream); 268 } 269 270 /** 271 * Create an InputStream instance that wraps another stream and that computes the secure hash (using the algorithm with the 272 * supplied name) as the returned stream is used. This can be used to compute the hash of a stream while the stream is being 273 * processed by another reader, and saves from having to process the same stream twice. 274 * 275 * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used 276 * @param inputStream the stream containing the content that is to be hashed 277 * @return the hash of the contents as a byte array 278 * @throws NoSuchAlgorithmException 279 */ 280 public static HashingInputStream createHashingStream( String digestName, 281 InputStream inputStream ) throws NoSuchAlgorithmException { 282 MessageDigest digest = MessageDigest.getInstance(digestName); 283 return new HashingInputStream(digest, inputStream); 284 } 285 286 /** 287 * Create an Reader instance that wraps another reader and that computes the secure hash (using the algorithm with the 288 * supplied name) as the returned Reader is used. This can be used to compute the hash while the content is being processed, 289 * and saves from having to process the same content twice. 290 * 291 * @param algorithm the hashing function algorithm that should be used 292 * @param reader the reader containing the content that is to be hashed 293 * @param charset the character set used within the supplied reader; may not be null 294 * @return the hash of the contents as a byte array 295 * @throws NoSuchAlgorithmException 296 */ 297 public static HashingReader createHashingReader( Algorithm algorithm, 298 Reader reader, 299 Charset charset ) throws NoSuchAlgorithmException { 300 return createHashingReader(algorithm.digestName(), reader, charset); 301 } 302 303 /** 304 * Create an Reader instance that wraps another reader and that computes the secure hash (using the algorithm with the 305 * supplied name) as the returned Reader is used. This can be used to compute the hash while the content is being processed, 306 * and saves from having to process the same content twice. 307 * 308 * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used 309 * @param reader the reader containing the content that is to be hashed 310 * @param charset the character set used within the supplied reader; may not be null 311 * @return the hash of the contents as a byte array 312 * @throws NoSuchAlgorithmException 313 */ 314 public static HashingReader createHashingReader( String digestName, 315 Reader reader, 316 Charset charset ) throws NoSuchAlgorithmException { 317 MessageDigest digest = MessageDigest.getInstance(digestName); 318 return new HashingReader(digest, reader, charset); 319 } 320 321 /** 322 * Get the string representation of the supplied binary hash. 323 * 324 * @param hash the binary hash 325 * @return the hex-encoded representation of the binary hash, or null if the hash is null 326 */ 327 public static String asHexString( byte[] hash ) { 328 return hash != null ? StringUtil.getHexString(hash) : null; 329 } 330 331 /** 332 * Computes the sha1 value for the given string. 333 * 334 * @param string a non-null string 335 * @return the SHA1 value for the given string. 336 */ 337 public static String sha1( String string ) { 338 try { 339 byte[] sha1 = SecureHash.getHash(SecureHash.Algorithm.SHA_1, string.getBytes()); 340 return SecureHash.asHexString(sha1); 341 } catch (NoSuchAlgorithmException e) { 342 throw new SystemFailureException(e); 343 } 344 } 345 346 public static class HashingInputStream extends InputStream { 347 private final MessageDigest digest; 348 private final InputStream stream; 349 private byte[] hash; 350 351 protected HashingInputStream( MessageDigest digest, 352 InputStream input ) { 353 this.digest = digest; 354 this.stream = input; 355 } 356 357 @Override 358 public int read() throws IOException { 359 int result = stream.read(); 360 if (result != -1) { 361 digest.update((byte)result); 362 } 363 return result; 364 } 365 366 @Override 367 public int read( byte[] b, 368 int off, 369 int len ) throws IOException { 370 // Read from the stream ... 371 int n = stream.read(b, off, len); 372 if (n != -1) { 373 digest.update(b, off, n); 374 } 375 return n; 376 } 377 378 @Override 379 public int read( byte[] b ) throws IOException { 380 int n = stream.read(b); 381 if (n != -1) { 382 digest.update(b, 0, n); 383 } 384 return n; 385 } 386 387 @Override 388 public void close() throws IOException { 389 stream.close(); 390 if (hash == null) hash = digest.digest(); 391 } 392 393 /** 394 * Get the hash of the content read by this stream. This method will return null if the stream has not yet been closed. 395 * 396 * @return the hash of the contents as a byte array, or null if the stream has not yet been closed 397 */ 398 public byte[] getHash() { 399 return hash; 400 } 401 402 /** 403 * Get the string representation of the binary hash of the content read by this stream. This method will return null if 404 * the stream has not yet been closed. 405 * 406 * @return the hex-encoded representation of the binary hash of the contents, or null if the stream has not yet been 407 * closed 408 */ 409 public String getHashAsHexString() { 410 return SecureHash.asHexString(hash); 411 } 412 } 413 414 public static class HashingReader extends Reader { 415 private final MessageDigest digest; 416 private final Reader stream; 417 private byte[] hash; 418 private final CharsetEncoder encoder; 419 420 protected HashingReader( MessageDigest digest, 421 Reader input, 422 Charset charset ) { 423 this.digest = digest; 424 this.stream = input; 425 this.encoder = charset.newEncoder(); 426 } 427 428 /** 429 * {@inheritDoc} 430 * 431 * @see java.io.Reader#read() 432 */ 433 @Override 434 public int read() throws IOException { 435 int result = stream.read(); 436 if (result != -1) { 437 digest.update((byte)result); 438 } 439 return result; 440 } 441 442 /** 443 * {@inheritDoc} 444 * 445 * @see java.io.Reader#read(char[], int, int) 446 */ 447 @Override 448 public int read( char[] b, 449 int off, 450 int len ) throws IOException { 451 // Read from the stream ... 452 int n = stream.read(b, off, len); 453 if (n != -1) { 454 byte[] bytes = encoder.encode(CharBuffer.wrap(b)).array(); 455 digest.update(bytes, off, n); 456 } 457 return n; 458 } 459 460 /** 461 * {@inheritDoc} 462 * 463 * @see java.io.Reader#read(char[]) 464 */ 465 @Override 466 public int read( char[] b ) throws IOException { 467 int n = stream.read(b); 468 if (n != -1) { 469 byte[] bytes = encoder.encode(CharBuffer.wrap(b)).array(); 470 digest.update(bytes, 0, n); 471 } 472 return n; 473 } 474 475 /** 476 * {@inheritDoc} 477 * 478 * @see java.io.InputStream#close() 479 */ 480 @Override 481 public void close() throws IOException { 482 stream.close(); 483 if (hash == null) hash = digest.digest(); 484 } 485 486 /** 487 * Get the hash of the content read by this reader. This method will return null if the reader has not yet been closed. 488 * 489 * @return the hash of the contents as a byte array, or null if the reader has not yet been closed 490 */ 491 public byte[] getHash() { 492 return hash; 493 } 494 495 /** 496 * Get the string representation of the binary hash of the content read by this reader. This method will return null if 497 * the reader has not yet been closed. 498 * 499 * @return the hex-encoded representation of the binary hash of the contents, or null if the reader has not yet been 500 * closed 501 */ 502 public String getHashAsHexString() { 503 return SecureHash.asHexString(hash); 504 } 505 } 506 507 private SecureHash() { 508 } 509}