initial checkin

This commit is contained in:
2021-01-05 16:04:30 -05:00
commit 0e1b1068b1
14 changed files with 1418 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# Maven
.project
.classpath
.settings
target/

50
pom.xml Normal file
View File

@@ -0,0 +1,50 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8</groupId>
<artifactId>nio-crypto</artifactId>
<version>1.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>
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
<scope>test</scope>
</dependency>
</dependencies>
<distributionManagement>
<repository>
<id>inteligr8-public</id>
<name>Inteligr8 Releases</name>
<url>http://repos.yateslong.us/nexus/repository/inteligr8-public</url>
<layout>default</layout>
</repository>
</distributionManagement>
</project>

View File

@@ -0,0 +1,96 @@
package com.inteligr8.nio;
import java.nio.channels.Channel;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
public class AbstractCryptoByteChannel {
private static final String DEFAULT_CIPHER_MODE = "CBC";
private static final String DEFAULT_CIPHER_PADDING = "PKCS5Padding";
private static final Pattern ALGORITHM_PATTERN = Pattern.compile("(.+)/(.+)/(.+)");
private final int cipherMode;
private final Key key;
private final Cipher cipher;
public AbstractCryptoByteChannel(Channel channel, int cipherMode, Key key)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this(channel, cipherMode, key, null, null);
}
public AbstractCryptoByteChannel(Channel channel, int cipherMode, Key key, byte[] initializationVector)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this(channel, cipherMode, key, initializationVector, null);
}
public AbstractCryptoByteChannel(Channel channel, int cipherMode, Key key, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this(channel, cipherMode, key, null, provider);
}
public AbstractCryptoByteChannel(Channel channel, int cipherMode, Key key, byte[] initializationVector, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this.cipherMode = cipherMode;
this.key = key;
Cipher cipher = provider == null ? Cipher.getInstance(key.getAlgorithm()) : Cipher.getInstance(key.getAlgorithm(), provider);
// if the algorithm requires a stream cipher
if (cipher.getBlockSize() == 0) {
cipher.init(cipherMode, key);
// otherwise algorithm requires a block cipher
} else {
// if a mode not specified, force a mode
Matcher matcher = ALGORITHM_PATTERN.matcher(cipher.getAlgorithm());
if (!matcher.matches()) {
if (cipher.getAlgorithm().contains("/"))
throw new IllegalArgumentException("The algorithm contains a slash, but should contain 2 slashes, separating the algorithm, mode, and padding: " + cipher.getAlgorithm());
String algorithm = key.getAlgorithm() + "/" + DEFAULT_CIPHER_MODE + "/" + DEFAULT_CIPHER_PADDING;
matcher = ALGORITHM_PATTERN.matcher(algorithm);
if (!matcher.matches())
throw new IllegalArgumentException("The algorithm is not formatted properly: " + algorithm);
cipher = provider == null ? Cipher.getInstance(algorithm) : Cipher.getInstance(algorithm, provider);
}
if (initializationVector != null) {
cipher.init(cipherMode, key, new IvParameterSpec(initializationVector));
} else if (!matcher.group(2).equals("ECB")) {
byte[] iv = new byte[cipher.getBlockSize()];
new SecureRandom().nextBytes(iv);
cipher.init(cipherMode, key, new IvParameterSpec(iv));
} else {
cipher.init(cipherMode, key);
}
}
this.cipher = cipher;
}
protected Cipher getCipher() {
return this.cipher;
}
public byte[] getInitializationVector() {
return this.cipher.getIV();
}
public void setInitializationVector(byte[] initializationVector) throws InvalidAlgorithmParameterException {
try {
this.cipher.init(this.cipherMode, this.key, new IvParameterSpec(initializationVector));
} catch (InvalidKeyException ike) {
throw new RuntimeException("This will never happen, as the key was already declared valid by previous init()");
}
}
}

View File

