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.hash.MD5Digest;
import java.nio.ByteBuffer;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit tests for the MD5 digest.
*
* @author oodrive
* @author llambert
*
*/
public class TestMD5Digest {
@Test
public void testMD54k() throws NoSuchAlgorithmException {
final byte[] src = new byte[4096];
new SecureRandom().nextBytes(src);
for (int i = 0; i <= 4096; i++) {
for (int j = 0; j < i; j += 30) {
final byte[] digBouncy = digestBouncy(src, j, i);
final byte[] digLocal = digestLocal(src, j, i);
final byte[] digJvm = digestJvm(src, j, i);
Assert.assertTrue(Arrays.equals(digBouncy, digLocal));
Assert.assertTrue(Arrays.equals(digJvm, digLocal));
}
}
}
private byte[] digestBouncy(final byte[] src, final int offsetStart, final int offsetEnd) {
final org.bouncycastle.crypto.digests.MD5Digest digest = new org.bouncycastle.crypto.digests.MD5Digest();
digest.update(src, offsetStart, offsetEnd - offsetStart);
final byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return result;
}
private byte[] digestLocal(final byte[] src, final int offsetStart, final int offsetEnd) {
// Do not call ByteBuffer.wrap(src, offset, len) to check that digest will start from position and not cross the
// limit
final ByteBuffer buffer = ByteBuffer.wrap(src);
buffer.position(offsetStart).limit(offsetEnd);
final MD5Digest digest = new MD5Digest(buffer);
final byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return result;
}
private byte[] digestJvm(final byte[] src, final int offsetStart, final int offsetEnd)
throws NoSuchAlgorithmException {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(src, offsetStart, offsetEnd - offsetStart);
return digest.digest();
}
@Test
public void testMD5Perf() throws Exception {
testMD5Perf(true);
}
/**
* Real world usage of local hash function. Base code for a performance comparison campaign.
*
* @param callAssert
* @throws DigestException
* @throws NoSuchAlgorithmException
*/
private void testMD5Perf(final boolean callAssert) throws DigestException, NoSuchAlgorithmException {
// Prepare buffer
final int len = 4096;
final int loopCount = 40960;
final byte[] src = new byte[len];
new SecureRandom().nextBytes(src);
final ByteBuffer bufJvm = ByteBuffer.allocateDirect(len);
bufJvm.put(src).rewind();
final ByteBuffer bufBouncy = ByteBuffer.allocateDirect(len);
bufBouncy.put(src).rewind();
final ByteBuffer bufLocal = ByteBuffer.allocateDirect(len);
bufLocal.put(src).rewind();
// Count JVM implementation time
final byte[] digJvm = new byte[16];
{
final long startJvm = System.nanoTime();
for (int i = 0; i < loopCount; i++) {
final MessageDigest digest = MessageDigest.getInstance("MD5");
// Get data from ByteBuffer
final byte[] blockCopy = new byte[len];
bufJvm.position(0);
bufJvm.limit(len);
bufJvm.get(blockCopy);
digest.update(blockCopy, 0, len);
digest.digest(digJvm, 0, digJvm.length);
// Avoid loop suppression: use value
bufJvm.rewind();
bufJvm.put(digJvm[12]);
// System.out.print(" " + digJvm[12]);
}
final long endJvm = System.nanoTime();
System.out.println("Jvm impl " + ((float) loopCount / ((float) (endJvm - startJvm)) * 1000000000)
+ " hash/seconds");
}
// Count bouncy castle implementation time
final byte[] digBouncy = new byte[16];
{
final long startBouncy = System.nanoTime();
for (int i = 0; i < loopCount; i++) {
final org.bouncycastle.crypto.digests.MD5Digest digest = new org.bouncycastle.crypto.digests.MD5Digest();
// Get data from ByteBuffer
final byte[] blockCopy = new byte[len];
bufBouncy.position(0);
bufBouncy.limit(len);
bufBouncy.get(blockCopy);
digest.update(blockCopy, 0, len);
digest.doFinal(digBouncy, 0);
// Avoid loop suppression: use value
bufBouncy.rewind();
bufBouncy.put(digBouncy[12]);
// System.out.print(" " + digBouncy[12]);
}
final long endBouncy = System.nanoTime();
System.out.println("Bouncy Castle impl "
+ ((float) loopCount / ((float) (endBouncy - startBouncy)) * 1000000000) + " hash/seconds");
}
// Count local implementation time
final byte[] digLocal = new byte[16];
final long startLocal = System.nanoTime();
for (int i = 0; i < loopCount; i++) {
final MD5Digest digest = new MD5Digest(bufLocal);
digest.doFinal(digLocal, 0);
// Avoid loop suppression: use value
bufLocal.rewind();
bufLocal.put(digLocal[12]).rewind();
// System.out.print(" " + digLocal[12]);
}
final long endLocal = System.nanoTime();
System.out.println("Local impl " + ((float) loopCount / ((float) (endLocal - startLocal)) * 1000000000)
+ " hash/seconds");
if (callAssert) {
Assert.assertTrue(Arrays.equals(digBouncy, digLocal));
Assert.assertTrue(Arrays.equals(digJvm, digLocal));
}
}
/**
* Call perf test.
*
* @param ignored
*/
public static void main(final String[] ignored) {
try {
new TestMD5Digest().testMD5Perf(false);
}
catch (DigestException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}