http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/CipherOutputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CipherOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CipherOutputStream.java deleted file mode 100644 index 57a0e1b..0000000 --- a/src/main/java/org/apache/commons/crypto/stream/CipherOutputStream.java +++ /dev/null @@ -1,438 +0,0 @@ -/** - * 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.OutputStream; -import java.nio.ByteBuffer; -import java.nio.channels.Channel; -import java.nio.channels.WritableByteChannel; -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.IllegalBlockSizeException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -import org.apache.commons.crypto.cipher.Cipher; -import org.apache.commons.crypto.cipher.CipherTransformation; -import org.apache.commons.crypto.stream.output.ChannelOutput; -import org.apache.commons.crypto.stream.output.Output; -import org.apache.commons.crypto.stream.output.StreamOutput; -import org.apache.commons.crypto.utils.Utils; - -/** - * {@link CipherOutputStream} encrypts data and writes to the under layer - * output. It supports any mode of operations such as AES CBC/CTR/GCM mode - * in concept. It is not thread-safe. - */ - -public class CipherOutputStream extends OutputStream implements - WritableByteChannel { - private final byte[] oneByteBuf = new byte[1]; - - /** The output.*/ - Output output; - - /**the Cipher instance*/ - final Cipher 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 output stream is closed.*/ - private boolean closed; - - /** - * Input data buffer. The data starts at inBuffer.position() and ends at - * inBuffer.limit(). - */ - ByteBuffer inBuffer; - - /** - * Encrypted data buffer. The data starts at outBuffer.position() and ends at - * outBuffer.limit(). - */ - ByteBuffer outBuffer; - - /** - * Constructs a {@link org.apache.commons.crypto.stream.CipherOutputStream}. - * - * @param transformation the CipherTransformation instance. - * @param props The <code>Properties</code> class represents a set of - * properties. - * @param out the output stream. - * @param key crypto key for the cipher. - * @param params the algorithm parameters. - * @throws IOException if an I/O error occurs. - */ - public CipherOutputStream( - CipherTransformation transformation, - Properties props, - OutputStream out, - Key key, - AlgorithmParameterSpec params) throws IOException { - this(out, Utils.getCipherInstance(transformation, props), Utils.getBufferSize(props), key, - params); - } - - /** - * Constructs a {@link org.apache.commons.crypto.stream.CipherOutputStream}. - * - * @param transformation the CipherTransformation instance. - * @param props The <code>Properties</code> class represents a set of - * properties. - * @param out the WritableByteChannel instance. - * @param key crypto key for the cipher. - * @param params the algorithm parameters. - * @throws IOException if an I/O error occurs. - */ - public CipherOutputStream( - CipherTransformation transformation, - Properties props, - WritableByteChannel out, - Key key, - AlgorithmParameterSpec params) throws IOException { - this(out, Utils.getCipherInstance(transformation, props), Utils.getBufferSize(props), key, - params); - } - - /** - * Constructs a {@link org.apache.commons.crypto.stream.CipherOutputStream}. - * - * @param out the output stream. - * @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. - */ - public CipherOutputStream(OutputStream out, Cipher cipher, int bufferSize, - Key key, AlgorithmParameterSpec params) throws IOException { - this(new StreamOutput(out, bufferSize), cipher, bufferSize, key, params); - } - - /** - * Constructs a {@link org.apache.commons.crypto.stream.CipherOutputStream}. - * - * @param channel the WritableByteChannel 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. - */ - public CipherOutputStream(WritableByteChannel channel, Cipher cipher, - int bufferSize, Key key, AlgorithmParameterSpec params) throws IOException { - this(new ChannelOutput(channel), cipher, bufferSize, key, params); - } - - /** - * Constructs a {@link org.apache.commons.crypto.stream.CipherOutputStream}. - * - * @param output the output stream. - * @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 CipherOutputStream(Output output, Cipher cipher, int bufferSize, - Key key, AlgorithmParameterSpec params) - throws IOException { - - this.output = output; - this.bufferSize = Utils.checkBufferSize(cipher, bufferSize); - this.cipher = cipher; - - 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.getTransformation().getAlgorithmBlockSize()); - - initCipher(); - } - - /** - * Overrides the {@link java.io.OutputStream#write(byte[])}. - * Writes the specified byte to this output stream. - * - * @param b the data. - * @throws IOException if an I/O error occurs. - */ - @Override - public void write(int b) throws IOException { - oneByteBuf[0] = (byte) (b & 0xff); - write(oneByteBuf, 0, oneByteBuf.length); - } - - /** - * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}. - * Encryption is buffer based. - * If there is enough room in {@link #inBuffer}, then write to this buffer. - * If {@link #inBuffer} is full, then do encryption and write data to the - * underlying stream. - * - * @param b the data. - * @param off the start offset in the data. - * @param len the number of bytes to write. - * @throws IOException if an I/O error occurs. - */ - @Override - public void write(byte[] b, int off, int len) throws IOException { - checkStream(); - if (b == null) { - throw new NullPointerException(); - } else if (off < 0 || len < 0 || off > b.length || - len > b.length - off) { - throw new IndexOutOfBoundsException(); - } - - while (len > 0) { - final int remaining = inBuffer.remaining(); - if (len < remaining) { - inBuffer.put(b, off, len); - len = 0; - } else { - inBuffer.put(b, off, remaining); - off += remaining; - len -= remaining; - encrypt(); - } - } - } - - /** - * Overrides the {@link OutputStream#flush()}. - * To flush, we need to encrypt the data in the buffer and write to the - * underlying stream, then do the flush. - * - * @throws IOException if an I/O error occurs. - */ - @Override - public void flush() throws IOException { - checkStream(); - encrypt(); - output.flush(); - super.flush(); - } - - /** - * Overrides the {@link OutputStream#close()}. - * Closes this output stream and releases any system resources - * associated with this stream. - * - * @throws IOException if an I/O error occurs. - */ - @Override - public void close() throws IOException { - if (closed) { - return; - } - - try { - encryptFinal(); - output.close(); - freeBuffers(); - cipher.close(); - super.close(); - } finally { - closed = true; - } - } - - /** - * Overrides the {@link Channel#isOpen()}. - * Tells whether or not this channel is open. - * - * @return <tt>true</tt> if, and only if, this channel is open - */ - @Override - public boolean isOpen() { - return !closed; - } - - /** - * Overrides the {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. - * Writes a sequence of bytes to this channel from the given buffer. - * - * @param src The buffer from which bytes are to be retrieved. - * @return The number of bytes written, possibly zero. - * @throws IOException if an I/O error occurs. - */ - @Override - public int write(ByteBuffer src) throws IOException { - checkStream(); - final int len = src.remaining(); - int remaining = len; - while (remaining > 0) { - final int space = inBuffer.remaining(); - if (remaining < space) { - inBuffer.put(src); - remaining = 0; - } else { - // to void copy twice, we set the limit to copy directly - final int oldLimit = src.limit(); - final int newLimit = src.position() + space; - src.limit(newLimit); - - inBuffer.put(src); - - // restore the old limit - src.limit(oldLimit); - - remaining -= space; - encrypt(); - } - } - - return len; - } - - /** - * Initializes the cipher. - * - * @throws IOException if an I/O error occurs. - */ - protected void initCipher() - throws IOException { - try { - cipher.init(Cipher.ENCRYPT_MODE, key, params); - } catch (InvalidKeyException e) { - throw new IOException(e); - } catch(InvalidAlgorithmParameterException e) { - throw new IOException(e); - } - } - - /** - * Does the encryption, input is {@link #inBuffer} and output is - * {@link #outBuffer}. - * - *@throws IOException if an I/O error occurs. - */ - protected void encrypt() throws IOException { - - inBuffer.flip(); - outBuffer.clear(); - - try { - cipher.update(inBuffer, outBuffer); - } catch (ShortBufferException e) { - throw new IOException(e); - } - - inBuffer.clear(); - outBuffer.flip(); - - // write to output - output.write(outBuffer); - } - - /** - * Does final encryption of the last data. - * - * @throws IOException if an I/O error occurs. - */ - protected void encryptFinal() throws IOException { - inBuffer.flip(); - outBuffer.clear(); - - try { - cipher.doFinal(inBuffer, outBuffer); - } catch (ShortBufferException e) { - throw new IOException(e); - } catch (IllegalBlockSizeException e) { - throw new IOException(e); - } catch( BadPaddingException e) { - throw new IOException(e); - } - - inBuffer.clear(); - outBuffer.flip(); - - // write to output - output.write(outBuffer); - } - - protected void checkStream() throws IOException { - if (closed) { - throw new IOException("Stream closed"); - } - } - - /** Forcibly free the direct buffers. */ - protected void freeBuffers() { - Utils.freeDirectBuffer(inBuffer); - Utils.freeDirectBuffer(outBuffer); - } - - /** - * Gets the outBuffer. - * - * @return the outBuffer. - */ - protected ByteBuffer getOutBuffer() { - return outBuffer; - } - - /** - * Gets the internal Cipher. - * - * @return the cipher instance. - */ - protected Cipher getCipher() { - return cipher; - } - - /** - * Gets the buffer size. - * - * @return the buffer size. - */ - protected int getBufferSize() { - return bufferSize; - } - - /** - * Gets the inBuffer. - * - * @return the inBuffer. - */ - protected ByteBuffer getInBuffer() { - return inBuffer; - } -}
http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java new file mode 100644 index 0000000..2d62a76 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java @@ -0,0 +1,559 @@ +/** + * 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.nio.ByteBuffer; +import java.nio.channels.Channel; +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.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +import org.apache.commons.crypto.cipher.CryptoCipher; +import org.apache.commons.crypto.cipher.CipherTransformation; +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 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(). + */ + protected ByteBuffer inBuffer; + + /** + * The decrypted data buffer. The data starts at outBuffer.position() and + * ends at outBuffer.limit(). + */ + protected ByteBuffer outBuffer; + + /** + * Constructs a {@link CryptoInputStream}. + * + * @param transformation the CipherTransformation instance. + * @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(CipherTransformation transformation, + Properties props, InputStream in, Key key, AlgorithmParameterSpec params) + throws IOException { + this(in, Utils.getCipherInstance(transformation, props), Utils.getBufferSize(props), key, + params); + } + + /** + * Constructs a {@link CryptoInputStream}. + * + * @param transformation the CipherTransformation instance. + * @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(CipherTransformation transformation, + Properties props, ReadableByteChannel in, Key key, AlgorithmParameterSpec params) + throws IOException { + this(in, Utils.getCipherInstance(transformation, props), + Utils.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. + */ + public 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. + */ + public 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. + */ + public CryptoInputStream(Input input, CryptoCipher cipher, int bufferSize, + Key key, AlgorithmParameterSpec params) throws IOException { + this.input = input; + this.cipher = cipher; + this.bufferSize = Utils.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.getTransformation().getAlgorithmBlockSize()); + 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 <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) ; + 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; + } else { + // 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 <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; + } else { + 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 CTRCipherInputStream} don't support the mark method. + */ + @Override + public boolean markSupported() { + return false; + } + + /** + * Overrides the {@link Channel#isOpen()}. + * + * @return <tt>true</tt> 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 <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; + } else { + 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(CryptoCipher.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. + */ + 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() { + Utils.freeDirectBuffer(inBuffer); + Utils.freeDirectBuffer(outBuffer); + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java new file mode 100644 index 0000000..7735385 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java @@ -0,0 +1,441 @@ +/** + * 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.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channel; +import java.nio.channels.WritableByteChannel; +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.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +import org.apache.commons.crypto.cipher.CryptoCipher; +import org.apache.commons.crypto.cipher.CipherTransformation; +import org.apache.commons.crypto.stream.output.ChannelOutput; +import org.apache.commons.crypto.stream.output.Output; +import org.apache.commons.crypto.stream.output.StreamOutput; +import org.apache.commons.crypto.utils.Utils; + +/** + * {@link CryptoOutputStream} encrypts data and writes to the under layer + * output. It supports any mode of operations such as AES CBC/CTR/GCM mode + * in concept. It is not thread-safe. + */ + +public class CryptoOutputStream extends OutputStream implements + WritableByteChannel { + private final byte[] oneByteBuf = new byte[1]; + + /** The output.*/ + Output output; + + /**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 output stream is closed.*/ + private boolean closed; + + /** + * Input data buffer. The data starts at inBuffer.position() and ends at + * inBuffer.limit(). + */ + ByteBuffer inBuffer; + + /** + * Encrypted data buffer. The data starts at outBuffer.position() and ends at + * outBuffer.limit(). + */ + ByteBuffer outBuffer; + + /** + * Constructs a {@link org.apache.commons.crypto.stream.CryptoOutputStream}. + * + * @param transformation the CipherTransformation instance. + * @param props The <code>Properties</code> class represents a set of + * properties. + * @param out the output stream. + * @param key crypto key for the cipher. + * @param params the algorithm parameters. + * @throws IOException if an I/O error occurs. + */ + + + public CryptoOutputStream( + CipherTransformation transformation, + Properties props, + OutputStream out, + Key key, + AlgorithmParameterSpec params) throws IOException { + this(out, Utils.getCipherInstance(transformation, props), Utils.getBufferSize(props), key, params); + + } + + /** + * Constructs a {@link org.apache.commons.crypto.stream.CryptoOutputStream}. + * + * @param transformation the CipherTransformation instance. + * @param props The <code>Properties</code> class represents a set of + * properties. + * @param out the WritableByteChannel instance. + * @param key crypto key for the cipher. + * @param params the algorithm parameters. + * @throws IOException if an I/O error occurs. + */ + public CryptoOutputStream( + CipherTransformation transformation, + Properties props, + WritableByteChannel out, + Key key, + AlgorithmParameterSpec params) throws IOException { + this(out, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, params); + + } + + /** + * Constructs a {@link org.apache.commons.crypto.stream.CryptoOutputStream}. + * + * @param out the output stream. + * @param cipher the CryptoCipher 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. + */ + public CryptoOutputStream(OutputStream out, CryptoCipher cipher, int bufferSize, + Key key, AlgorithmParameterSpec params) throws IOException { + this(new StreamOutput(out, bufferSize), cipher, bufferSize, key, params); + } + + /** + * Constructs a {@link org.apache.commons.crypto.stream.CryptoOutputStream}. + * + * @param channel the WritableByteChannel 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. + */ + public CryptoOutputStream(WritableByteChannel channel, CryptoCipher cipher, + int bufferSize, Key key, AlgorithmParameterSpec params) throws IOException { + this(new ChannelOutput(channel), cipher, bufferSize, key, params); + } + + /** + * Constructs a {@link org.apache.commons.crypto.stream.CryptoOutputStream}. + * + * @param output the output stream. + * @param cipher the CryptoCipher 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 CryptoOutputStream(Output output, CryptoCipher cipher, int bufferSize, + Key key, AlgorithmParameterSpec params) + throws IOException { + + this.output = output; + this.bufferSize = Utils.checkBufferSize(cipher, bufferSize); + this.cipher = cipher; + + 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.getTransformation().getAlgorithmBlockSize()); + + initCipher(); + } + + /** + * Overrides the {@link java.io.OutputStream#write(byte[])}. + * Writes the specified byte to this output stream. + * + * @param b the data. + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(int b) throws IOException { + oneByteBuf[0] = (byte) (b & 0xff); + write(oneByteBuf, 0, oneByteBuf.length); + } + + /** + * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}. + * Encryption is buffer based. + * If there is enough room in {@link #inBuffer}, then write to this buffer. + * If {@link #inBuffer} is full, then do encryption and write data to the + * underlying stream. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + checkStream(); + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || off > b.length || + len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + + while (len > 0) { + final int remaining = inBuffer.remaining(); + if (len < remaining) { + inBuffer.put(b, off, len); + len = 0; + } else { + inBuffer.put(b, off, remaining); + off += remaining; + len -= remaining; + encrypt(); + } + } + } + + /** + * Overrides the {@link OutputStream#flush()}. + * To flush, we need to encrypt the data in the buffer and write to the + * underlying stream, then do the flush. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void flush() throws IOException { + checkStream(); + encrypt(); + output.flush(); + super.flush(); + } + + /** + * Overrides the {@link OutputStream#close()}. + * Closes this output stream and releases any system resources + * associated with this stream. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + if (closed) { + return; + } + + try { + encryptFinal(); + output.close(); + freeBuffers(); + cipher.close(); + super.close(); + } finally { + closed = true; + } + } + + /** + * Overrides the {@link Channel#isOpen()}. + * Tells whether or not this channel is open. + * + * @return <tt>true</tt> if, and only if, this channel is open + */ + @Override + public boolean isOpen() { + return !closed; + } + + /** + * Overrides the {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. + * Writes a sequence of bytes to this channel from the given buffer. + * + * @param src The buffer from which bytes are to be retrieved. + * @return The number of bytes written, possibly zero. + * @throws IOException if an I/O error occurs. + */ + @Override + public int write(ByteBuffer src) throws IOException { + checkStream(); + final int len = src.remaining(); + int remaining = len; + while (remaining > 0) { + final int space = inBuffer.remaining(); + if (remaining < space) { + inBuffer.put(src); + remaining = 0; + } else { + // to void copy twice, we set the limit to copy directly + final int oldLimit = src.limit(); + final int newLimit = src.position() + space; + src.limit(newLimit); + + inBuffer.put(src); + + // restore the old limit + src.limit(oldLimit); + + remaining -= space; + encrypt(); + } + } + + return len; + } + + /** + * Initializes the cipher. + * + * @throws IOException if an I/O error occurs. + */ + protected void initCipher() + throws IOException { + try { + cipher.init(CryptoCipher.ENCRYPT_MODE, key, params); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch(InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + } + + /** + * Does the encryption, input is {@link #inBuffer} and output is + * {@link #outBuffer}. + * + *@throws IOException if an I/O error occurs. + */ + protected void encrypt() throws IOException { + + inBuffer.flip(); + outBuffer.clear(); + + try { + cipher.update(inBuffer, outBuffer); + } catch (ShortBufferException e) { + throw new IOException(e); + } + + inBuffer.clear(); + outBuffer.flip(); + + // write to output + output.write(outBuffer); + } + + /** + * Does final encryption of the last data. + * + * @throws IOException if an I/O error occurs. + */ + protected void encryptFinal() throws IOException { + inBuffer.flip(); + outBuffer.clear(); + + try { + cipher.doFinal(inBuffer, outBuffer); + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } catch( BadPaddingException e) { + throw new IOException(e); + } + + inBuffer.clear(); + outBuffer.flip(); + + // write to output + output.write(outBuffer); + } + + protected void checkStream() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + /** Forcibly free the direct buffers. */ + protected void freeBuffers() { + Utils.freeDirectBuffer(inBuffer); + Utils.freeDirectBuffer(outBuffer); + } + + /** + * Gets the outBuffer. + * + * @return the outBuffer. + */ + protected ByteBuffer getOutBuffer() { + return outBuffer; + } + + /** + * Gets the internal Cipher. + * + * @return the cipher instance. + */ + protected CryptoCipher getCipher() { + return cipher; + } + + /** + * Gets the buffer size. + * + * @return the buffer size. + */ + protected int getBufferSize() { + return bufferSize; + } + + /** + * Gets the inBuffer. + * + * @return the inBuffer. + */ + protected ByteBuffer getInBuffer() { + return inBuffer; + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/PositionedCipherInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/PositionedCipherInputStream.java b/src/main/java/org/apache/commons/crypto/stream/PositionedCipherInputStream.java deleted file mode 100644 index 0a7c09b..0000000 --- a/src/main/java/org/apache/commons/crypto/stream/PositionedCipherInputStream.java +++ /dev/null @@ -1,362 +0,0 @@ -/** - * 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.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.util.Properties; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -import org.apache.commons.crypto.cipher.Cipher; -import org.apache.commons.crypto.cipher.CipherFactory; -import org.apache.commons.crypto.stream.input.Input; -import org.apache.commons.crypto.utils.IOUtils; -import org.apache.commons.crypto.utils.Utils; - -import static org.apache.commons.crypto.cipher.CipherTransformation.AES_CTR_NOPADDING; - -/** - * PositionedCipherInputStream provides the capability to decrypt the stream starting - * at random position as well as provides the foundation for positioned read for - * decrypting. This needs a stream cipher mode such as AES CTR mode. - */ -public class PositionedCipherInputStream extends CTRCipherInputStream { - - /** - * DirectBuffer pool - */ - private final Queue<ByteBuffer> bufferPool = new - ConcurrentLinkedQueue<ByteBuffer>(); - - /** - * Cipher pool - */ - private final Queue<CipherState> cipherPool = new - ConcurrentLinkedQueue<CipherState>(); - - /** - * Constructs a {@link PositionedCipherInputStream}. - * - * @param props The <code>Properties</code> class represents a set of - * properties. - * @param in the input data. - * @param key crypto key for the cipher. - * @param iv Initialization vector for the cipher. - * @param streamOffset the start offset in the data. - * @throws IOException if an I/O error occurs. - */ - public PositionedCipherInputStream(Properties props, Input in, byte[] key, - byte[] iv, long streamOffset) throws IOException { - this(in, Utils.getCipherInstance(AES_CTR_NOPADDING, props), - Utils.getBufferSize(props), key, iv, streamOffset); - } - - /** - * Constructs a {@link PositionedCipherInputStream}. - * - * @param input the input data. - * @param cipher the Cipher instance. - * @param bufferSize the bufferSize. - * @param key crypto key for the cipher. - * @param iv Initialization vector for the cipher. - * @param streamOffset the start offset in the data. - * @throws IOException if an I/O error occurs. - */ - public PositionedCipherInputStream(Input input, Cipher cipher, int bufferSize, - byte[] key, byte[] iv, long streamOffset) throws IOException { - super(input, cipher, bufferSize, key, iv, streamOffset); - } - - /** - * Reads up to the specified number of bytes from a given position - * within a stream and return the number of bytes read. This does not - * change the current offset of the stream, and is thread-safe. - * - * @param buffer the buffer into which the data is read. - * @param length the maximum number of bytes to read. - * @param offset the start offset in the data. - * @param position the offset from the start of the stream. - * @throws IOException if an I/O error occurs. - */ - public int read(long position, byte[] buffer, int offset, int length) - throws IOException { - checkStream(); - final int n = input.read(position, buffer, offset, length); - if (n > 0) { - // This operation does not change the current offset of the file - decrypt(position, buffer, offset, n); - } - return n; - } - - /** - * Reads the specified number of bytes from a given position within a stream. - * This does not change the current offset of the stream and is thread-safe. - * - * @param buffer the buffer into which the data is read. - * @param length the maximum number of bytes to read. - * @param offset the start offset in the data. - * @param position the offset from the start of the stream. - * @throws IOException if an I/O error occurs. - */ - public void readFully(long position, byte[] buffer, int offset, int length) - throws IOException { - checkStream(); - IOUtils.readFully(input, position, buffer, offset, length); - if (length > 0) { - // This operation does not change the current offset of the file - decrypt(position, buffer, offset, length); - } - } - - /** - * Reads the specified number of bytes from a given position within a stream. - * This does not change the current offset of the stream and is thread-safe. - * - * @param position the offset from the start of the stream. - * @param buffer the buffer into which the data is read. - * @throws IOException if an I/O error occurs. - */ - public void readFully(long position, byte[] buffer) throws IOException { - readFully(position, buffer, 0, buffer.length); - } - - /** - * Decrypts length bytes in buffer starting at offset. Output is also put - * into buffer starting at offset. It is thread-safe. - * - * @param buffer the buffer into which the data is read. - * @param offset the start offset in the data. - * @param position the offset from the start of the stream. - * @param length the maximum number of bytes to read. - * @throws IOException if an I/O error occurs. - */ - protected void decrypt(long position, byte[] buffer, int offset, int length) - throws IOException { - ByteBuffer inBuffer = getBuffer(); - ByteBuffer outBuffer = getBuffer(); - CipherState state = null; - try { - state = getCipherState(); - byte[] iv = getInitIV().clone(); - resetCipher(state, position, iv); - byte padding = getPadding(position); - inBuffer.position(padding); // Set proper position for input data. - - int n = 0; - while (n < length) { - int toDecrypt = Math.min(length - n, inBuffer.remaining()); - inBuffer.put(buffer, offset + n, toDecrypt); - - // Do decryption - decrypt(state, inBuffer, outBuffer, padding); - - outBuffer.get(buffer, offset + n, toDecrypt); - n += toDecrypt; - padding = postDecryption(state, inBuffer, position + n, iv); - } - } finally { - returnBuffer(inBuffer); - returnBuffer(outBuffer); - returnCipherState(state); - } - } - - /** - * 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() - */ - private void decrypt(CipherState state, ByteBuffer inBuffer, - ByteBuffer outBuffer, byte padding) throws IOException { - Utils.checkState(inBuffer.position() >= padding); - if(inBuffer.position() == padding) { - // There is no real data in inBuffer. - return; - } - inBuffer.flip(); - outBuffer.clear(); - decryptBuffer(state, inBuffer, outBuffer); - inBuffer.clear(); - outBuffer.flip(); - if (padding > 0) { - /* - * The plain text and cipher text have a 1:1 mapping, they start at the - * same position. - */ - outBuffer.position(padding); - } - } - - private void decryptBuffer(CipherState state, ByteBuffer inBuffer, ByteBuffer outBuffer) - throws IOException { - int inputSize = inBuffer.remaining(); - try { - int n = state.getCipher().update(inBuffer, outBuffer); - if (n < inputSize) { - /** - * Typically code will not get here. Cipher#update will consume all - * input data and put result in outBuffer. - * Cipher#doFinal will reset the cipher context. - */ - state.getCipher().doFinal(inBuffer, outBuffer); - state.reset(true); - } - } catch (ShortBufferException e) { - throw new IOException(e); - } catch (IllegalBlockSizeException e) { - throw new IOException(e); - } catch (BadPaddingException e) { - throw new IOException(e); - } - } - - /** - * This method is executed immediately after decryption. Check whether - * cipher should be updated and recalculate padding if needed. - */ - private byte postDecryption(CipherState state, ByteBuffer inBuffer, - long position, byte[] iv) throws IOException { - byte padding = 0; - if (state.isReset()) { - /* - * This code is generally not executed since the cipher usually - * maintains cipher context (e.g. the counter) internally. However, - * some implementations can't maintain context so a re-init is necessary - * after each decryption call. - */ - resetCipher(state, position, iv); - padding = getPadding(position); - inBuffer.position(padding); - } - return padding; - } - - /** Calculate the counter and iv, reset the cipher. */ - private void resetCipher(CipherState state, long position, byte[] iv) - throws IOException { - final long counter = getCounter(position); - Utils.calculateIV(getInitIV(), counter, iv); - try { - state.getCipher().init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); - } catch (InvalidKeyException e) { - throw new IOException(e); - } catch (InvalidAlgorithmParameterException e) { - throw new IOException(e); - } - state.reset(false); - } - - /** Get Cipher from pool */ - private CipherState getCipherState() throws IOException { - CipherState state = cipherPool.poll(); - if (state == null) { - Cipher cipher; - try { - cipher = CipherFactory.getInstance(getCipher().getTransformation(), - getCipher().getProperties()); - } catch (GeneralSecurityException e) { - throw new IOException(e); - } - state = new CipherState(cipher); - } - - return state; - } - - /** Return Cipher to pool */ - private void returnCipherState(CipherState state) { - if (state != null) { - cipherPool.add(state); - } - } - - /** Get direct buffer from pool */ - private ByteBuffer getBuffer() { - ByteBuffer buffer = bufferPool.poll(); - if (buffer == null) { - buffer = ByteBuffer.allocateDirect(getBufferSize()); - } - - return buffer; - } - - /** Return direct buffer to pool */ - private void returnBuffer(ByteBuffer buf) { - if (buf != null) { - buf.clear(); - bufferPool.add(buf); - } - } - - /** - * Overrides the {@link CipherInputStream#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 (!isOpen()) { - return; - } - - cleanBufferPool(); - super.close(); - } - - /** Clean direct buffer pool */ - private void cleanBufferPool() { - ByteBuffer buf; - while ((buf = bufferPool.poll()) != null) { - Utils.freeDirectBuffer(buf); - } - } - - private class CipherState { - private Cipher cipher; - private boolean reset; - - public CipherState(Cipher cipher) { - this.cipher = cipher; - this.reset = false; - } - - public Cipher getCipher() { - return cipher; - } - - public boolean isReset() { - return reset; - } - - public void reset(boolean reset) { - this.reset = reset; - } - } -} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java new file mode 100644 index 0000000..7efbec6 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java @@ -0,0 +1,362 @@ +/** + * 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.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +import org.apache.commons.crypto.cipher.CryptoCipher; +import org.apache.commons.crypto.cipher.CryptoCipherFactory; +import org.apache.commons.crypto.stream.input.Input; +import org.apache.commons.crypto.utils.IOUtils; +import org.apache.commons.crypto.utils.Utils; + +import static org.apache.commons.crypto.cipher.CipherTransformation.AES_CTR_NOPADDING; + +/** + * PositionedCryptoInputStream provides the capability to decrypt the stream starting + * at random position as well as provides the foundation for positioned read for + * decrypting. This needs a stream cipher mode such as AES CTR mode. + */ +public class PositionedCryptoInputStream extends CTRCryptoInputStream { + + /** + * DirectBuffer pool + */ + private final Queue<ByteBuffer> bufferPool = new + ConcurrentLinkedQueue<ByteBuffer>(); + + /** + * CryptoCipher pool + */ + private final Queue<CipherState> cipherPool = new + ConcurrentLinkedQueue<CipherState>(); + + /** + * Constructs a {@link PositionedCryptoInputStream}. + * + * @param props The <code>Properties</code> class represents a set of + * properties. + * @param in the input data. + * @param key crypto key for the cipher. + * @param iv Initialization vector for the cipher. + * @param streamOffset the start offset in the data. + * @throws IOException if an I/O error occurs. + */ + public PositionedCryptoInputStream(Properties props, Input in, byte[] key, + byte[] iv, long streamOffset) throws IOException { + this(in, Utils.getCipherInstance(AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + /** + * Constructs a {@link PositionedCryptoInputStream}. + * + * @param input the input data. + * @param cipher the CryptoCipher instance. + * @param bufferSize the bufferSize. + * @param key crypto key for the cipher. + * @param iv Initialization vector for the cipher. + * @param streamOffset the start offset in the data. + * @throws IOException if an I/O error occurs. + */ + public PositionedCryptoInputStream(Input input, CryptoCipher cipher, int bufferSize, + byte[] key, byte[] iv, long streamOffset) throws IOException { + super(input, cipher, bufferSize, key, iv, streamOffset); + } + + /** + * Reads up to the specified number of bytes from a given position + * within a stream and return the number of bytes read. This does not + * change the current offset of the stream, and is thread-safe. + * + * @param buffer the buffer into which the data is read. + * @param length the maximum number of bytes to read. + * @param offset the start offset in the data. + * @param position the offset from the start of the stream. + * @throws IOException if an I/O error occurs. + */ + public int read(long position, byte[] buffer, int offset, int length) + throws IOException { + checkStream(); + final int n = input.read(position, buffer, offset, length); + if (n > 0) { + // This operation does not change the current offset of the file + decrypt(position, buffer, offset, n); + } + return n; + } + + /** + * Reads the specified number of bytes from a given position within a stream. + * This does not change the current offset of the stream and is thread-safe. + * + * @param buffer the buffer into which the data is read. + * @param length the maximum number of bytes to read. + * @param offset the start offset in the data. + * @param position the offset from the start of the stream. + * @throws IOException if an I/O error occurs. + */ + public void readFully(long position, byte[] buffer, int offset, int length) + throws IOException { + checkStream(); + IOUtils.readFully(input, position, buffer, offset, length); + if (length > 0) { + // This operation does not change the current offset of the file + decrypt(position, buffer, offset, length); + } + } + + /** + * Reads the specified number of bytes from a given position within a stream. + * This does not change the current offset of the stream and is thread-safe. + * + * @param position the offset from the start of the stream. + * @param buffer the buffer into which the data is read. + * @throws IOException if an I/O error occurs. + */ + public void readFully(long position, byte[] buffer) throws IOException { + readFully(position, buffer, 0, buffer.length); + } + + /** + * Decrypts length bytes in buffer starting at offset. Output is also put + * into buffer starting at offset. It is thread-safe. + * + * @param buffer the buffer into which the data is read. + * @param offset the start offset in the data. + * @param position the offset from the start of the stream. + * @param length the maximum number of bytes to read. + * @throws IOException if an I/O error occurs. + */ + protected void decrypt(long position, byte[] buffer, int offset, int length) + throws IOException { + ByteBuffer inBuffer = getBuffer(); + ByteBuffer outBuffer = getBuffer(); + CipherState state = null; + try { + state = getCipherState(); + byte[] iv = getInitIV().clone(); + resetCipher(state, position, iv); + byte padding = getPadding(position); + inBuffer.position(padding); // Set proper position for input data. + + int n = 0; + while (n < length) { + int toDecrypt = Math.min(length - n, inBuffer.remaining()); + inBuffer.put(buffer, offset + n, toDecrypt); + + // Do decryption + decrypt(state, inBuffer, outBuffer, padding); + + outBuffer.get(buffer, offset + n, toDecrypt); + n += toDecrypt; + padding = postDecryption(state, inBuffer, position + n, iv); + } + } finally { + returnBuffer(inBuffer); + returnBuffer(outBuffer); + returnCipherState(state); + } + } + + /** + * 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() + */ + private void decrypt(CipherState state, ByteBuffer inBuffer, + ByteBuffer outBuffer, byte padding) throws IOException { + Utils.checkState(inBuffer.position() >= padding); + if(inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + inBuffer.flip(); + outBuffer.clear(); + decryptBuffer(state, inBuffer, outBuffer); + inBuffer.clear(); + outBuffer.flip(); + if (padding > 0) { + /* + * The plain text and cipher text have a 1:1 mapping, they start at the + * same position. + */ + outBuffer.position(padding); + } + } + + private void decryptBuffer(CipherState state, ByteBuffer inBuffer, ByteBuffer outBuffer) + throws IOException { + int inputSize = inBuffer.remaining(); + try { + int n = state.getCipher().update(inBuffer, outBuffer); + if (n < inputSize) { + /** + * Typically code will not get here. CryptoCipher#update will consume all + * input data and put result in outBuffer. + * CryptoCipher#doFinal will reset the cipher context. + */ + state.getCipher().doFinal(inBuffer, outBuffer); + state.reset(true); + } + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } catch (BadPaddingException e) { + throw new IOException(e); + } + } + + /** + * This method is executed immediately after decryption. Check whether + * cipher should be updated and recalculate padding if needed. + */ + private byte postDecryption(CipherState state, ByteBuffer inBuffer, + long position, byte[] iv) throws IOException { + byte padding = 0; + if (state.isReset()) { + /* + * This code is generally not executed since the cipher usually + * maintains cipher context (e.g. the counter) internally. However, + * some implementations can't maintain context so a re-init is necessary + * after each decryption call. + */ + resetCipher(state, position, iv); + padding = getPadding(position); + inBuffer.position(padding); + } + return padding; + } + + /** Calculate the counter and iv, reset the cipher. */ + private void resetCipher(CipherState state, long position, byte[] iv) + throws IOException { + final long counter = getCounter(position); + Utils.calculateIV(getInitIV(), counter, iv); + try { + state.getCipher().init(CryptoCipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + state.reset(false); + } + + /** Get CryptoCipher from pool */ + private CipherState getCipherState() throws IOException { + CipherState state = cipherPool.poll(); + if (state == null) { + CryptoCipher cipher; + try { + cipher = CryptoCipherFactory.getInstance(getCipher().getTransformation(), + getCipher().getProperties()); + } catch (GeneralSecurityException e) { + throw new IOException(e); + } + state = new CipherState(cipher); + } + + return state; + } + + /** Return CryptoCipher to pool */ + private void returnCipherState(CipherState state) { + if (state != null) { + cipherPool.add(state); + } + } + + /** Get direct buffer from pool */ + private ByteBuffer getBuffer() { + ByteBuffer buffer = bufferPool.poll(); + if (buffer == null) { + buffer = ByteBuffer.allocateDirect(getBufferSize()); + } + + return buffer; + } + + /** Return direct buffer to pool */ + private void returnBuffer(ByteBuffer buf) { + if (buf != null) { + buf.clear(); + bufferPool.add(buf); + } + } + + /** + * Overrides the {@link CryptoInputStream#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 (!isOpen()) { + return; + } + + cleanBufferPool(); + super.close(); + } + + /** Clean direct buffer pool */ + private void cleanBufferPool() { + ByteBuffer buf; + while ((buf = bufferPool.poll()) != null) { + Utils.freeDirectBuffer(buf); + } + } + + private class CipherState { + private CryptoCipher cipher; + private boolean reset; + + public CipherState(CryptoCipher cipher) { + this.cipher = cipher; + this.reset = false; + } + + public CryptoCipher getCipher() { + return cipher; + } + + public boolean isReset() { + return reset; + } + + public void reset(boolean reset) { + this.reset = reset; + } + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java b/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java index 9aff624..ee24a0c 100644 --- a/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java +++ b/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java @@ -23,7 +23,7 @@ import java.nio.channels.ReadableByteChannel; /** * The ChannelInput class takes a <code>ReadableByteChannel</code> object and - * wraps it as <code>Input</code> object acceptable by <code>CipherInputStream</code>. + * wraps it as <code>Input</code> object acceptable by <code>CryptoInputStream</code>. */ public class ChannelInput implements Input { private static final int SKIP_BUFFER_SIZE = 2048; http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/input/Input.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/input/Input.java b/src/main/java/org/apache/commons/crypto/stream/input/Input.java index 2971edb..a63c6ca 100644 --- a/src/main/java/org/apache/commons/crypto/stream/input/Input.java +++ b/src/main/java/org/apache/commons/crypto/stream/input/Input.java @@ -21,7 +21,7 @@ import java.io.IOException; import java.nio.ByteBuffer; /** - * The Input interface abstract the input source of <code>CipherInputStream</code> so that + * The Input interface abstract the input source of <code>CryptoInputStream</code> so that * different implementation of input can be used. The implementation Input interface will usually * wraps an input mechanism such as <code>InputStream</code> or <code>ReadableByteChannel</code>. */ http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java b/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java index c7e6771..ac3739b 100644 --- a/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java +++ b/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java @@ -23,7 +23,7 @@ import java.nio.ByteBuffer; /** * The StreamInput class takes a <code>InputStream</code> object and - * wraps it as <code>Input</code> object acceptable by <code>CipherInputStream</code>. + * wraps it as <code>Input</code> object acceptable by <code>CryptoInputStream</code>. */ public class StreamInput implements Input { private byte[] buf; http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java b/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java index 0a60217..bae82a8 100644 --- a/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java +++ b/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java @@ -23,7 +23,7 @@ import java.nio.channels.WritableByteChannel; /** * The ChannelOutput class takes a <code>WritableByteChannel</code> object and wraps it as - * <code>Output</code> object acceptable by <code>CipherOutputStream</code> as the output target. + * <code>Output</code> object acceptable by <code>CryptoOutputStream</code> as the output target. */ public class ChannelOutput implements Output { http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/output/Output.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/output/Output.java b/src/main/java/org/apache/commons/crypto/stream/output/Output.java index c57a89e..903fcea 100644 --- a/src/main/java/org/apache/commons/crypto/stream/output/Output.java +++ b/src/main/java/org/apache/commons/crypto/stream/output/Output.java @@ -21,7 +21,7 @@ import java.io.IOException; import java.nio.ByteBuffer; /** - * The Output interface abstract the output target of <code>CipherOutputStream</code> so that + * The Output interface abstract the output target of <code>CryptoOutputStream</code> so that * different implementation of output can be used. The implementation Output interface will usually * wraps an output mechanism such as <code>OutputStream</code> or <code>WritableByteChannel</code>. */ http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java b/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java index 73f0c74..e359c0d 100644 --- a/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java +++ b/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java @@ -22,8 +22,8 @@ import java.io.OutputStream; import java.nio.ByteBuffer; /** - * The StreamOutput class takes a <code>OutputStream</code> object and wraps it as - * <code>Output</code> object acceptable by <code>CipherOutputStream</code> as the output target. + * The StreamOutput class takes a <code>OutputStream</code> object and wraps it as + * <code>Output</code> object acceptable by <code>CryptoOutputStream</code> as the output target. */ public class StreamOutput implements Output { private byte[] buf; http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java b/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java index 99e6d8c..4e1abd1 100644 --- a/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java +++ b/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; -import org.apache.commons.crypto.cipher.Cipher; +import org.apache.commons.crypto.cipher.CryptoCipher; /** * General utility methods for working with reflection. @@ -37,7 +37,7 @@ public class ReflectionUtils { static { classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader == null) { - classLoader = Cipher.class.getClassLoader(); + classLoader = CryptoCipher.class.getClassLoader(); } } @@ -51,7 +51,7 @@ public class ReflectionUtils { /** * A unique class which is used as a sentinel value in the caching - * for getClassByName. {@link Cipher#getClassByNameOrNull(String)}. + * for getClassByName. {@link CryptoCipher#getClassByNameOrNull(String)}. */ private static abstract class NegativeCacheSentinel {} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/main/java/org/apache/commons/crypto/utils/Utils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/utils/Utils.java b/src/main/java/org/apache/commons/crypto/utils/Utils.java index c12fd9c..fe413a1 100644 --- a/src/main/java/org/apache/commons/crypto/utils/Utils.java +++ b/src/main/java/org/apache/commons/crypto/utils/Utils.java @@ -26,8 +26,8 @@ import java.util.Enumeration; import java.util.List; import java.util.Properties; -import org.apache.commons.crypto.cipher.Cipher; -import org.apache.commons.crypto.cipher.CipherFactory; +import org.apache.commons.crypto.cipher.CryptoCipher; +import org.apache.commons.crypto.cipher.CryptoCipherFactory; import org.apache.commons.crypto.cipher.CipherTransformation; import org.apache.commons.crypto.conf.ConfigurationKeys; @@ -209,10 +209,10 @@ public class Utils { /** * Checks whether the cipher is supported streaming. * - * @param cipher the {@link org.apache.commons.crypto.cipher.Cipher} instance. + * @param cipher the {@link CryptoCipher} instance. * @throws IOException if an I/O error occurs. */ - public static void checkStreamCipher(Cipher cipher) throws IOException { + public static void checkStreamCipher(CryptoCipher cipher) throws IOException { if (cipher.getTransformation() != CipherTransformation.AES_CTR_NOPADDING) { throw new IOException("AES/CTR/NoPadding is required"); } @@ -221,11 +221,11 @@ public class Utils { /** * Checks and floors buffer size. * - * @param cipher the {@link org.apache.commons.crypto.cipher.Cipher} instance. + * @param cipher the {@link CryptoCipher} instance. * @param bufferSize the buffer size. * @return the remaining buffer size. */ - public static int checkBufferSize(Cipher cipher, int bufferSize) { + public static int checkBufferSize(CryptoCipher cipher, int bufferSize) { checkArgument(bufferSize >= MIN_BUFFER_SIZE, "Minimum value of buffer size is " + MIN_BUFFER_SIZE + "."); return bufferSize - bufferSize % cipher.getTransformation() @@ -233,10 +233,10 @@ public class Utils { } /** - * This method is only for Counter (CTR) mode. Generally the Cipher calculates the + * This method is only for Counter (CTR) mode. Generally the CryptoCipher calculates the * IV and maintain encryption context internally.For example a * {@link javax.crypto.Cipher} will maintain its encryption context internally - * when we do encryption/decryption using the Cipher#update interface. + * when we do encryption/decryption using the CryptoCipher#update interface. * <p/> * Encryption/Decryption is not always on the entire file. For example, * in Hadoop, a node may only decrypt a portion of a file (i.e. a split). @@ -269,18 +269,18 @@ public class Utils { } /** - * Helper method to create a Cipher instance and throws only IOException. + * Helper method to create a CryptoCipher instance and throws only IOException. * * @param props The <code>Properties</code> class represents a set of * properties. * @param transformation the CipherTransformation instance. - * @return the Cipher instance. + * @return the CryptoCipher instance. * @throws IOException if an I/O error occurs. */ - public static Cipher getCipherInstance(CipherTransformation transformation, - Properties props) throws IOException { + public static CryptoCipher getCipherInstance(CipherTransformation transformation, + Properties props) throws IOException { try { - return CipherFactory.getInstance(transformation, props); + return CryptoCipherFactory.getInstance(transformation, props); } catch (GeneralSecurityException e) { throw new IOException(e); }