/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.common.util; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.modeshape.common.SystemFailureException; import org.modeshape.common.annotation.Immutable; /** * A simple utility to generate various kinds of secure hashes. */ @Immutable public class SecureHash { /** * Commonly-used hashing algorithms. */ @Immutable public enum Algorithm { MD2("MD2", 128, "The MD2 message digest algorithm as defined in RFC 1319"), MD5("MD5", 128, "The MD5 message digest algorithm as defined in RFC 1321"), SHA_1("SHA-1", 160, "The Secure Hash Algorithm, as defined in Secure Hash Standard, NIST FIPS 180-1"), SHA_256( "SHA-256", 256, "New hash algorithms for which the draft Federal Information Processing Standard 180-2, " + "Secure Hash Standard (SHS) is now available. SHA-256 is a 256-bit hash function intended to provide 128 bits of " + "security against collision attacks."), SHA_384( "SHA-384", 384, "New hash algorithms for which the draft Federal Information Processing Standard 180-2, " + "Secure Hash Standard (SHS) is now available. A 384-bit hash may be obtained by truncating the SHA-512 output."), SHA_512( "SHA-512", 512, "New hash algorithms for which the draft Federal Information Processing Standard 180-2, " + "Secure Hash Standard (SHS) is now available. SHA-512 is a 512-bit hash function intended to provide 256 bits of security."); private final String name; private final String description; private final int numberOfBits; private final int numberOfBytes; private final int numberOfHexChars; private Algorithm( String name, int numberOfBits, String description ) { assert numberOfBits % 8 == 0; this.name = name; this.description = description; this.numberOfBits = numberOfBits; this.numberOfBytes = this.numberOfBits / 8; this.numberOfHexChars = this.numberOfBits / 4; } public String digestName() { return this.name; } public String description() { return this.description; } /** * Get the length of the hexadecimal representation. * * @return the number of hexadecimal characters */ public int getHexadecimalStringLength() { return numberOfHexChars; } /** * Determine whether the supplied string is of the correct format to contain a hexadecimal representation of this * algorithm. * * @param string the string; may not be null * @return true if the string might contain a hexadecimal representation of this algorithm, or false otherwise */ public boolean isHexadecimal( String string ) { return string.length() == getHexadecimalStringLength() && StringUtil.isHexString(string); } /** * Get the length of the hexadecimal representation. * * @return the number of hexadecimal characters */ public int getNumberOfBytes() { return numberOfBytes; } /** * Get the number of bits that make up a digest. * * @return the number of bits */ public int getNumberOfBits() { return numberOfBits; } @Override public String toString() { return digestName(); } } /** * Get the hash of the supplied content, using the supplied digest algorithm. * * @param algorithm the hashing function algorithm that should be used * @param content the content to be hashed; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException if the supplied algorithm could not be found * @throws IllegalArgumentException if the algorithm is null */ public static byte[] getHash( Algorithm algorithm, byte[] content ) throws NoSuchAlgorithmException { CheckArg.isNotNull(algorithm, "algorithm"); return getHash(algorithm.digestName(), content); } /** * Get the hash of the supplied content, using the supplied digest algorithm. * * @param algorithm the hashing function algorithm that should be used * @param file the file containing the content to be hashed; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException if the supplied algorithm could not be found * @throws IllegalArgumentException if the algorithm is null * @throws IOException if there is an error reading the file */ public static byte[] getHash( Algorithm algorithm, File file ) throws NoSuchAlgorithmException, IOException { CheckArg.isNotNull(algorithm, "algorithm"); return getHash(algorithm.digestName(), file); } /** * Get the hash of the supplied content, using the supplied digest algorithm. * * @param algorithm the hashing function algorithm that should be used * @param stream the stream containing the content to be hashed; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException if the supplied algorithm could not be found * @throws IllegalArgumentException if the algorithm is null * @throws IOException if there is an error reading the stream */ public static byte[] getHash( Algorithm algorithm, InputStream stream ) throws NoSuchAlgorithmException, IOException { CheckArg.isNotNull(algorithm, "algorithm"); return getHash(algorithm.digestName(), stream); } /** * Get the hash of the supplied content, using the digest identified by the supplied name. * * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used * @param content the content to be hashed; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException if the supplied algorithm could not be found */ public static byte[] getHash( String digestName, byte[] content ) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance(digestName); assert digest != null; return digest.digest(content); } /** * Get the hash of the supplied content, using the digest identified by the supplied name. * * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used * @param file the file whose content is to be hashed; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException if the supplied algorithm could not be found * @throws IOException if there is an error reading the file */ public static byte[] getHash( String digestName, File file ) throws NoSuchAlgorithmException, IOException { CheckArg.isNotNull(file, "file"); MessageDigest digest = MessageDigest.getInstance(digestName); assert digest != null; InputStream in = new BufferedInputStream(new FileInputStream(file)); boolean error = false; try { int bufSize = 1024; byte[] buffer = new byte[bufSize]; int n = in.read(buffer, 0, bufSize); while (n != -1) { digest.update(buffer, 0, n); n = in.read(buffer, 0, bufSize); } } catch (IOException e) { error = true; throw e; } finally { try { in.close(); } catch (IOException e) { if (!error) throw e; } } return digest.digest(); } /** * Get the hash of the supplied content, using the digest identified by the supplied name. Note that this method never closes * the supplied stream. * * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used * @param stream the stream containing the content to be hashed; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException if the supplied algorithm could not be found * @throws IOException if there is an error reading the stream */ public static byte[] getHash( String digestName, InputStream stream ) throws NoSuchAlgorithmException, IOException { CheckArg.isNotNull(stream, "stream"); MessageDigest digest = MessageDigest.getInstance(digestName); assert digest != null; int bufSize = 1024; byte[] buffer = new byte[bufSize]; int n = stream.read(buffer, 0, bufSize); while (n != -1) { digest.update(buffer, 0, n); n = stream.read(buffer, 0, bufSize); } return digest.digest(); } /** * Create an InputStream instance that wraps another stream and that computes the secure hash (using the algorithm with the * supplied name) as the returned stream is used. This can be used to compute the hash of a stream while the stream is being * processed by another reader, and saves from having to process the same stream twice. * * @param algorithm the hashing function algorithm that should be used * @param inputStream the stream containing the content that is to be hashed * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException */ public static HashingInputStream createHashingStream( Algorithm algorithm, InputStream inputStream ) throws NoSuchAlgorithmException { return createHashingStream(algorithm.digestName(), inputStream); } /** * Create an InputStream instance that wraps another stream and that computes the secure hash (using the algorithm with the * supplied name) as the returned stream is used. This can be used to compute the hash of a stream while the stream is being * processed by another reader, and saves from having to process the same stream twice. * * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used * @param inputStream the stream containing the content that is to be hashed * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException */ public static HashingInputStream createHashingStream( String digestName, InputStream inputStream ) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance(digestName); return new HashingInputStream(digest, inputStream); } /** * Create an Reader instance that wraps another reader and that computes the secure hash (using the algorithm with the * supplied name) as the returned Reader is used. This can be used to compute the hash while the content is being processed, * and saves from having to process the same content twice. * * @param algorithm the hashing function algorithm that should be used * @param reader the reader containing the content that is to be hashed * @param charset the character set used within the supplied reader; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException */ public static HashingReader createHashingReader( Algorithm algorithm, Reader reader, Charset charset ) throws NoSuchAlgorithmException { return createHashingReader(algorithm.digestName(), reader, charset); } /** * Create an Reader instance that wraps another reader and that computes the secure hash (using the algorithm with the * supplied name) as the returned Reader is used. This can be used to compute the hash while the content is being processed, * and saves from having to process the same content twice. * * @param digestName the name of the hashing function (or {@link MessageDigest message digest}) that should be used * @param reader the reader containing the content that is to be hashed * @param charset the character set used within the supplied reader; may not be null * @return the hash of the contents as a byte array * @throws NoSuchAlgorithmException */ public static HashingReader createHashingReader( String digestName, Reader reader, Charset charset ) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance(digestName); return new HashingReader(digest, reader, charset); } /** * Get the string representation of the supplied binary hash. * * @param hash the binary hash * @return the hex-encoded representation of the binary hash, or null if the hash is null */ public static String asHexString( byte[] hash ) { return hash != null ? StringUtil.getHexString(hash) : null; } /** * Computes the sha1 value for the given string. * * @param string a non-null string * @return the SHA1 value for the given string. */ public static String sha1( String string ) { try { byte[] sha1 = SecureHash.getHash(SecureHash.Algorithm.SHA_1, string.getBytes()); return SecureHash.asHexString(sha1); } catch (NoSuchAlgorithmException e) { throw new SystemFailureException(e); } } public static class HashingInputStream extends InputStream { private final MessageDigest digest; private final InputStream stream; private byte[] hash; protected HashingInputStream( MessageDigest digest, InputStream input ) { this.digest = digest; this.stream = input; } @Override public int read() throws IOException { int result = stream.read(); if (result != -1) { digest.update((byte)result); } return result; } @Override public int read( byte[] b, int off, int len ) throws IOException { // Read from the stream ... int n = stream.read(b, off, len); if (n != -1) { digest.update(b, off, n); } return n; } @Override public int read( byte[] b ) throws IOException { int n = stream.read(b); if (n != -1) { digest.update(b, 0, n); } return n; } @Override public void close() throws IOException { stream.close(); if (hash == null) hash = digest.digest(); } /** * Get the hash of the content read by this stream. This method will return null if the stream has not yet been closed. * * @return the hash of the contents as a byte array, or null if the stream has not yet been closed */ public byte[] getHash() { return hash; } /** * Get the string representation of the binary hash of the content read by this stream. This method will return null if * the stream has not yet been closed. * * @return the hex-encoded representation of the binary hash of the contents, or null if the stream has not yet been * closed */ public String getHashAsHexString() { return SecureHash.asHexString(hash); } } public static class HashingReader extends Reader { private final MessageDigest digest; private final Reader stream; private byte[] hash; private final CharsetEncoder encoder; protected HashingReader( MessageDigest digest, Reader input, Charset charset ) { this.digest = digest; this.stream = input; this.encoder = charset.newEncoder(); } /** * {@inheritDoc} * * @see java.io.Reader#read() */ @Override public int read() throws IOException { int result = stream.read(); if (result != -1) { digest.update((byte)result); } return result; } /** * {@inheritDoc} * * @see java.io.Reader#read(char[], int, int) */ @Override public int read( char[] b, int off, int len ) throws IOException { // Read from the stream ... int n = stream.read(b, off, len); if (n != -1) { byte[] bytes = encoder.encode(CharBuffer.wrap(b)).array(); digest.update(bytes, off, n); } return n; } /** * {@inheritDoc} * * @see java.io.Reader#read(char[]) */ @Override public int read( char[] b ) throws IOException { int n = stream.read(b); if (n != -1) { byte[] bytes = encoder.encode(CharBuffer.wrap(b)).array(); digest.update(bytes, 0, n); } return n; } /** * {@inheritDoc} * * @see java.io.InputStream#close() */ @Override public void close() throws IOException { stream.close(); if (hash == null) hash = digest.digest(); } /** * Get the hash of the content read by this reader. This method will return null if the reader has not yet been closed. * * @return the hash of the contents as a byte array, or null if the reader has not yet been closed */ public byte[] getHash() { return hash; } /** * Get the string representation of the binary hash of the content read by this reader. This method will return null if * the reader has not yet been closed. * * @return the hex-encoded representation of the binary hash of the contents, or null if the reader has not yet been * closed */ public String getHashAsHexString() { return SecureHash.asHexString(hash); } } private SecureHash() { } }