@@ -0,0 +1,286 @@
package com.inteligr8.nio;
import java.io.Flushable;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DecryptingByteChannel extends AbstractCryptoByteChannel implements ReadableByteChannel, Flushable {
private final Logger logger = LoggerFactory.getLogger(DecryptingByteChannel.class);
private final ReadableByteChannel rbchannel;
private boolean finished;
private boolean started;
private boolean closed;
private ByteBuffer blockBuffer;
private long encryptedBytesRead = 0L;
private long decryptedBytesWritten = 0L;
public DecryptingByteChannel(ReadableByteChannel rbchannel, Key key)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(rbchannel, Cipher.DECRYPT_MODE, key);
this.rbchannel = rbchannel;
this.init();
}
public DecryptingByteChannel(ReadableByteChannel rbchannel, Key key, byte[] initializationVector)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(rbchannel, Cipher.DECRYPT_MODE, key, initializationVector);
this.rbchannel = rbchannel;
this.init();
}
public DecryptingByteChannel(ReadableByteChannel rbchannel, Key key, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(rbchannel, Cipher.DECRYPT_MODE, key, provider);
this.rbchannel = rbchannel;
this.init();
}
public DecryptingByteChannel(ReadableByteChannel rbchannel, Key key, byte[] initializationVector, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(rbchannel, Cipher.DECRYPT_MODE, key, initializationVector, provider);
this.rbchannel = rbchannel;
this.init();
}
private void init() {
this.closed = !this.rbchannel.isOpen();
this.finished = false;
this.started = false;
if (this.getCipher().getBlockSize() > 0)
this.blockBuffer = ByteBuffer.allocate(this.getCipher().getBlockSize() * 3);
}
public long getEncryptedBytesRead() {
return this.encryptedBytesRead;
}
public long getDecryptedBytesWritten() {
return this.decryptedBytesWritten;
}
public void close() throws IOException {
// do not close wbchannel; we may be only encrypting some of the channel
if (!this.closed && this.started && !this.finished)
this.logger.warn("A decryption channel was closed before it was properly finished");
this.closed = true;
}
public boolean isOpen() {
return !this.closed && this.rbchannel.isOpen();
}
@Override
public void flush() throws IOException {
if (!this.closed && this.started && !this.finished && this.logger.isInfoEnabled())
this.logger.info("A decryption channel was flushed before it was properly finished");
}
public int read(ByteBuffer decryptedBuffer) throws IOException {
if (this.closed)
throw new ClosedChannelException();
if (this.logger.isTraceEnabled())
this.logger.trace("write(" + decryptedBuffer.remaining() + "): ");
int totalBytesRead = 0;
// if the channel is empty and cipher finalized
if (this.finished) {
// and block buffer has some data still
if (this.blockBuffer != null && this.blockBuffer.position() > 0) {
int bytesWritten = this.drainBlockBuffer(decryptedBuffer);
this.decryptedBytesWritten += bytesWritten;
return 0;
// otherwise no more data anywhere
} else {
return -1;
}
}
// if block buffer has some data
if (this.blockBuffer != null && this.blockBuffer.position() > 0) {
int bytesWritten = this.drainBlockBuffer(decryptedBuffer);
this.decryptedBytesWritten += bytesWritten;
// if we filled the buffer, then we are done for now
if (decryptedBuffer.remaining() == 0)
return 0;
// assert: at this point, blockBuffer should be in clear position
if (this.blockBuffer.position() > 0 || this.blockBuffer.limit() < this.blockBuffer.capacity())
throw new AssertionError("This should never happen");
}
if (!this.started)
this.started = true;
try {
int bytesRead = this.decryptToBuffer(decryptedBuffer);
while (bytesRead >= 0 && decryptedBuffer.remaining() > 0) {
totalBytesRead += bytesRead;
bytesRead = this.decryptToBuffer(decryptedBuffer);
}
totalBytesRead += Math.max(0, bytesRead);
} catch (BadPaddingException bpe) {
throw new IOException("A padding issue occurred during decryption: " + bpe.getMessage(), bpe);
} catch (IllegalBlockSizeException ibse) {
throw new IOException("A padding issue occurred during decryption: " + ibse.getMessage(), ibse);
}
return totalBytesRead;
}
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;
}
private int drainBlockBuffer(ByteBuffer decryptedBuffer) {
// switch to read mode
this.blockBuffer.flip();
// drain the buffer (if possible)
int bytesCopied = this.copyBuffer(this.blockBuffer, decryptedBuffer);
// switch back to write mode
this.blockBuffer.compact();
return bytesCopied;
}
private int readBuffer(ByteBuffer buffer) throws IOException {
int totalBytesRead = 0;
int bytesRead = this.rbchannel.read(buffer);
if (bytesRead < 0)
return bytesRead;
while (buffer.remaining() > 0 && bytesRead >= 0) {
totalBytesRead += bytesRead;
bytesRead = this.rbchannel.read(buffer);
}
totalBytesRead += Math.max(bytesRead, 0);
this.encryptedBytesRead += totalBytesRead;
return totalBytesRead;
}
private int decryptToBuffer(ByteBuffer decryptedBuffer) throws BadPaddingException, IllegalBlockSizeException, IOException {
int blockSize = this.getCipher().getBlockSize();
ByteBuffer encryptedBuffer = null;
if (this.blockBuffer == null) {
encryptedBuffer = ByteBuffer.allocate(decryptedBuffer.remaining());
} else {
int encryptedBufferSize = decryptedBuffer.remaining() / blockSize * blockSize + blockSize;
encryptedBuffer = ByteBuffer.allocate(encryptedBufferSize);
}
int maxBytesToRead = encryptedBuffer.remaining();
if (this.logger.isTraceEnabled())
this.logger.trace("decryptToBuffer(): max bytes to read: " + maxBytesToRead);
// read encrypted bytes from channel
int bytesRead = this.readBuffer(encryptedBuffer);
if (this.logger.isTraceEnabled())
this.logger.trace("decryptToBuffer(): bytes actually read: " + bytesRead);
// if nothing more to read...ever
if (bytesRead < 0) {
try {
// try and finish the decryption
// since the buffer may not be big enough, use the block buffer
int bytesWritten = this.getCipher().doFinal(ByteBuffer.allocate(0), this.blockBuffer);
if (this.logger.isTraceEnabled()) {
this.logger.trace("decryptToBuffer(): bytes actually decrypted: 0");
this.logger.trace("decryptToBuffer(): bytes written: " + bytesWritten);
}
// copy as much as decryptedBuffer can take
bytesWritten = this.drainBlockBuffer(decryptedBuffer);
this.decryptedBytesWritten += bytesWritten;
this.finished = true;
return bytesWritten > 0 ? 0 : -1;
} catch (ShortBufferException sbe) {
throw new BufferOverflowException();
}
} else {
// switch to read mode
encryptedBuffer.flip();
try {
int bytesWritten = 0;
int origlimit = encryptedBuffer.limit();
if (this.blockBuffer != null) {
// only decrypt up to the last 2 blocks, as it might be a partial block
// and i don't want to create a whole new buffer, which takes memory
encryptedBuffer.limit(Math.max(0, encryptedBuffer.limit() - 2 * blockSize));
}
// if we aren't dealing with a decrypted buffer smaller than the block size
if (encryptedBuffer.remaining() > 0) {
// do the bulk of the decryption
bytesWritten += this.getCipher().update(encryptedBuffer, decryptedBuffer);
}
if (this.blockBuffer != null) {
// return the limit to where it should be
encryptedBuffer.limit(origlimit);
}
// if we aren't dealing with a decryptedBuffer with the perfect size
// this should also only happen when dealing with block buffers
if (encryptedBuffer.remaining() > 0) {
this.getCipher().update(encryptedBuffer, this.blockBuffer);
// copy as much as decryptedBuffer can take
bytesWritten += this.drainBlockBuffer(decryptedBuffer);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("decryptToBuffer(): bytes actually decrypted: " + (maxBytesToRead - encryptedBuffer.remaining()));
this.logger.trace("decryptToBuffer(): bytes written: " + bytesWritten);
}
this.decryptedBytesWritten += bytesWritten;
return bytesRead;
} catch (ShortBufferException sbe) {
throw new BufferOverflowException();
}
}
}
}

