org.apache.commons.crypto.stream.CryptoInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-crypto Show documentation
Show all versions of commons-crypto Show documentation
Apache Commons Crypto is a cryptographic library optimized with AES-NI (Advanced Encryption
Standard New Instructions). It provides Java API for both cipher level and Java stream level.
Developers can use it to implement high performance AES encryption/decryption with the minimum
code and effort. Please note that Crypto doesn't implement the cryptographic algorithm such as
AES directly. It wraps to Openssl or JCE which implement the algorithms.
Features
--------
1. Cipher API for low level cryptographic operations.
2. Java stream API (CryptoInputStream/CryptoOutputStream) for high level stream encyrption/decryption.
3. Both optimized with high performance AES encryption/decryption. (1400 MB/s - 1700 MB/s throughput in modern Xeon processors).
4. JNI-based implementation to achieve comparable performance to the native C++ version based on OpenSsl.
5. Portable across various operating systems (currently only Linux/MacOSX/Windows);
Apache Commons Crypto loads the library according to your machine environment (it checks system properties, `os.name` and `os.arch`).
6. Simple usage. Add the commons-crypto-(version).jar file to your classpath.
Export restrictions
-------------------
This distribution includes cryptographic software.
The country in which you currently reside may have restrictions
on the import, possession, use, and/or re-export to another country,
of encryption software. BEFORE using any encryption software,
please check your country's laws, regulations and policies
concerning the import, possession, or use, and re-export of
encryption software, to see if this is permitted.
See <http://www.wassenaar.org/> for more information.
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS),
has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1,
which includes information security software using or performing
cryptographic functions with asymmetric algorithms.
The form and manner of this Apache Software Foundation distribution makes
it eligible for export under the License Exception
ENC Technology Software Unrestricted (TSU) exception
(see the BIS Export Administration Regulations, Section 740.13)
for both object code and source code.
The following provides more details on the included cryptographic software:
* Commons Crypto use [Java Cryptography Extension](http://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html) provided by Java
* Commons Crypto link to and use [OpenSSL](https://www.openssl.org/) ciphers
/**
* 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.
*
*/
public class CryptoInputStream extends InputStream implements
ReadableByteChannel {
private final byte[] oneByteBuf = new byte[1];
/**
* 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. */
final CryptoCipher cipher;
/** The buffer size. */
final int bufferSize;
/** Crypto key for the cipher. */
final Key key;
/** the algorithm parameters */
final AlgorithmParameterSpec params;
/** 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.
*/
private boolean finalDone = false;
/** The input data. */
Input input;
/**
* 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().
*/
ByteBuffer outBuffer;
// stream related configuration keys
/**
* 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 {@link CryptoInputStream}.
*
* @param 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.
* @param props The Properties
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 {@link CryptoInputStream}.
*
* @param 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.
* @param props The Properties
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 {@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 {@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 {@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 {@link java.io.InputStream#read()}. Reads the next byte of
* data from the input stream.
*
* @return the next byte of data, or -1
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 {@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 {@link java.io.InputStream#skip(long)}. Skips over and
* discards n
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 {@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 {@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 {@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 {@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 {@link InputStream#markSupported()}.
*
* @return false,the {@link CtrCryptoInputStream} don't support the mark
* method.
*/
@Override
public boolean markSupported() {
return false;
}
/**
* Overrides the {@link java.nio.channels.Channel#isOpen()}.
*
* @return true if, and only if, this channel is open.
*/
@Override
public boolean isOpen() {
return !closed;
}
/**
* 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 -1 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.
*
* @return the bufferSize.
*/
protected int getBufferSize() {
return bufferSize;
}
/**
* Gets the key.
*
* @return the key.
*/
protected Key getKey() {
return key;
}
/**
* Gets the internal CryptoCipher.
*
* @return the cipher instance.
*/
protected CryptoCipher getCipher() {
return cipher;
}
/**
* Gets the specification of cryptographic parameters.
*
* @return the params.
*/
protected AlgorithmParameterSpec getParams() {
return params;
}
/**
* Gets the input.
*
* @return the input.
*/
protected Input getInput() {
return input;
}
/**
* 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.
*
* @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.
*/
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.
*/
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.
*/
protected void checkStream() throws IOException {
if (closed) {
throw new IOException("Stream closed");
}
}
/** Forcibly free the direct buffers. */
protected void freeBuffers() {
CryptoInputStream.freeDirectBuffer(inBuffer);
CryptoInputStream.freeDirectBuffer(outBuffer);
}
/**
* 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.
*
* @param props The Properties
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.
*
* @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.
*
* @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();
}
}