/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.bean;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.util.Arrays;
import org.cryptacular.CryptoException;
import org.cryptacular.EncodingException;
import org.cryptacular.StreamException;
import org.cryptacular.codec.Codec;
import org.cryptacular.spec.Spec;
import org.cryptacular.util.CodecUtil;
/**
* Computes a hash in an encoded format, e.g. hex, base64.
*
* @author Middleware Services
*/
public class EncodingHashBean extends AbstractHashBean implements HashBean<String>
{
/** Determines kind of encoding. */
private Spec<Codec> codecSpec;
/** Whether data provided to this bean includes a salt. */
private boolean salted;
/** Creates a new instance. */
public EncodingHashBean() {}
/**
* Creates a new instance that will not be salted. Delegates to {@link #EncodingHashBean(Spec, Spec, int, boolean)}.
*
* @param codecSpec Digest specification.
* @param digestSpec Digest specification.
* @param iterations Number of hash rounds.
*/
public EncodingHashBean(
final Spec<Codec> codecSpec,
final Spec<Digest> digestSpec,
final int iterations)
{
this(codecSpec, digestSpec, iterations, false);
}
/**
* Creates a new instance by specifying all properties.
*
* @param codecSpec Digest specification.
* @param digestSpec Digest specification.
* @param iterations Number of hash rounds.
* @param salted Whether hash data will be salted.
*/
public EncodingHashBean(
final Spec<Codec> codecSpec,
final Spec<Digest> digestSpec,
final int iterations,
final boolean salted)
{
super(digestSpec, iterations);
setCodecSpec(codecSpec);
setSalted(salted);
}
/** @return Codec specification that determines the encoding applied to the hash output bytes. */
public Spec<Codec> getCodecSpec()
{
return codecSpec;
}
/**
* Sets the codec specification that determines the encoding applied to the hash output bytes.
*
* @param codecSpec Codec specification, e.g. {@link org.cryptacular.spec.CodecSpec#BASE64}, {@link
* org.cryptacular.spec.CodecSpec#HEX}.
*/
public void setCodecSpec(final Spec<Codec> codecSpec)
{
this.codecSpec = codecSpec;
}
/**
* Whether data provided to {@link #hash(Object...)} includes a salt as the last parameter.
*
* @return whether hash data includes a salt
*/
public boolean isSalted()
{
return salted;
}
/**
* Sets whether {@link #hash(Object...)} should expect a salt as the last parameter.
*
* @param salted whether hash data includes a salt
*/
public void setSalted(final boolean salted)
{
this.salted = salted;
}
/**
* Hashes the given data. If {@link #isSalted()} is true then the last parameter MUST be a byte array containing the
* salt. The salt value will be appended to the encoded hash that is returned.
*
* @param data Data to hash.
*
* @return Encoded digest output, including a salt if provided.
*
* @throws CryptoException on hash computation errors.
* @throws EncodingException on encoding errors.
* @throws StreamException on stream IO errors.
*/
@Override
public String hash(final Object... data) throws CryptoException, EncodingException, StreamException
{
if (salted) {
if (data.length < 2 || !(data[data.length - 1] instanceof byte[])) {
throw new IllegalArgumentException("Last parameter must be a salt of type byte[]");
}
final byte[] hashSalt = (byte[]) data[data.length - 1];
return CodecUtil.encode(codecSpec.newInstance().getEncoder(), Arrays.concatenate(hashInternal(data), hashSalt));
}
return CodecUtil.encode(codecSpec.newInstance().getEncoder(), hashInternal(data));
}
/**
* Compares a known hash value with the hash of the given data.
*
* @param hash Known encoded hash value. If the length of the hash bytes after decoding is greater than the length
* of the digest output, anything beyond the digest length is considered salt data that is hashed
* <strong>after</strong> the input data.
* @param data Data to hash.
*
* @return True if the hashed data matches the given hash, false otherwise.
*
* @throws CryptoException on hash computation errors.
* @throws EncodingException on encoding errors.
* @throws StreamException on stream IO errors.
*/
@Override
public boolean compare(final String hash, final Object... data)
throws CryptoException, EncodingException, StreamException
{
return compareInternal(CodecUtil.decode(codecSpec.newInstance().getDecoder(), hash), data);
}
}