View File

@@ -0,0 +1,215 @@
package com.inteligr8.nio;
import java.io.Flushable;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* No need for block buffer?
*
* @author brian.long
*/
public class EncryptingByteChannel extends AbstractCryptoByteChannel implements WritableByteChannel, Flushable {
private final Logger logger = LoggerFactory.getLogger(EncryptingByteChannel.class);
private final WritableByteChannel wbchannel;
private boolean closed;
private boolean finished = false;
private int extraBytesReported = 0;
private long decryptedBytesRead = 0L;
private long encryptedBytesWritten = 0L;
public EncryptingByteChannel(WritableByteChannel wbchannel, Key key)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(wbchannel, Cipher.ENCRYPT_MODE, key);
this.wbchannel = wbchannel;
this.closed = !wbchannel.isOpen();
}
public EncryptingByteChannel(WritableByteChannel wbchannel, Key key, byte[] initializationVector)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(wbchannel, Cipher.ENCRYPT_MODE, key, initializationVector);
this.wbchannel = wbchannel;
this.closed = !wbchannel.isOpen();
}
public EncryptingByteChannel(WritableByteChannel wbchannel, Key key, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(wbchannel, Cipher.ENCRYPT_MODE, key, provider);
this.wbchannel = wbchannel;
this.closed = !wbchannel.isOpen();
}
public EncryptingByteChannel(WritableByteChannel wbchannel, Key key, byte[] initializationVector, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
super(wbchannel, Cipher.ENCRYPT_MODE, key, initializationVector, provider);
this.wbchannel = wbchannel;
this.closed = !wbchannel.isOpen();
}
public long getDecryptedBytesRead() {
return this.decryptedBytesRead;
}
public long getEncryptedBytesWritten() {
return this.encryptedBytesWritten;
}
public void close() throws IOException {
// do not close wbchannel; we may be only encrypting some of the channel
if (!this.closed)
this.flush();
this.closed = true;
}
public boolean isOpen() {
return !this.closed && this.wbchannel.isOpen();
}
@Override
public void flush() throws IOException {
if (!this.finished) {
try {
this.encryptFinish();
} catch (IllegalBlockSizeException ibse) {
// output error, because a lot of people like to supress IOException on close
String message = "The encryption algorithm is a block algorithm which requires padding: " + ibse.getMessage();
this.logger.error(message);
throw new IOException(message, ibse);
}
}
}
public int write(ByteBuffer decryptedBuffer) throws IOException {
if (this.logger.isTraceEnabled())
this.logger.trace("write(" + decryptedBuffer.remaining() + "): ");
int totalBytesWritten = 0;
if (this.finished)
return 0;
int bytesToRead = decryptedBuffer.remaining();
if (bytesToRead > 0) {
// encrypt the decryptedBuffer into this channel
int bytesWritten = this.encryptBuffer(decryptedBuffer);
totalBytesWritten += bytesWritten;
}
int bytesRead = bytesToRead - decryptedBuffer.remaining();
this.decryptedBytesRead += bytesRead;
if (totalBytesWritten > 0) {
try {
return totalBytesWritten - this.extraBytesReported;
} finally {
this.extraBytesReported = 0;
}
} else if (bytesToRead > 0) {
// since the cipher could do nothing at times, at least report 1 byte written
// even if no bytes were read
this.extraBytesReported++;
return 1;
} else {
return 0;
}
}
private int writeBuffer(ByteBuffer buffer) throws IOException {
int totalBytesWritten = 0;
int bytesWritten = this.wbchannel.write(buffer);
while (buffer.remaining() > 0 && bytesWritten > 0) {
totalBytesWritten += bytesWritten;
bytesWritten = this.wbchannel.write(buffer);
}
totalBytesWritten += bytesWritten;
this.encryptedBytesWritten += totalBytesWritten;
return totalBytesWritten;
}
private int encryptBuffer(ByteBuffer decryptedBuffer) throws IOException {
int bytesToRead = decryptedBuffer.remaining();
int bytesToWrite = this.getCipher().getOutputSize(bytesToRead);
if (this.logger.isTraceEnabled()) {
this.logger.trace("encryptBuffer(): bytes to read: " + bytesToRead);
this.logger.trace("encryptBuffer(): bytes to possibly write: " + bytesToWrite);
}
// always created in write mode
ByteBuffer encryptedBuffer = ByteBuffer.allocate(bytesToWrite);
try {
// do the encryption
int bytesEncrypted = this.getCipher().update(decryptedBuffer, encryptedBuffer);
int bytesRead = bytesToRead - decryptedBuffer.remaining();
if (this.logger.isTraceEnabled()) {
this.logger.trace("encryptBuffer(): bytes read: " + bytesRead);
this.logger.trace("encryptBuffer(): bytes actually encrypted: " + bytesEncrypted);
}
// switch to read mode
encryptedBuffer.flip();
// write encrypted bytes to channel
int bytesWritten = this.writeBuffer(encryptedBuffer);
if (this.logger.isTraceEnabled())
this.logger.trace("encryptBuffer(): encrypted bytes written to channel: " + bytesWritten);
if (bytesWritten != bytesEncrypted)
this.logger.error((bytesEncrypted - bytesWritten) + " encrypted bytes were not written to the channel due to the channel being full");
return bytesWritten;
} catch (ShortBufferException sbe) {
throw new BufferOverflowException();
}
}
private int encryptFinish() throws IllegalBlockSizeException, IOException {
int bytesToWrite = this.getCipher().getOutputSize(0);
if (this.logger.isTraceEnabled())
this.logger.trace("encryptFinish(): bytes to possibly write: " + bytesToWrite);
// always created in write mode
ByteBuffer encryptedBuffer = ByteBuffer.allocate(bytesToWrite);
try {
// do the encryption
int bytesEncrypted = this.getCipher().doFinal(ByteBuffer.allocate(0), encryptedBuffer);
if (this.logger.isTraceEnabled())
this.logger.trace("encryptFinish(): bytes actually encrypted: " + bytesEncrypted);
// switch to read mode
encryptedBuffer.flip();
// write encrypted bytes to channel
int bytesWritten = this.writeBuffer(encryptedBuffer);
if (this.logger.isTraceEnabled())
this.logger.trace("encryptFinish(): encrypted bytes written to channel: " + bytesWritten);
if (bytesWritten != bytesEncrypted)
this.logger.error((bytesEncrypted - bytesWritten) + " encrypted bytes were not written to the channel due to the channel being full");
this.finished = true;
return bytesWritten;
} catch (ShortBufferException sbe) {
throw new BufferOverflowException();
} catch (BadPaddingException bpe) {
throw new RuntimeException("This should never happen: only possible during decryption");
}
}
}

