added initial DigestBuffer
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.inteligr8</groupId>
|
||||
<artifactId>nio-crypto</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
<name>Java NIO Crypto Adapter</name>
|
||||
<description>This project implements the javax.crypto API using java.nio instead of java.io.</description>
|
||||
|
||||
|
55
src/main/java/com/inteligr8/nio/AbstractBuffer.java
Executable file
55
src/main/java/com/inteligr8/nio/AbstractBuffer.java
Executable file
@@ -0,0 +1,55 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public abstract class AbstractBuffer {
|
||||
|
||||
protected long sourceBytesRead = 0L;
|
||||
protected long targetBytesWritten = 0L;
|
||||
protected boolean started = false;
|
||||
protected boolean finished = false;
|
||||
|
||||
public Status getStatus() {
|
||||
if (this.finished) return Status.Finished;
|
||||
else if (this.started) return Status.Started;
|
||||
else return Status.Initialized;
|
||||
}
|
||||
|
||||
public long getTotalBytesRead() {
|
||||
return this.sourceBytesRead;
|
||||
}
|
||||
|
||||
public long getTotalBytesWritten() {
|
||||
return this.targetBytesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method copies every byte possible from one buffer to another. It
|
||||
* stops copying gracefully when either the source buffer is empty or the
|
||||
* target buffer is full.
|
||||
*
|
||||
* @param sourceBuffer A NIO buffer ready for reading
|
||||
* @param targetBuffer A NIO buffer ready for writing
|
||||
* @return The number of bytes transferred
|
||||
*/
|
||||
protected int copyBuffer(ByteBuffer sourceBuffer, ByteBuffer targetBuffer) {
|
||||
int bytesToCopy = Math.min(sourceBuffer.remaining(), targetBuffer.remaining());
|
||||
int realLimit = sourceBuffer.limit();
|
||||
int tmpLimit = sourceBuffer.position() + bytesToCopy;
|
||||
|
||||
// read only the amount both buffers can handle
|
||||
sourceBuffer.limit(tmpLimit);
|
||||
|
||||
// do the actual transfer of bytes
|
||||
targetBuffer.put(sourceBuffer);
|
||||
|
||||
// reset the limit to its actual value (which could be the same)
|
||||
sourceBuffer.limit(realLimit);
|
||||
|
||||
return bytesToCopy;
|
||||
}
|
||||
|
||||
}
|
@@ -20,7 +20,15 @@ import javax.crypto.spec.IvParameterSpec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CipherBuffer {
|
||||
/**
|
||||
* This class attempts to implement the JCE Cipher using the Java NIO library
|
||||
* and concepts. The JCE Cipher accepts ByteBuffer, but it can be complicated
|
||||
* to use. This class makes encryption and decryption act more like typical
|
||||
* NIO buffer operations.
|
||||
*
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public class CipherBuffer extends AbstractBuffer {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CipherBuffer.class);
|
||||
private final String DEFAULT_CIPHER_MODE = "ECB";
|
||||
@@ -31,10 +39,6 @@ public class CipherBuffer {
|
||||
private final Key key;
|
||||
private final Cipher cipher;
|
||||
private final ByteBuffer leftoverBuffer;
|
||||
private long sourceBytesRead = 0L;
|
||||
private long targetBytesWritten = 0L;
|
||||
private boolean started = false;
|
||||
private boolean finished = false;
|
||||
|
||||
public CipherBuffer(CipherParameters cparams)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
@@ -102,20 +106,6 @@ public class CipherBuffer {
|
||||
return this.cipher;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
if (this.finished) return Status.Finished;
|
||||
else if (this.started) return Status.Started;
|
||||
else return Status.Initialized;
|
||||
}
|
||||
|
||||
public long getTotalBytesRead() {
|
||||
return this.sourceBytesRead;
|
||||
}
|
||||
|
||||
public long getTotalBytesWritten() {
|
||||
return this.targetBytesWritten;
|
||||
}
|
||||
|
||||
public byte[] getInitializationVector() {
|
||||
return this.cipher.getIV();
|
||||
}
|
||||
@@ -135,7 +125,7 @@ public class CipherBuffer {
|
||||
* specified buffer. Any subsequent call to `stream()` will fail.
|
||||
*
|
||||
* @param targetBuffer A NIO buffer ready for writing
|
||||
* @return true if more data may need flushed; false if subsequent calls will do nothing
|
||||
* @return true if more data may need flushed; false if subsequent calls are not expected
|
||||
* @throws BadPaddingException
|
||||
* @throws IllegalBlockSizeException
|
||||
*/
|
||||
@@ -283,30 +273,4 @@ public class CipherBuffer {
|
||||
return oldTargetBytesWritten < this.targetBytesWritten || sourceBuffer.hasRemaining();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method copies every byte possible from one buffer to another. It
|
||||
* stops copying gracefully when either the source buffer is empty or the
|
||||
* target buffer is full.
|
||||
*
|
||||
* @param sourceBuffer A NIO buffer ready for reading
|
||||
* @param targetBuffer A NIO buffer ready for writing
|
||||
* @return The number of bytes transferred
|
||||
*/
|
||||
private int copyBuffer(ByteBuffer sourceBuffer, ByteBuffer targetBuffer) {
|
||||
int bytesToCopy = Math.min(sourceBuffer.remaining(), targetBuffer.remaining());
|
||||
int realLimit = sourceBuffer.limit();
|
||||
int tmpLimit = sourceBuffer.position() + bytesToCopy;
|
||||
|
||||
// read only the amount both buffers can handle
|
||||
sourceBuffer.limit(tmpLimit);
|
||||
|
||||
// do the actual transfer of bytes
|
||||
targetBuffer.put(sourceBuffer);
|
||||
|
||||
// reset the limit to its actual value (which could be the same)
|
||||
sourceBuffer.limit(realLimit);
|
||||
|
||||
return bytesToCopy;
|
||||
}
|
||||
|
||||
}
|
||||
|
109
src/main/java/com/inteligr8/nio/DigestBuffer.java
Executable file
109
src/main/java/com/inteligr8/nio/DigestBuffer.java
Executable file
@@ -0,0 +1,109 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class attempts to implement the JCA MessageDigest using the Java NIO
|
||||
* library and concepts. It makes hashing act more like typical NIO buffer
|
||||
* operations.
|
||||
*
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public class DigestBuffer extends AbstractBuffer {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DigestBuffer.class);
|
||||
|
||||
private final MessageDigest digest;
|
||||
private final ByteBuffer leftoverBuffer;
|
||||
|
||||
public DigestBuffer(DigestParameters dparams) throws NoSuchAlgorithmException {
|
||||
this.digest = dparams.getProvider() == null ? MessageDigest.getInstance(dparams.getAlgorithm()) : MessageDigest.getInstance(dparams.getAlgorithm(), dparams.getProvider());
|
||||
if (this.logger.isDebugEnabled())
|
||||
this.logger.debug("digest algorithm: " + this.digest.getAlgorithm());
|
||||
|
||||
this.leftoverBuffer = ByteBuffer.allocate(this.digest.getDigestLength());
|
||||
this.leftoverBuffer.flip();
|
||||
}
|
||||
|
||||
public MessageDigest getMessageDigest() {
|
||||
return this.digest;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method flushes the digest cache and buffer into the specified
|
||||
* buffer. Any subsequent call to `stream()` will fail.
|
||||
*
|
||||
* @param targetBuffer A NIO buffer ready for writing
|
||||
* @return true if more data may need flushed; false if subsequent calls will do nothing
|
||||
*/
|
||||
public boolean flush(ByteBuffer targetBuffer) throws DigestException {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("flush(" + targetBuffer.remaining() + ")");
|
||||
|
||||
long oldTargetBytesWritten = this.targetBytesWritten;
|
||||
|
||||
// leftovers exist; must eat
|
||||
if (this.leftoverBuffer.remaining() > 0) {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("flush(" + targetBuffer.remaining() + "): draining leftovers");
|
||||
|
||||
this.targetBytesWritten += this.copyBuffer(this.leftoverBuffer, targetBuffer);
|
||||
|
||||
if (this.leftoverBuffer.remaining() > 0)
|
||||
return true;
|
||||
// otherwise no more data anywhere
|
||||
} else {
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("flush(" + targetBuffer.remaining() + "): nothing to flush");
|
||||
}
|
||||
|
||||
if (!this.finished) {
|
||||
// ready for writing
|
||||
this.leftoverBuffer.clear();
|
||||
|
||||
byte[] bytes = this.digest.digest();
|
||||
this.leftoverBuffer.put(bytes);
|
||||
this.finished = true;
|
||||
|
||||
// ready for reading
|
||||
this.leftoverBuffer.flip();
|
||||
|
||||
this.targetBytesWritten += this.copyBuffer(this.leftoverBuffer, targetBuffer);
|
||||
|
||||
if (this.leftoverBuffer.hasRemaining())
|
||||
return true;
|
||||
}
|
||||
|
||||
return oldTargetBytesWritten < this.targetBytesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method streams the digest from a source NIO buffer to a target NIO
|
||||
* buffer.
|
||||
*
|
||||
* @param sourceBuffer A NIO buffer ready for reading
|
||||
*/
|
||||
public void stream(ByteBuffer sourceBuffer) {
|
||||
if (sourceBuffer == null)
|
||||
throw new IllegalArgumentException();
|
||||
if (this.finished)
|
||||
throw new IllegalStateException("There should no longer be a source to this digest");
|
||||
|
||||
if (this.logger.isTraceEnabled())
|
||||
this.logger.trace("stream(" + sourceBuffer.remaining() + ")");
|
||||
|
||||
if (!this.started)
|
||||
this.started = true;
|
||||
|
||||
int bytesToRead = sourceBuffer.remaining();
|
||||
this.digest.update(sourceBuffer);
|
||||
this.sourceBytesRead += bytesToRead - sourceBuffer.remaining();
|
||||
}
|
||||
|
||||
}
|
36
src/main/java/com/inteligr8/nio/DigestParameters.java
Executable file
36
src/main/java/com/inteligr8/nio/DigestParameters.java
Executable file
@@ -0,0 +1,36 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.security.Provider;
|
||||
|
||||
public class DigestParameters {
|
||||
|
||||
private String algorithm;
|
||||
private Provider provider;
|
||||
|
||||
public DigestParameters(String algorithm) {
|
||||
this(algorithm, null);
|
||||
}
|
||||
|
||||
public DigestParameters(String algorithm, Provider provider) {
|
||||
this.algorithm = algorithm;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return this.algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public Provider getProvider() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
public DigestParameters setProvider(Provider provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
166
src/test/java/com/inteligr8/nio/AbstractDigestUnitTest.java
Executable file
166
src/test/java/com/inteligr8/nio/AbstractDigestUnitTest.java
Executable file
@@ -0,0 +1,166 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.DigestException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public abstract class AbstractDigestUnitTest {
|
||||
|
||||
public abstract String getDefaultAlgorithm();
|
||||
|
||||
protected DigestBuffer createDigest() throws NoSuchAlgorithmException {
|
||||
return new DigestBuffer(new DigestParameters(this.getDefaultAlgorithm()));
|
||||
}
|
||||
|
||||
public void validateText(String text, String hex) throws NoSuchAlgorithmException, DigestException {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(Charset.forName("utf-8")));
|
||||
|
||||
DigestBuffer digest = this.createDigest();
|
||||
digest.stream(buffer);
|
||||
Assert.assertFalse(buffer.hasRemaining());
|
||||
|
||||
ByteBuffer output = ByteBuffer.allocate(digest.getMessageDigest().getDigestLength());
|
||||
while (digest.flush(output))
|
||||
;
|
||||
|
||||
output.flip();
|
||||
Assert.assertEquals(hex, this.buffer2hex(output));
|
||||
}
|
||||
|
||||
public void validateFile(File file, String hex) throws NoSuchAlgorithmException, IOException, DigestException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
FileChannel fchannel = FileChannel.open(file.toPath());
|
||||
try {
|
||||
while (fchannel.read(buffer) >= 0) {
|
||||
buffer.flip();
|
||||
digest.stream(buffer);
|
||||
buffer.compact();
|
||||
}
|
||||
} finally {
|
||||
fchannel.close();
|
||||
}
|
||||
|
||||
ByteBuffer output = ByteBuffer.allocate(digest.getMessageDigest().getDigestLength());
|
||||
while (digest.flush(output))
|
||||
;
|
||||
|
||||
output.flip();
|
||||
Assert.assertEquals(hex, this.buffer2hex(output));
|
||||
}
|
||||
|
||||
private String buffer2hex(ByteBuffer buffer) {
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
return new String(Hex.encodeHex(bytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyFlushEmptyTargetHash() throws Exception {
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(0);
|
||||
Assert.assertTrue(digest.flush(buffer));
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(0L, digest.getTotalBytesWritten());
|
||||
|
||||
Assert.assertTrue(digest.flush(buffer));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyFlushHash() throws Exception {
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
Assert.assertTrue(digest.flush(buffer));
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(digest.getMessageDigest().getDigestLength(), digest.getTotalBytesWritten());
|
||||
|
||||
Assert.assertFalse(digest.flush(buffer));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyMultiFlushHash() throws Exception {
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
int chunkSize = digest.getMessageDigest().getDigestLength() / 2 - 1;
|
||||
ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
|
||||
Assert.assertTrue(digest.flush(buffer));
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(chunkSize, digest.getTotalBytesWritten());
|
||||
Assert.assertEquals(chunkSize, buffer.position());
|
||||
|
||||
buffer.clear();
|
||||
Assert.assertTrue(digest.flush(buffer));
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(chunkSize * 2L, digest.getTotalBytesWritten());
|
||||
Assert.assertEquals(chunkSize, buffer.position());
|
||||
|
||||
buffer.clear();
|
||||
Assert.assertTrue(digest.flush(buffer));
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(digest.getMessageDigest().getDigestLength(), digest.getTotalBytesWritten());
|
||||
Assert.assertEquals(2, buffer.position());
|
||||
|
||||
buffer.clear();
|
||||
Assert.assertFalse(digest.flush(buffer));
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(digest.getMessageDigest().getDigestLength(), digest.getTotalBytesWritten());
|
||||
Assert.assertEquals(0, buffer.position());
|
||||
|
||||
Assert.assertFalse(digest.flush(buffer));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyStreamEmptyTargetHash() throws Exception {
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
ByteBuffer sourceBuffer = ByteBuffer.allocate(0);
|
||||
digest.stream(sourceBuffer);
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(0L, digest.getTotalBytesWritten());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyStreamHash() throws Exception {
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
ByteBuffer sourceBuffer = ByteBuffer.allocate(0);
|
||||
digest.stream(sourceBuffer);
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(0L, digest.getTotalBytesWritten());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyMultiStreamHash() throws Exception {
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
ByteBuffer sourceBuffer = ByteBuffer.allocate(0);
|
||||
digest.stream(sourceBuffer);
|
||||
Assert.assertEquals(0L, digest.getTotalBytesRead());
|
||||
Assert.assertEquals(0L, digest.getTotalBytesWritten());
|
||||
|
||||
digest.stream(sourceBuffer);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testStreamAfterFlush() throws Exception {
|
||||
DigestBuffer digest = this.createDigest();
|
||||
|
||||
ByteBuffer sourceBuffer = ByteBuffer.allocate(0);
|
||||
ByteBuffer targetBuffer = ByteBuffer.allocate(0);
|
||||
Assert.assertTrue(digest.flush(targetBuffer));
|
||||
digest.stream(sourceBuffer);
|
||||
Assert.fail();
|
||||
}
|
||||
|
||||
}
|
29
src/test/java/com/inteligr8/nio/Md5DigestUnitTest.java
Executable file
29
src/test/java/com/inteligr8/nio/Md5DigestUnitTest.java
Executable file
@@ -0,0 +1,29 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class Md5DigestUnitTest extends AbstractDigestUnitTest {
|
||||
|
||||
@Override
|
||||
public String getDefaultAlgorithm() {
|
||||
return "MD5";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortText() throws Exception {
|
||||
this.validateText("Hello", "8b1a9953c4611296a827abf8c47804d7");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longText() throws Exception {
|
||||
this.validateText("Here is some text that is much longer than short. It is long. How about that?", "d9c042e20cc3215cda5f7f3648623abb");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileText() throws Exception {
|
||||
this.validateFile(new File("pom.xml"), "ba34d113dab4cc80d148ba6745852efd");
|
||||
}
|
||||
|
||||
}
|
29
src/test/java/com/inteligr8/nio/Sha256DigestUnitTest.java
Executable file
29
src/test/java/com/inteligr8/nio/Sha256DigestUnitTest.java
Executable file
@@ -0,0 +1,29 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class Sha256DigestUnitTest extends AbstractDigestUnitTest {
|
||||
|
||||
@Override
|
||||
public String getDefaultAlgorithm() {
|
||||
return "SHA-256";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortText() throws Exception {
|
||||
this.validateText("Hello", "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longText() throws Exception {
|
||||
this.validateText("Here is some text that is much longer than short. It is long. How about that?", "3acef9887592d6f6347bf750d0b63d1d652ec81d88fe0ff2f5f1d7aa6f21dc29");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileText() throws Exception {
|
||||
this.validateFile(new File("pom.xml"), "f9b266659cb1137cda357ad6bf31509f13defbe53c0e5a90069bc9eb95387800");
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user