Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.commons.crypto.stream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Properties;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import org.apache.commons.crypto.Crypto;
import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.stream.input.ChannelInput;
import org.apache.commons.crypto.stream.input.Input;
import org.apache.commons.crypto.stream.input.StreamInput;
import org.apache.commons.crypto.utils.Utils;
CryptoInputStream reads input data and decrypts data in stream manner. It
supports any mode of operations such as AES CBC/CTR/GCM mode in concept.It is
not thread-safe.
/**
* CryptoInputStream reads input data and decrypts data in stream manner. It
* supports any mode of operations such as AES CBC/CTR/GCM mode in concept.It is
* not thread-safe.
*
*/
public class CryptoInputStream extends InputStream implements
ReadableByteChannel {
private final byte[] oneByteBuf = new byte[1];
The configuration key of the buffer size for stream.
/**
* The configuration key of the buffer size for stream.
*/
public static final String STREAM_BUFFER_SIZE_KEY = Crypto.CONF_PREFIX
+ "stream.buffer.size";
The CryptoCipher instance. /** The CryptoCipher instance. */
final CryptoCipher cipher;
The buffer size. /** The buffer size. */
final int bufferSize;
Crypto key for the cipher. /** Crypto key for the cipher. */
final Key key;
the algorithm parameters /** the algorithm parameters */
final AlgorithmParameterSpec params;
Flag to mark whether the input stream is closed. /** Flag to mark whether the input stream is closed. */
private boolean closed;
Flag to mark whether do final of the cipher to end the decrypting stream.
/**
* Flag to mark whether do final of the cipher to end the decrypting stream.
*/
private boolean finalDone = false;
The input data. /** The input data. */
Input input;
Input data buffer. The data starts at inBuffer.position() and ends at to
inBuffer.limit().
/**
* Input data buffer. The data starts at inBuffer.position() and ends at to
* inBuffer.limit().
*/
ByteBuffer inBuffer;
The decrypted data buffer. The data starts at outBuffer.position() and
ends at outBuffer.limit().
/**
* The decrypted data buffer. The data starts at outBuffer.position() and
* ends at outBuffer.limit().
*/
ByteBuffer outBuffer;
// stream related configuration keys
The default value of the buffer size for stream.
/**
* The default value of the buffer size for stream.
*/
private static final int STREAM_BUFFER_SIZE_DEFAULT = 8192;
private static final int MIN_BUFFER_SIZE = 512;
Constructs a CryptoInputStream
. Params: - transformation – the name of the transformation, e.g.,
AES/CBC/PKCS5Padding.
See the Java Cryptography Architecture Standard Algorithm Name Documentation
for information about standard transformation names.
- props – The
Properties
class represents a set of
properties. - in – the input stream.
- key – crypto key for the cipher.
- params – the algorithm parameters.
Throws: - IOException – if an I/O error occurs.
/**
* Constructs a {@link CryptoInputStream}.
*
* @param transformation the name of the transformation, e.g.,
* <i>AES/CBC/PKCS5Padding</i>.
* See the Java Cryptography Architecture Standard Algorithm Name Documentation
* for information about standard transformation names.
* @param props The <code>Properties</code> class represents a set of
* properties.
* @param in the input stream.
* @param key crypto key for the cipher.
* @param params the algorithm parameters.
* @throws IOException if an I/O error occurs.
*/
public CryptoInputStream(String transformation,
Properties props, InputStream in, Key key,
AlgorithmParameterSpec params) throws IOException {
this(in, Utils.getCipherInstance(transformation, props),
CryptoInputStream.getBufferSize(props), key, params);
}
Constructs a CryptoInputStream
. Params: - transformation – the name of the transformation, e.g.,
AES/CBC/PKCS5Padding.
See the Java Cryptography Architecture Standard Algorithm Name Documentation
for information about standard transformation names.
- props – The
Properties
class represents a set of
properties. - in – the ReadableByteChannel object.
- key – crypto key for the cipher.
- params – the algorithm parameters.
Throws: - IOException – if an I/O error occurs.
/**
* Constructs a {@link CryptoInputStream}.
*
* @param transformation the name of the transformation, e.g.,
* <i>AES/CBC/PKCS5Padding</i>.
* See the Java Cryptography Architecture Standard Algorithm Name Documentation
* for information about standard transformation names.
* @param props The <code>Properties</code> class represents a set of
* properties.
* @param in the ReadableByteChannel object.
* @param key crypto key for the cipher.
* @param params the algorithm parameters.
* @throws IOException if an I/O error occurs.
*/
public CryptoInputStream(String transformation,
Properties props, ReadableByteChannel in, Key key,
AlgorithmParameterSpec params) throws IOException {
this(in, Utils.getCipherInstance(transformation, props), CryptoInputStream
.getBufferSize(props), key, params);
}
Constructs a CryptoInputStream
. Params: - cipher – the cipher instance.
- in – the input stream.
- bufferSize – the bufferSize.
- key – crypto key for the cipher.
- params – the algorithm parameters.
Throws: - IOException – if an I/O error occurs.
/**
* Constructs a {@link CryptoInputStream}.
*
* @param cipher the cipher instance.
* @param in the input stream.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param params the algorithm parameters.
* @throws IOException if an I/O error occurs.
*/
protected CryptoInputStream(InputStream in, CryptoCipher cipher,
int bufferSize, Key key, AlgorithmParameterSpec params)
throws IOException {
this(new StreamInput(in, bufferSize), cipher, bufferSize, key, params);
}
Constructs a CryptoInputStream
. Params: - in – the ReadableByteChannel instance.
- cipher – the cipher instance.
- bufferSize – the bufferSize.
- key – crypto key for the cipher.
- params – the algorithm parameters.
Throws: - IOException – if an I/O error occurs.
/**
* Constructs a {@link CryptoInputStream}.
*
* @param in the ReadableByteChannel instance.
* @param cipher the cipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param params the algorithm parameters.
* @throws IOException if an I/O error occurs.
*/
protected CryptoInputStream(ReadableByteChannel in, CryptoCipher cipher,
int bufferSize, Key key, AlgorithmParameterSpec params)
throws IOException {
this(new ChannelInput(in), cipher, bufferSize, key, params);
}
Constructs a CryptoInputStream
. Params: - input – the input data.
- cipher – the cipher instance.
- bufferSize – the bufferSize.
- key – crypto key for the cipher.
- params – the algorithm parameters.
Throws: - IOException – if an I/O error occurs.
/**
* Constructs a {@link CryptoInputStream}.
*
* @param input the input data.
* @param cipher the cipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param params the algorithm parameters.
* @throws IOException if an I/O error occurs.
*/
protected CryptoInputStream(Input input, CryptoCipher cipher, int bufferSize,
Key key, AlgorithmParameterSpec params) throws IOException {
this.input = input;
this.cipher = cipher;
this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
this.key = key;
this.params = params;
if (!(params instanceof IvParameterSpec)) {
// other AlgorithmParameterSpec such as GCMParameterSpec is not
// supported now.
throw new IOException("Illegal parameters");
}
inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
outBuffer = ByteBuffer.allocateDirect(this.bufferSize
+ cipher.getBlockSize());
outBuffer.limit(0);
initCipher();
}
Overrides the InputStream.read()
. Reads the next byte of data from the input stream. Throws: - IOException – if an I/O error occurs.
Returns: the next byte of data, or -1
if the end of the
stream is reached.
/**
* Overrides the {@link java.io.InputStream#read()}. Reads the next byte of
* data from the input stream.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @throws IOException if an I/O error occurs.
*/
@Override
public int read() throws IOException {
int n;
while ((n = read(oneByteBuf, 0, 1)) == 0) { //NOPMD
/* no op */
}
return (n == -1) ? -1 : oneByteBuf[0] & 0xff;
}
Overrides the InputStream.read(byte[], int, int)
. Decryption is buffer based. If there is data in outBuffer
, then read it out of this buffer. If there is no data in outBuffer
, then read more from the underlying stream and do the decryption. Params: - b – the buffer into which the decrypted data is read.
- off – the buffer offset.
- len – the maximum number of decrypted data bytes to read.
Throws: - IOException – if an I/O error occurs.
Returns: int the total number of decrypted data bytes read into the
buffer.
/**
* Overrides the {@link java.io.InputStream#read(byte[], int, int)}.
* Decryption is buffer based. If there is data in {@link #outBuffer}, then
* read it out of this buffer. If there is no data in {@link #outBuffer},
* then read more from the underlying stream and do the decryption.
*
* @param b the buffer into which the decrypted data is read.
* @param off the buffer offset.
* @param len the maximum number of decrypted data bytes to read.
* @return int the total number of decrypted data bytes read into the
* buffer.
* @throws IOException if an I/O error occurs.
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
checkStream();
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int remaining = outBuffer.remaining();
if (remaining > 0) {
// Satisfy the read with the existing data
int n = Math.min(len, remaining);
outBuffer.get(b, off, n);
return n;
}
// No data in the out buffer, try read new data and decrypt it
int nd = decryptMore();
if (nd <= 0) {
return nd;
}
int n = Math.min(len, outBuffer.remaining());
outBuffer.get(b, off, n);
return n;
}
Overrides the InputStream.skip(long)
. Skips over and discards n
bytes of data from this input stream.
Params: - n – the number of bytes to be skipped.
Throws: - IOException – if an I/O error occurs.
Returns: the actual number of bytes skipped.
/**
* Overrides the {@link java.io.InputStream#skip(long)}. Skips over and
* discards <code>n</code> bytes of data from this input stream.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @throws IOException if an I/O error occurs.
*/
@Override
public long skip(long n) throws IOException {
Utils.checkArgument(n >= 0, "Negative skip length.");
checkStream();
if (n == 0) {
return 0;
}
long remaining = n;
int nd;
while (remaining > 0) {
if (remaining <= outBuffer.remaining()) {
// Skip in the remaining buffer
int pos = outBuffer.position() + (int) remaining;
outBuffer.position(pos);
remaining = 0;
break;
}
remaining -= outBuffer.remaining();
outBuffer.clear();
nd = decryptMore();
if (nd < 0) {
break;
}
}
return n - remaining;
}
Overrides the InputStream.available()
. Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking by the next invocation of a method for this input stream. Throws: - IOException – if an I/O error occurs.
Returns: an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking or 0
when it reaches the end of the input stream.
/**
* Overrides the {@link InputStream#available()}. Returns an estimate of the
* number of bytes that can be read (or skipped over) from this input stream
* without blocking by the next invocation of a method for this input
* stream.
*
* @return an estimate of the number of bytes that can be read (or skipped
* over) from this input stream without blocking or {@code 0} when
* it reaches the end of the input stream.
* @throws IOException if an I/O error occurs.
*/
@Override
public int available() throws IOException {
checkStream();
return input.available() + outBuffer.remaining();
}
Overrides the InputStream.close()
. Closes this input stream and releases any system resources associated with the stream. Throws: - IOException – if an I/O error occurs.
/**
* Overrides the {@link InputStream#close()}. Closes this input stream and
* releases any system resources associated with the stream.
*
* @throws IOException if an I/O error occurs.
*/
@Override
public void close() throws IOException {
if (closed) {
return;
}
input.close();
freeBuffers();
cipher.close();
super.close();
closed = true;
}
Overrides the InputStream.mark(int)
. For CryptoInputStream
,we don't support the mark method. Params: - readlimit – the maximum limit of bytes that can be read before the
mark position becomes invalid.
/**
* Overrides the {@link java.io.InputStream#mark(int)}. For
* {@link CryptoInputStream},we don't support the mark method.
*
* @param readlimit the maximum limit of bytes that can be read before the
* mark position becomes invalid.
*/
@Override
public void mark(int readlimit) {
}
Overrides the InputStream.reset()
. For CryptoInputStream
,we don't support the reset method. Throws: - IOException – if an I/O error occurs.
/**
* Overrides the {@link InputStream#reset()}. For {@link CryptoInputStream}
* ,we don't support the reset method.
*
* @throws IOException if an I/O error occurs.
*/
@Override
public void reset() throws IOException {
throw new IOException("Mark/reset not supported");
}
Overrides the InputStream.markSupported()
. Returns: false,the CtrCryptoInputStream
don't support the mark method.
/**
* Overrides the {@link InputStream#markSupported()}.
*
* @return false,the {@link CtrCryptoInputStream} don't support the mark
* method.
*/
@Override
public boolean markSupported() {
return false;
}
Overrides the Channel.isOpen()
. Returns: true if, and only if, this channel is open.
/**
* Overrides the {@link java.nio.channels.Channel#isOpen()}.
*
* @return <tt>true</tt> if, and only if, this channel is open.
*/
@Override
public boolean isOpen() {
return !closed;
}
Overrides the ReadableByteChannel.read(ByteBuffer)
. Reads a sequence of bytes from this channel into the given buffer. Params: - dst – The buffer into which bytes are to be transferred.
Throws: - IOException – if an I/O error occurs.
Returns: The number of bytes read, possibly zero, or -1 if the
channel has reached end-of-stream.
/**
* Overrides the
* {@link java.nio.channels.ReadableByteChannel#read(ByteBuffer)}. Reads a
* sequence of bytes from this channel into the given buffer.
*
* @param dst The buffer into which bytes are to be transferred.
* @return The number of bytes read, possibly zero, or <tt>-1</tt> if the
* channel has reached end-of-stream.
* @throws IOException if an I/O error occurs.
*/
@Override
public int read(ByteBuffer dst) throws IOException {
checkStream();
int remaining = outBuffer.remaining();
if (remaining <= 0) {
// Decrypt more data
int nd = decryptMore();
if (nd < 0) {
return -1;
}
}
// Copy decrypted data from outBuffer to dst
remaining = outBuffer.remaining();
final int toRead = dst.remaining();
if (toRead <= remaining) {
final int limit = outBuffer.limit();
outBuffer.limit(outBuffer.position() + toRead);
dst.put(outBuffer);
outBuffer.limit(limit);
return toRead;
}
dst.put(outBuffer);
return remaining;
}
Gets the buffer size.
Returns: the bufferSize.
/**
* Gets the buffer size.
*
* @return the bufferSize.
*/
protected int getBufferSize() {
return bufferSize;
}
Gets the key.
Returns: the key.
/**
* Gets the key.
*
* @return the key.
*/
protected Key getKey() {
return key;
}
Gets the internal CryptoCipher.
Returns: the cipher instance.
/**
* Gets the internal CryptoCipher.
*
* @return the cipher instance.
*/
protected CryptoCipher getCipher() {
return cipher;
}
Gets the specification of cryptographic parameters.
Returns: the params.
/**
* Gets the specification of cryptographic parameters.
*
* @return the params.
*/
protected AlgorithmParameterSpec getParams() {
return params;
}
Gets the input.
Returns: the input.
/**
* Gets the input.
*
* @return the input.
*/
protected Input getInput() {
return input;
}
Initializes the cipher.
Throws: - IOException – if an I/O error occurs.
/**
* Initializes the cipher.
*
* @throws IOException if an I/O error occurs.
*/
protected void initCipher() throws IOException {
try {
cipher.init(Cipher.DECRYPT_MODE, key, params);
} catch (InvalidKeyException e) {
throw new IOException(e);
} catch (InvalidAlgorithmParameterException e) {
throw new IOException(e);
}
}
Decrypts more data by reading the under layer stream. The decrypted data
will be put in the output buffer. If the end of the under stream reached,
we will do final of the cipher to finish all the decrypting of data.
Throws: - IOException – if an I/O error occurs.
Returns: The number of decrypted data. -1 if end of the decrypted stream.
/**
* Decrypts more data by reading the under layer stream. The decrypted data
* will be put in the output buffer. If the end of the under stream reached,
* we will do final of the cipher to finish all the decrypting of data.
*
* @return The number of decrypted data. -1 if end of the decrypted stream.
* @throws IOException if an I/O error occurs.
*/
protected int decryptMore() throws IOException {
if (finalDone) {
return -1;
}
int n = input.read(inBuffer);
if (n < 0) {
// The stream is end, finalize the cipher stream
decryptFinal();
// Satisfy the read with the remaining
int remaining = outBuffer.remaining();
if (remaining > 0) {
return remaining;
}
// End of the stream
return -1;
} else if (n == 0) {
// No data is read, but the stream is not end yet
return 0;
} else {
decrypt();
return outBuffer.remaining();
}
}
Does the decryption using inBuffer as input and outBuffer as output. Upon
return, inBuffer is cleared; the decrypted data starts at
outBuffer.position() and ends at outBuffer.limit().
Throws: - IOException – if an I/O error occurs.
/**
* Does the decryption using inBuffer as input and outBuffer as output. Upon
* return, inBuffer is cleared; the decrypted data starts at
* outBuffer.position() and ends at outBuffer.limit().
*
* @throws IOException if an I/O error occurs.
*/
protected void decrypt() throws IOException {
// Prepare the input buffer and clear the out buffer
inBuffer.flip();
outBuffer.clear();
try {
cipher.update(inBuffer, outBuffer);
} catch (ShortBufferException e) {
throw new IOException(e);
}
// Clear the input buffer and prepare out buffer
inBuffer.clear();
outBuffer.flip();
}
Does final of the cipher to end the decrypting stream.
Throws: - IOException – if an I/O error occurs.
/**
* Does final of the cipher to end the decrypting stream.
*
* @throws IOException if an I/O error occurs.
*/
protected void decryptFinal() throws IOException {
// Prepare the input buffer and clear the out buffer
inBuffer.flip();
outBuffer.clear();
try {
cipher.doFinal(inBuffer, outBuffer);
finalDone = true;
} catch (ShortBufferException e) {
throw new IOException(e);
} catch (IllegalBlockSizeException e) {
throw new IOException(e);
} catch (BadPaddingException e) {
throw new IOException(e);
}
// Clear the input buffer and prepare out buffer
inBuffer.clear();
outBuffer.flip();
}
Checks whether the stream is closed.
Throws: - IOException – if an I/O error occurs.
/**
* Checks whether the stream is closed.
*
* @throws IOException if an I/O error occurs.
*/
protected void checkStream() throws IOException {
if (closed) {
throw new IOException("Stream closed");
}
}
Forcibly free the direct buffers. /** Forcibly free the direct buffers. */
protected void freeBuffers() {
CryptoInputStream.freeDirectBuffer(inBuffer);
CryptoInputStream.freeDirectBuffer(outBuffer);
}
Forcibly free the direct buffer.
Params: - buffer – the bytebuffer to be freed.
/**
* Forcibly free the direct buffer.
*
* @param buffer the bytebuffer to be freed.
*/
static void freeDirectBuffer(ByteBuffer buffer) {
try {
/* Using reflection to implement sun.nio.ch.DirectBuffer.cleaner()
.clean(); */
final String SUN_CLASS = "sun.nio.ch.DirectBuffer";
Class<?>[] interfaces = buffer.getClass().getInterfaces();
for (Class<?> clazz : interfaces) {
if (clazz.getName().equals(SUN_CLASS)) {
final Object[] NO_PARAM = new Object[0];
/* DirectBuffer#cleaner() */
Method getCleaner = Class.forName(SUN_CLASS).getMethod("cleaner");
Object cleaner = getCleaner.invoke(buffer, NO_PARAM);
/* Cleaner#clean() */
Method cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
cleanMethod.invoke(cleaner, NO_PARAM);
return;
}
}
} catch (ReflectiveOperationException e) { // NOPMD
// Ignore the Reflection exception.
}
}
Reads crypto buffer size.
Params: - props – The
Properties
class represents a set of
properties.
Returns: the buffer size.
/**
* Reads crypto buffer size.
*
* @param props The <code>Properties</code> class represents a set of
* properties.
* @return the buffer size.
* */
static int getBufferSize(Properties props) {
String bufferSizeStr = props.getProperty(CryptoInputStream.STREAM_BUFFER_SIZE_KEY);
if (bufferSizeStr == null || bufferSizeStr.isEmpty()) {
return CryptoInputStream.STREAM_BUFFER_SIZE_DEFAULT;
}
return Integer.parseInt(bufferSizeStr);
}
Checks whether the cipher is supported streaming.
Params: - cipher – the
CryptoCipher
instance.
Throws: - IOException – if an I/O error occurs.
/**
* Checks whether the cipher is supported streaming.
*
* @param cipher the {@link CryptoCipher} instance.
* @throws IOException if an I/O error occurs.
*/
static void checkStreamCipher(CryptoCipher cipher)
throws IOException {
if (!cipher.getAlgorithm().equals("AES/CTR/NoPadding")) {
throw new IOException("AES/CTR/NoPadding is required");
}
}
Checks and floors buffer size.
Params: - cipher – the
CryptoCipher
instance. - bufferSize – the buffer size.
Returns: the remaining buffer size.
/**
* Checks and floors buffer size.
*
* @param cipher the {@link CryptoCipher} instance.
* @param bufferSize the buffer size.
* @return the remaining buffer size.
*/
static int checkBufferSize(CryptoCipher cipher, int bufferSize) {
Utils.checkArgument(bufferSize >= CryptoInputStream.MIN_BUFFER_SIZE,
"Minimum value of buffer size is " + CryptoInputStream.MIN_BUFFER_SIZE + ".");
return bufferSize - bufferSize
% cipher.getBlockSize();
}
}