View File

@@ -0,0 +1,79 @@
package com.inteligr8.nio;
import java.io.Flushable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import javax.crypto.NoSuchPaddingException;
public class IVDecryptingByteChannel implements ReadableByteChannel, Flushable {
private final ReadableByteChannel rbchannel;
private final DecryptingByteChannel dbchannel;
private boolean readIV = false;
public IVDecryptingByteChannel(ReadableByteChannel rbchannel, Key key)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this.rbchannel = rbchannel;
this.dbchannel = new DecryptingByteChannel(rbchannel, key);
}
public IVDecryptingByteChannel(ReadableByteChannel rbchannel, Key key, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this.rbchannel = rbchannel;
this.dbchannel = new DecryptingByteChannel(rbchannel, key, provider);
}
@Override
public void close() throws IOException {
this.dbchannel.close();
}
@Override
public boolean isOpen() {
return this.dbchannel.isOpen();
}
@Override
public void flush() throws IOException {
this.dbchannel.flush();
}
@Override
public int read(ByteBuffer dst) throws IOException {
int bytes = 0;
if (!this.readIV) {
ByteBuffer ivbuffer = ByteBuffer.allocate(this.dbchannel.getCipher().getBlockSize());
bytes += this.rbchannel.read(ivbuffer);
ivbuffer.flip();
byte[] iv = new byte[ivbuffer.remaining()];
ivbuffer.get(iv);
try {
this.dbchannel.setInitializationVector(iv);
} catch (InvalidAlgorithmParameterException iape) {
throw new IOException(iape.getMessage(), iape);
}
this.readIV = true;
}
bytes += this.dbchannel.read(dst);
return bytes;
}
public long getDecryptedBytesWritten() {
return this.dbchannel.getDecryptedBytesWritten();
}
public long getEncryptedBytesRead() {
return this.dbchannel.getCipher().getBlockSize() + this.dbchannel.getEncryptedBytesRead();
}
}

