/*
* 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() {
}
}