package io.eguan.hash;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import io.eguan.utils.ByteArrays;
import java.nio.ByteBuffer;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import com.google.protobuf.ByteString;
/**
* Hash algorithm. The class can be associated to {@link ByteBufferDigest} to create and check persistent hash digest of
* the contents of a {@link ByteBuffer}.
*
* @author oodrive
* @author llambert
*
*/
public enum HashAlgorithm {
/**
* MD5 as defined by <a href='http://tools.ietf.org/html/rfc1321'>RFC 1321</a>.
*/
MD5("MD5", (byte) 0, 16) {
/*
* Implementation based on MD5Digest if the native is found.
*
* @see io.eguan.hash.HashAlgorithm#getByteBufferDigestProvider(java.nio.ByteBuffer)
*/
@Override
final ByteBufferDigestProvider getByteBufferDigestProvider(final ByteBuffer byteBuffer) {
// Prefer native implementation if available
if (MD5Digest.isNative()) {
final ByteBuffer digest = MD5Digest.doFinalNative(byteBuffer);
return getByteBufferDigestProvider(this, digest);
}
else {
return super.getByteBufferDigestProvider(byteBuffer);
}
}
@Override
ByteBufferDigestProvider getByteStringDigestProvider(final ByteString byteString) {
// Prefer native implementation if available
if (MD5Digest.isNative()) {
final ByteBuffer digest = MD5Digest.doFinalNative(byteString);
return getByteBufferDigestProvider(this, digest);
}
else {
return super.getByteStringDigestProvider(byteString);
}
}
},
/**
* TIGER as defined by <a href='http://www.cs.technion.ac.il/~biham/Reports/Tiger/'>Tiger — A Fast New Hash
* Function</a>.
*/
TIGER("Tiger", (byte) 1, 24) {
/*
* Implementation based on TigerDigest.
*
* @see io.eguan.hash.HashAlgorithm#getByteBufferDigestProvider(java.nio.ByteBuffer)
*/
@Override
final ByteBufferDigestProvider getByteBufferDigestProvider(final ByteBuffer byteBuffer) {
// Prefer native implementation if available
if (TigerDigest.isNative()) {
final ByteBuffer digest = TigerDigest.doFinalNative(byteBuffer);
return getByteBufferDigestProvider(this, digest);
}
else {
final TigerDigest digest = new TigerDigest(byteBuffer);
return getByteBufferDigestProvider(this, digest);
}
}
@Override
ByteBufferDigestProvider getByteStringDigestProvider(final ByteString byteString) {
// Prefer native implementation if available
if (TigerDigest.isNative()) {
final ByteBuffer digest = TigerDigest.doFinalNative(byteString);
return getByteBufferDigestProvider(this, digest);
}
else {
return super.getByteStringDigestProvider(byteString);
}
}
},
/**
* SHA-1 as defined by <a href='http://tools.ietf.org/html/rfc3174'>RFC 3174</a>.
*/
SHA1("SHA-1", (byte) 2, 20) {
/*
* Implementation based on SHA1Digest if the native is found.
*
* @see io.eguan.hash.HashAlgorithm#getByteBufferDigestProvider(java.nio.ByteBuffer)
*/
@Override
final ByteBufferDigestProvider getByteBufferDigestProvider(final ByteBuffer byteBuffer) {
// Prefer native implementation if available
if (SHA1Digest.isNative()) {
final ByteBuffer digest = SHA1Digest.doFinalNative(byteBuffer);
return getByteBufferDigestProvider(this, digest);
}
else {
return super.getByteBufferDigestProvider(byteBuffer);
}
}
@Override
ByteBufferDigestProvider getByteStringDigestProvider(final ByteString byteString) {
// Prefer native implementation if available
if (SHA1Digest.isNative()) {
final ByteBuffer digest = SHA1Digest.doFinalNative(byteString);
return getByteBufferDigestProvider(this, digest);
}
else {
return super.getByteStringDigestProvider(byteString);
}
}
},
/**
* SHA-256 as defined by <a href='http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf'>FIPS 180-3:
* Secure Hash Standard (SHS)</a>.
*/
SHA256("SHA-256", (byte) 3, 32),
/**
* SHA-512 as defined by <a href='http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf'>FIPS 180-3:
* Secure Hash Standard (SHS)</a>.
*/
SHA512("SHA-512", (byte) 4, 64);
/** Version of the hash digest encoding */
private static final byte VERSION1 = (byte) 0x40; // 0100 0000
/** Bits reserved for the version encoding in the header */
private static final byte VERSION_MASK = (byte) 0xC0; // 1100 0000
/**
* Create a new {@link HashAlgorithm}.
*
* @param standardName
* standard name, to get a {@link MessageDigest} for example.
* @param index
* index, id of the algorithm (written in the header)
* @param digestLen
* length of the digest in bytes
*/
private HashAlgorithm(final String standardName, final byte index, final int digestLen) {
this.standardName = standardName;
this.index = index;
this.digestLen = digestLen;
}
/** Standard name, needed to get an implementation */
private final String standardName;
/** Constant index, stored in constant hash. */
private final byte index;
/** digest length in bytes */
private final int digestLen;
/**
* Gets the Java standard name of the algorithm.
*
* @return the standard name.
*/
public final String getStandardName() {
return standardName;
}
/**
* Gets the length of a standard digest.
*
* @return the digest length
*/
public final int getStandardDigestLength() {
return digestLen;
}
/**
* Gets the length of a persistent digest.
*
* @return the length of a persistent digest (with header and trailer)
*/
public final int getPersistedDigestLength() {
return 1 + digestLen + 1;
}
/**
* Gets the {@link HashAlgorithm} used to code the given hash
*
* @param hash
* hash to analyze
* @return the {@link HashAlgorithm} used to code <code>hash</code>
* @throws NoSuchAlgorithmException
* if the hash can not be analyzed
*/
static final HashAlgorithm getHashHashAlgorithm(final byte[] hash) throws NoSuchAlgorithmException {
final byte header = hash[0];
final byte version = (byte) (header & VERSION_MASK);
final byte index = (byte) (header & ~VERSION_MASK);
if (version == VERSION1) {
for (final HashAlgorithm hashAlgorithm : values()) {
if (hashAlgorithm.index == index) {
return hashAlgorithm;
}
}
throw new NoSuchAlgorithmException("index=" + index);
}
else {
throw new NoSuchAlgorithmException("version=" + version);
}
}
/**
* Tells if the hash is valid for this algorithm.
*
* @param hash
* hash to test
* @return <code>true</code> if the hash is correct
*/
final boolean checkHash(final byte[] hash) {
final byte header = hash[0];
final byte version = (byte) (header & VERSION_MASK);
if (version == VERSION1) {
if (index != (byte) (header & ~VERSION_MASK)) {
// Wrong algorithm
return false;
}
// Check hash length: header+len+trailer
if (hash.length != (1 + digestLen + 1)) {
// Wrong length
return false;
}
// Check trailer
final int limit = 1 + digestLen;
byte trailer = hash[0];
for (int i = 1; i < limit; i++) {
trailer ^= hash[i];
}
if (hash[limit] != trailer) {
return false;
}
// Ok
return true;
}
else {
// Unknown version
return false;
}
}
/**
* Hash the byte between the position and the limit of the {@link ByteBuffer}. The {@link ByteBuffer} is left
* unchanged. Default implementation based on a {@link MessageDigest}.
*
* @param byteBuffer
* buffer to hash
* @return digest provider
*/
ByteBufferDigestProvider getByteBufferDigestProvider(final ByteBuffer byteBuffer) {
final MessageDigest digest;
try {
digest = MessageDigest.getInstance(getStandardName());
}
catch (final NoSuchAlgorithmException e) {
// Algorithms should be supported by the JVM
throw new IllegalStateException(e);
}
// Hash the whole ByteBuffer
final ByteBuffer duplicate = byteBuffer.duplicate();
final byte[] src = new byte[duplicate.limit() - duplicate.position()];
duplicate.get(src);
assert duplicate.position() == duplicate.limit();
digest.update(src);
// Allocate result: header <digest> trailer
final byte[] hash = new byte[1 + digestLen + 1];
final int offset = writeHeader(hash);
try {
digest.digest(hash, offset, digestLen);
}
catch (final DigestException e) {
// Should not occur
throw new IllegalStateException(e);
}
// Write trailer
writeTrailer(hash, offset + digestLen);
return new ByteBufferDigestProviderImpl(this, hash);
}
ByteBufferDigestProvider getByteStringDigestProvider(final ByteString byteString) {
return getByteBufferDigestProvider(byteString.asReadOnlyByteBuffer());
}
static final ByteBufferDigestProvider getByteBufferDigestProvider(final HashAlgorithm hashAlgorithm,
final Digest digest) {
// Allocate result: header <digest> trailer
final int len = digest.getDigestSize();
final byte[] hash = new byte[1 + len + 1];
// Write header
final int offset = hashAlgorithm.writeHeader(hash);
// Write digest
digest.doFinal(hash, offset);
// Write trailer
hashAlgorithm.writeTrailer(hash, offset + len);
return new ByteBufferDigestProviderImpl(hashAlgorithm, hash);
}
/**
* Compute the digest provider for the given digest buffer. Release the given buffer.
*
* @param hashAlgorithm
* @param digest
* Computed digest. The {@link ByteBuffer} is released.
* @return the digest provider
*/
static final ByteBufferDigestProvider getByteBufferDigestProvider(final HashAlgorithm hashAlgorithm,
final ByteBuffer digest) {
try {
// Allocate result: header <digest> trailer
final int len = digest.capacity();
final byte[] hash = new byte[1 + len + 1];
// Write header
final int offset = hashAlgorithm.writeHeader(hash);
// Write digest
ByteArrays.fillArray(digest, hash, offset);
// Write trailer
hashAlgorithm.writeTrailer(hash, offset + len);
return new ByteBufferDigestProviderImpl(hashAlgorithm, hash);
}
finally {
HashByteBufferCache.release(digest);
}
}
/**
* Write the header of a digest for this algorithm.
*
* @param hash
* resulting hash
* @return index to write the position of the digest in hash.
*/
final int writeHeader(final byte[] hash) {
hash[0] = (byte) (VERSION1 | index);
return 1;
}
/**
* Write the trailer in the digest.
*
* @param hash
* resulting hash
* @param offset
* position to write the trailer to
*/
final void writeTrailer(final byte[] hash, final int offset) {
byte trailer = hash[0];
for (int i = 1; i < offset; i++) {
trailer ^= hash[i];
}
hash[offset] = trailer;
}
/**
* Implementation of the {@link ByteBufferDigestProvider}. The hash
*
*
*/
static class ByteBufferDigestProviderImpl implements ByteBufferDigestProvider {
private final HashAlgorithm hashAlgorithm;
private final byte[] digest;
ByteBufferDigestProviderImpl(final HashAlgorithm hashAlgorithm, final byte[] digest) {
super();
this.hashAlgorithm = hashAlgorithm;
this.digest = digest;
}
@Override
public final HashAlgorithm getAlgorithm() {
return hashAlgorithm;
}
@Override
public final byte[] getDigest() {
return digest;
}
}
}