View File

@@ -0,0 +1,66 @@
package com.inteligr8.nio;
import java.io.Flushable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import javax.crypto.NoSuchPaddingException;
public class IVEncryptingByteChannel implements WritableByteChannel, Flushable {
private final EncryptingByteChannel ebchannel;
private boolean wroteIV = false;
public IVEncryptingByteChannel(WritableByteChannel wbchannel, Key key)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this.ebchannel = new EncryptingByteChannel(wbchannel, key);
}
public IVEncryptingByteChannel(WritableByteChannel wbchannel, Key key, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
this.ebchannel = new EncryptingByteChannel(wbchannel, key, provider);
}
@Override
public void close() throws IOException {
this.ebchannel.close();
}
@Override
public boolean isOpen() {
return this.ebchannel.isOpen();
}
@Override
public void flush() throws IOException {
this.ebchannel.flush();
}
@Override
public int write(ByteBuffer src) throws IOException {
int bytes = 0;
if (!this.wroteIV) {
ByteBuffer iv = ByteBuffer.wrap(this.ebchannel.getInitializationVector());
bytes += this.ebchannel.write(iv);
this.wroteIV = true;
}
bytes += this.ebchannel.write(src);
return bytes;
}
public long getDecryptedBytesRead() {
return this.ebchannel.getDecryptedBytesRead();
}
public long getEncryptedBytesWritten() {
return this.ebchannel.getEncryptedBytesWritten();
}
}

View File

@@ -0,0 +1,76 @@
package com.inteligr8.nio;
import java.nio.charset.Charset;
import java.security.Key;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.junit.BeforeClass;
import org.junit.Test;
public abstract class AbstractCryptoByteChannelUnitTest {
private final Charset charset = Charset.forName("utf-8");
private static Key key;
private static int keySizeInBytes;
@BeforeClass
public static void createKey() throws DecoderException {
byte[] keyInBytes = Hex.decodeHex("0123456789abcdef0123456789abcdef".toCharArray());
key = new SecretKeySpec(keyInBytes, "AES");
keySizeInBytes = keyInBytes.length;
}
public Charset getCharset() {
return this.charset;
}
protected Key getKey() {
return key;
}
protected int getKeySizeInBytes() {
return keySizeInBytes;
}
@Test
public void empty() throws Exception {
this.test("", "0efb6bfed93b4d1ea2123ba4db075ff6");
}
@Test
public void lessThan8b() throws Exception {
this.test("Hello", "6aa9f9479de4e65399463357ada98066");
}
@Test
public void exactly8b() throws Exception {
this.test("Hi there", "be52f747f1a8608a35240f847a2b36ae");
}
@Test
public void between8b16b() throws Exception {
this.test("Hello World!", "9747ea180837d5caccc7a7ad94ab9f32");
}
@Test
public void exactly16b() throws Exception {
this.test("Hello over there", "1190f9879f9d173b3544d18db65d1e8270ea9a2d6f918a1a808849277d612e79");
}
@Test
public void between16b32b() throws Exception {
this.test("Howdy y'all compadres!", "5fd28457d8c440fe350c1f1ad5038522a426ec00eb40246a670367a5c966d570");
}
@Test
public void moreThan32b() throws Exception {
this.test("Hello to all and to all a good night", "3383ee4d7b65c32124b992da2e888dec1635ad33a399336d7763f040bad669036baf3253c9859029d13c91c3e9d8a463");
}
public abstract void test(String text, String hex) throws Exception;
}

View File

@@ -0,0 +1,84 @@
package com.inteligr8.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
public class ByteBufferChannel implements ByteChannel {
private final ByteBuffer bb;
private final ByteBuffer robb;
private final Object limitLock = new Object();
public ByteBufferChannel(int capacity) {
this.bb = ByteBuffer.allocate(capacity);
this.robb = this.bb.asReadOnlyBuffer();
this.robb.limit(0);
}
public ByteBufferChannel(ByteBuffer bb) {
this.bb = bb;
this.robb = this.bb.asReadOnlyBuffer();
this.bb.position(this.bb.limit());
this.bb.limit(this.bb.capacity());
}
public ByteBuffer getByteBuffer() {
return this.robb.asReadOnlyBuffer();
}
public boolean isOpen() {
return true;
}
public void close() {
}
public long size() {
return this.bb.position();
}
public void compact() {
synchronized (this.limitLock) {
this.robb.compact();
this.bb.position(this.robb.limit());
this.bb.limit(this.bb.capacity());
}
}
public int read(ByteBuffer bb) throws IOException {
if (this.robb.remaining() <= 0)
return -1; // End-of-stream
int oldLimit = this.robb.limit();
if (this.robb.remaining() > bb.remaining())
this.robb.limit(this.robb.position() + bb.remaining());
int bytes = this.robb.remaining();
bb.put(this.robb);
this.robb.limit(oldLimit);
return bytes;
}
public int write(ByteBuffer bb) throws IOException {
int oldLimit = -1;
if (bb.remaining() > this.bb.remaining()) {
oldLimit = bb.limit();
bb.limit(bb.position() + this.bb.remaining());
}
int bytes = bb.remaining();
synchronized (this.limitLock) {
this.bb.put(bb);
this.robb.limit(this.bb.position());
}
if (oldLimit > -1)
bb.limit(oldLimit);
return bytes;
}
}

