org.cryptacular.util.HashUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cryptacular Show documentation
Show all versions of cryptacular Show documentation
The spectacular complement to the Bouncy Castle crypto API for Java.
The newest version!
/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.cryptacular.CryptoException;
import org.cryptacular.SaltedHash;
import org.cryptacular.StreamException;
import org.cryptacular.io.Resource;
/**
* Utility class for computing cryptographic hashes.
*
* @author Middleware Services
*/
public final class HashUtil
{
/** Private constructor of utility class. */
private HashUtil() {}
/**
* Computes the hash of the given data using the given algorithm. A salted hash may be produced as follows:
*
*
// data is a byte array containing raw data to digest
final byte[] salt = new RBGNonce(16).generate();
final byte[] hash = HashUtil.hash(new SHA1Digest(), data, salt);
*
*
* @param digest Hash algorithm.
* @param data Data to hash. Supported types are byte[]
, {@link CharSequence} ,{@link InputStream}, and
* {@link Resource}. Character data is processed in the UTF-8
character set; if another
* character set is desired, the caller should convert to byte[]
and provide the resulting
* bytes.
*
* @return Byte array of length {@link Digest#getDigestSize()} containing hash output.
*
* @throws CryptoException on hash computation errors.
* @throws StreamException on stream IO errors.
*/
public static byte[] hash(final Digest digest, final Object... data) throws CryptoException, StreamException
{
for (Object o : data) {
if (o instanceof byte[]) {
final byte[] bytes = (byte[]) o;
digest.update(bytes, 0, bytes.length);
} else if (o instanceof String) {
final byte[] bytes = ByteUtil.toBytes((String) o);
digest.update(bytes, 0, bytes.length);
} else if (o instanceof InputStream) {
hashStream(digest, (InputStream) o);
} else if (o instanceof Resource) {
final InputStream in;
try {
in = ((Resource) o).getInputStream();
} catch (IOException e) {
throw new StreamException(e);
}
hashStream(digest, in);
} else {
throw new IllegalArgumentException("Invalid input data type " + o);
}
}
final byte[] output = new byte[digest.getDigestSize()];
try {
digest.doFinal(output, 0);
} catch (RuntimeException e) {
throw new CryptoException("Hash computation error", e);
}
return output;
}
/**
* Computes the iterated hash of the given data using the given algorithm. The following example demonstrates a
* typical usage pattern, a salted hash with 10 rounds:
*
*
// data is a byte array containing raw data to digest
final byte[] salt = new RBGNonce(16).generate();
final byte[] hash = HashUtil.hash(new SHA1Digest(), 10, data, salt);
*
*
* @param digest Hash algorithm.
* @param iterations Number of hash rounds. Must be positive value.
* @param data Data to hash. Supported types are byte[]
, {@link CharSequence} ,{@link InputStream}, and
* {@link Resource}. Character data is processed in the UTF-8
character set; if another
* character set is desired, the caller should convert to byte[]
and provide the resulting
* bytes.
*
* @return Byte array of length {@link Digest#getDigestSize()} containing hash output.
*
* @throws CryptoException on hash computation errors.
* @throws StreamException on stream IO errors.
*/
public static byte[] hash(final Digest digest, final int iterations, final Object... data)
throws CryptoException, StreamException
{
if (iterations < 1) {
throw new IllegalArgumentException("Iterations must be positive");
}
final byte[] output = hash(digest, data);
try {
for (int i = 1; i < iterations; i++) {
digest.update(output, 0, output.length);
digest.doFinal(output, 0);
}
} catch (RuntimeException e) {
throw new CryptoException("Hash computation error", e);
}
return output;
}
/**
* Determines whether the hash of the given input equals a known value.
*
* @param digest Hash algorithm.
* @param hash Hash to compare with. If the length of the array is greater than the length of the digest output,
* anything beyond the digest length is considered salt data that is hashed after the
* input data.
* @param iterations Number of hash rounds.
* @param data Data to hash.
*
* @return True if the hash of the data under the given digest is equal to the hash, false otherwise.
*
* @throws CryptoException on hash computation errors.
* @throws StreamException on stream IO errors.
*/
public static boolean compareHash(final Digest digest, final byte[] hash, final int iterations, final Object... data)
throws CryptoException, StreamException
{
if (hash.length > digest.getDigestSize()) {
final byte[] hashPart = Arrays.copyOfRange(hash, 0, digest.getDigestSize());
final byte[] saltPart = Arrays.copyOfRange(hash, digest.getDigestSize(), hash.length);
final Object[] dataWithSalt = Arrays.copyOf(data, data.length + 1);
dataWithSalt[data.length] = saltPart;
return Arrays.equals(hash(digest, iterations, dataWithSalt), hashPart);
}
return Arrays.equals(hash(digest, iterations, data), hash);
}
/**
* Determines whether the salted hash of the given input equals a known hash value.
*
* @param digest Hash algorithm.
* @param hash Salted hash data.
* @param iterations Number of hash rounds.
* @param saltAfterData True to apply salt after data, false to apply salt before data.
* @param data Data to hash, which should NOT include the salt value.
*
* @return True if the hash of the data under the given digest is equal to the hash, false otherwise.
*
* @throws CryptoException on hash computation errors.
* @throws StreamException on stream IO errors.
*/
public static boolean compareHash(
final Digest digest,
final SaltedHash hash,
final int iterations,
final boolean saltAfterData,
final Object... data)
throws CryptoException, StreamException
{
final Object[] dataWithSalt;
if (saltAfterData) {
dataWithSalt = Arrays.copyOf(data, data.length + 1);
dataWithSalt[data.length] = hash.getSalt();
} else {
dataWithSalt = new Object[data.length + 1];
dataWithSalt[0] = hash.getSalt();
System.arraycopy(data, 0, dataWithSalt, 1, data.length);
}
return Arrays.equals(hash(digest, iterations, dataWithSalt), hash.getHash());
}
/**
* Produces the SHA-1 hash of the given data.
*
* @param data Data to hash. See {@link #hash(Digest, Object...)} for supported inputs.
*
* @return 20-byte array containing hash output.
*
* @see #hash(Digest, Object...)
*/
public static byte[] sha1(final Object... data)
{
return hash(new SHA1Digest(), data);
}
/**
* Produces the SHA-256 hash of the given data.
*
* @param data Data to hash. See {@link #hash(Digest, Object...)} for supported inputs.
*
* @return 32-byte array containing hash output.
*
* @see #hash(Digest, Object...)
*/
public static byte[] sha256(final Object... data)
{
return hash(new SHA256Digest(), data);
}
/**
* Produces the SHA-512 hash of the given data.
*
* @param data Data to hash. See {@link #hash(Digest, Object...)} for supported inputs.
*
* @return 64-byte array containing hash output.
*
* @see #hash(Digest, Object...)
*/
public static byte[] sha512(final Object... data)
{
return hash(new SHA512Digest(), data);
}
/**
* Produces the SHA-3 hash of the given data.
*
* @param bitLength One of the supported SHA-3 output bit lengths: 224, 256, 384, or 512.
* @param data Data to hash. See {@link #hash(Digest, Object...)} for supported inputs.
*
* @return Byte array of size bitLength
containing hash output.
*
* @see #hash(Digest, Object...)
*/
public static byte[] sha3(final int bitLength, final Object... data)
{
return hash(new SHA3Digest(bitLength), data);
}
/**
* Digests the data in the given stream. Note this method does not finalize the digest process by calling {@link
* Digest#doFinal(byte[], int)}.
*
* @param digest Digest algorithm.
* @param in Input stream containing data to hash.
*/
private static void hashStream(final Digest digest, final InputStream in)
{
final byte[] buffer = new byte[StreamUtil.CHUNK_SIZE];
int length;
try {
while ((length = in.read(buffer)) > 0) {
digest.update(buffer, 0, length);
}
} catch (IOException e) {
throw new StreamException(e);
}
}
}