View File

@@ -0,0 +1,127 @@
package com.inteligr8.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.NoSuchPaddingException;
import org.junit.Assert;
import org.junit.Test;
import com.inteligr8.nio.DecryptingByteChannel;
import com.inteligr8.nio.EncryptingByteChannel;
public class CryptoByteChannelUnitTest extends AbstractCryptoByteChannelUnitTest {
public void test(String text, String hex) throws Exception {
this.text(text);
this.chunk(text, 512);
this.chunk(text, 5);
}
@Test
public void partialLessThan8b() throws Exception {
this.partialText("5Hello", "5");
}
@Test
public void partialMoreThan8b() throws Exception {
this.partialText("12Hello World!", "12");
}
private void text(String text) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
int keySizeInBytes = this.getKeySizeInBytes();
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
EncryptingByteChannel ebchannel = new EncryptingByteChannel(bbchannel, this.getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesWritten = ebchannel.write(this.getCharset().encode(text));
if (text.length() == 0) Assert.assertEquals(0, bytesWritten);
else Assert.assertEquals(Math.max(1, text.length() / keySizeInBytes * keySizeInBytes), bytesWritten);
} finally {
ebchannel.close();
}
Assert.assertTrue(bbchannel.size() >= text.length());
ByteBuffer bbuffer = ByteBuffer.allocate(1024);
DecryptingByteChannel dbchannel = new DecryptingByteChannel(bbchannel, this.getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesRead = dbchannel.read(bbuffer);
Assert.assertEquals(text.length() / keySizeInBytes * keySizeInBytes + keySizeInBytes, bytesRead);
} finally {
dbchannel.close();
}
bbuffer.flip();
Assert.assertEquals(text, this.getCharset().decode(bbuffer).toString());
}
private void chunk(String text, int chunkSize) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
ByteBuffer bbuffer = this.getCharset().encode(text);
int realLimit = bbuffer.limit();
bbuffer.limit(Math.min(chunkSize, realLimit));
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
EncryptingByteChannel ebchannel = new EncryptingByteChannel(bbchannel, getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesWritten = 1;
while (bytesWritten > 0) {
bytesWritten = ebchannel.write(bbuffer);
bbuffer.limit(Math.min(bbuffer.limit() + chunkSize, realLimit));
}
} finally {
ebchannel.close();
}
Assert.assertTrue(bbchannel.size() >= text.length());
bbuffer = ByteBuffer.allocate(1024);
realLimit = bbuffer.limit();
bbuffer.limit(Math.min(chunkSize, realLimit));
DecryptingByteChannel dbchannel = new DecryptingByteChannel(bbchannel, getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesRead = 1;
while (bytesRead >= 0) {
bytesRead = dbchannel.read(bbuffer);
bbuffer.limit(Math.min(bbuffer.limit() + chunkSize, realLimit));
}
} finally {
dbchannel.close();
}
bbuffer.flip();
Assert.assertEquals(text, this.getCharset().decode(bbuffer).toString());
}
private void partialText(String text, String startsWith) throws Exception {
int keySizeInBytes = this.getKeySizeInBytes();
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
EncryptingByteChannel ebchannel = new EncryptingByteChannel(bbchannel, this.getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesWritten = ebchannel.write(this.getCharset().encode(text));
Assert.assertEquals(Math.max(1, text.length() / keySizeInBytes * keySizeInBytes), bytesWritten);
} finally {
ebchannel.close();
}
Assert.assertTrue(bbchannel.size() >= text.length());
ByteBuffer bbuffer = ByteBuffer.allocate(startsWith.length());
DecryptingByteChannel dbchannel = new DecryptingByteChannel(bbchannel, this.getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesRead = dbchannel.read(bbuffer);
Assert.assertEquals(startsWith.length() / keySizeInBytes * keySizeInBytes + keySizeInBytes, bytesRead);
} finally {
dbchannel.close();
}
bbuffer.flip();
Assert.assertEquals(startsWith, this.getCharset().decode(bbuffer).toString());
}
}

View File

@@ -0,0 +1,36 @@
package com.inteligr8.nio;
import java.nio.ByteBuffer;
import org.apache.commons.codec.binary.Hex;
import org.junit.Assert;
import com.inteligr8.nio.DecryptingByteChannel;
public class DecryptingByteChannelUnitTest extends AbstractCryptoByteChannelUnitTest {
public void test(String text, String hex) throws Exception {
int keySizeInBytes = this.getKeySizeInBytes();
Assert.assertEquals(2 * (text.length() / keySizeInBytes * keySizeInBytes + keySizeInBytes), hex.length());
ByteBuffer bbuffer = ByteBuffer.allocate(1024);
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
bbchannel.write(ByteBuffer.wrap(Hex.decodeHex(hex.toCharArray())));
DecryptingByteChannel dbchannel = new DecryptingByteChannel(bbchannel, this.getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesRead = dbchannel.read(bbuffer);
Assert.assertEquals(hex.length() / 2, bytesRead);
} finally {
dbchannel.close();
}
Assert.assertEquals(hex.length() / 2, dbchannel.getEncryptedBytesRead());
Assert.assertEquals(text.length(), dbchannel.getDecryptedBytesWritten());
bbuffer.flip();
Assert.assertEquals(text.length(), bbuffer.remaining());
Assert.assertEquals(text, this.getCharset().decode(bbuffer).toString());
}
}

View File

@@ -0,0 +1,39 @@
package com.inteligr8.nio;
import java.nio.ByteBuffer;
import org.apache.commons.codec.binary.Hex;
import org.junit.Assert;
import com.inteligr8.nio.EncryptingByteChannel;
public class EncryptingByteChannelUnitTest extends AbstractCryptoByteChannelUnitTest {
public void test(String text, String hex) throws Exception {
int keySizeInBytes = this.getKeySizeInBytes();
Assert.assertEquals(2 * (text.length() / keySizeInBytes * keySizeInBytes + keySizeInBytes), hex.length());
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
EncryptingByteChannel ebchannel = new EncryptingByteChannel(bbchannel, this.getKey(), new byte[this.getKeySizeInBytes()]);
try {
int bytesWritten = ebchannel.write(this.getCharset().encode(text));
if (text.length() == 0) Assert.assertEquals(0, bytesWritten);
else Assert.assertEquals(Math.max(1, text.length() / keySizeInBytes * keySizeInBytes), bytesWritten);
} finally {
ebchannel.close();
}
Assert.assertEquals(text.length(), ebchannel.getDecryptedBytesRead());
Assert.assertEquals(hex.length() / 2, ebchannel.getEncryptedBytesWritten());
ByteBuffer bbuffer = ByteBuffer.allocate(1024);
bbchannel.read(bbuffer);
bbuffer.flip();
byte[] bytes = new byte[bbuffer.remaining()];
bbuffer.get(bytes);
Assert.assertEquals(hex, new String(Hex.encodeHex(bytes)));
Assert.assertEquals(hex.length() / 2, bytes.length);
}
}

View File

@@ -0,0 +1,127 @@
package com.inteligr8.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.NoSuchPaddingException;
import org.junit.Assert;
import org.junit.Test;
import com.inteligr8.nio.IVDecryptingByteChannel;
import com.inteligr8.nio.IVEncryptingByteChannel;
public class IVCryptoByteChannelUnitTest extends AbstractCryptoByteChannelUnitTest {
public void test(String text, String hex) throws Exception {
this.text(text);
this.chunk(text, 512);
this.chunk(text, 5);
}
@Test
public void partialLessThan8b() throws Exception {
this.partialText("5Hello", "5");
}
@Test
public void partialMoreThan8b() throws Exception {
this.partialText("12Hello World!", "12");
}
private void text(String text) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
int keySizeInBytes = this.getKeySizeInBytes();
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
IVEncryptingByteChannel ebchannel = new IVEncryptingByteChannel(bbchannel, this.getKey());
try {
int bytesWritten = ebchannel.write(this.getCharset().encode(text));
if (text.length() == 0) Assert.assertEquals(keySizeInBytes, bytesWritten);
else Assert.assertEquals(this.getKeySizeInBytes() + Math.max(1, text.length() / keySizeInBytes * keySizeInBytes), bytesWritten);
} finally {
ebchannel.close();
}
Assert.assertTrue(bbchannel.size() >= text.length());
ByteBuffer bbuffer = ByteBuffer.allocate(1024);
IVDecryptingByteChannel dbchannel = new IVDecryptingByteChannel(bbchannel, this.getKey());
try {
int bytesRead = dbchannel.read(bbuffer);
Assert.assertEquals(text.length() / keySizeInBytes * keySizeInBytes + 2 * keySizeInBytes, bytesRead);
} finally {
dbchannel.close();
}
bbuffer.flip();
Assert.assertEquals(text, this.getCharset().decode(bbuffer).toString());
}
private void chunk(String text, int chunkSize) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
ByteBuffer bbuffer = this.getCharset().encode(text);
int realLimit = bbuffer.limit();
bbuffer.limit(Math.min(chunkSize, realLimit));
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
IVEncryptingByteChannel ebchannel = new IVEncryptingByteChannel(bbchannel, getKey());
try {
int bytesWritten = 1;
while (bytesWritten > 0) {
bytesWritten = ebchannel.write(bbuffer);
bbuffer.limit(Math.min(bbuffer.limit() + chunkSize, realLimit));
}
} finally {
ebchannel.close();
}
Assert.assertTrue(bbchannel.size() >= text.length());
bbuffer = ByteBuffer.allocate(1024);
realLimit = bbuffer.limit();
bbuffer.limit(Math.min(chunkSize, realLimit));
IVDecryptingByteChannel dbchannel = new IVDecryptingByteChannel(bbchannel, getKey());
try {
int bytesRead = 1;
while (bytesRead >= 0) {
bytesRead = dbchannel.read(bbuffer);
bbuffer.limit(Math.min(bbuffer.limit() + chunkSize, realLimit));
}
} finally {
dbchannel.close();
}
bbuffer.flip();
Assert.assertEquals(text, this.getCharset().decode(bbuffer).toString());
}
private void partialText(String text, String startsWith) throws Exception {
int keySizeInBytes = this.getKeySizeInBytes();
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
IVEncryptingByteChannel ebchannel = new IVEncryptingByteChannel(bbchannel, this.getKey());
try {
int bytesWritten = ebchannel.write(this.getCharset().encode(text));
Assert.assertEquals(this.getKeySizeInBytes() + Math.max(1, text.length() / keySizeInBytes * keySizeInBytes), bytesWritten);
} finally {
ebchannel.close();
}
Assert.assertTrue(bbchannel.size() >= text.length());
ByteBuffer bbuffer = ByteBuffer.allocate(startsWith.length());
IVDecryptingByteChannel dbchannel = new IVDecryptingByteChannel(bbchannel, this.getKey());
try {
int bytesRead = dbchannel.read(bbuffer);
Assert.assertEquals(startsWith.length() / keySizeInBytes * keySizeInBytes + 2 * keySizeInBytes, bytesRead);
} finally {
dbchannel.close();
}
bbuffer.flip();
Assert.assertEquals(startsWith, this.getCharset().decode(bbuffer).toString());
}
}

View File

@@ -0,0 +1,131 @@
package com.inteligr8.nio;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.Key;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.crypto.KeyGenerator;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.inteligr8.nio.IVDecryptingByteChannel;
import com.inteligr8.nio.IVEncryptingByteChannel;
public class StreamingCryptoByteChannelUnitTest {
private static Key key;
@BeforeClass
public static void createKey() throws Exception {
key = KeyGenerator.getInstance("AES").generateKey();
}
@Test
public void streamNioCryptoJar100() throws IOException, InterruptedException, TimeoutException, ExecutionException {
File userHome = new File(System.getProperty("user.home"));
File jar = new File(userHome, ".m2/repository/org/alfresco/consulting/nio-crypto/1.0.0-SNAPSHOT/nio-crypto-1.0.0-SNAPSHOT.jar");
this.stream(jar, 16384);
this.stream(jar, 1024);
this.stream(jar, 32);
this.stream(jar, 3);
this.stream(jar, 1);
}
@Test
public void streamAlfrescoWar50d() throws IOException, InterruptedException, TimeoutException, ExecutionException {
File userHome = new File(System.getProperty("user.home"));
File war = new File(userHome, ".m2/repository/org/alfresco/alfresco/5.0.d/alfresco-5.0.d.war");
this.stream(war, 16384);
}
private void stream(final File file, final int bufferSize) throws IOException, InterruptedException, TimeoutException, ExecutionException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 1L, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(5));
final int port = 12345;
Future<Void> encrypter = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
RandomAccessFile rafile = new RandomAccessFile(file, "r");
FileChannel fchannel = rafile.getChannel();
Thread.sleep(100L);
SocketChannel channel = SocketChannel.open();
Assert.assertTrue(channel.connect(new InetSocketAddress("localhost", port)));
IVEncryptingByteChannel ebchannel = new IVEncryptingByteChannel(channel, key);
ByteBuffer bbuffer = ByteBuffer.allocate(bufferSize);
while (fchannel.read(bbuffer) >= 0) {
bbuffer.flip();
ebchannel.write(bbuffer);
Assert.assertFalse(bbuffer.hasRemaining());
bbuffer.compact();
}
bbuffer.flip();
while (bbuffer.hasRemaining())
ebchannel.write(bbuffer);
ebchannel.flush();
ebchannel.close();
channel.close();
rafile.close();
return null;
}
});
Future<Void> decrypter = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
RandomAccessFile rafile = new RandomAccessFile(file, "r");
FileChannel fchannel = rafile.getChannel();
ServerSocketChannel schannel = ServerSocketChannel.open();
schannel.socket().bind(new InetSocketAddress(port));
SocketChannel channel = schannel.accept();
IVDecryptingByteChannel dbchannel = new IVDecryptingByteChannel(channel, key);
ByteBuffer bbuffer = ByteBuffer.allocate(bufferSize);
ByteBuffer fbuffer = ByteBuffer.allocate(bufferSize);
while (dbchannel.read(bbuffer) >= 0) {
bbuffer.flip();
fchannel.read(fbuffer);
fbuffer.flip();
Assert.assertEquals(fbuffer, bbuffer);
bbuffer.clear();
fbuffer.clear();
}
dbchannel.close();
channel.close();
schannel.close();
rafile.close();
return null;
}
});
encrypter.get(1L, TimeUnit.HOURS);
decrypter.get(1L, TimeUnit.HOURS);
executor.shutdown();
executor.awaitTermination(1L, TimeUnit.MINUTES);
}
}