CRYPTO-12: Rename CryptoInputStream to CipherInputStream and CryptoOutputStream to CipherOutputStream (Xianda Ke via Dian Fu)
Project: http://git-wip-us.apache.org/repos/asf/commons-crypto/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-crypto/commit/ad81d236 Tree: http://git-wip-us.apache.org/repos/asf/commons-crypto/tree/ad81d236 Diff: http://git-wip-us.apache.org/repos/asf/commons-crypto/diff/ad81d236 Branch: refs/heads/master Commit: ad81d236a9e132f59486b5a73c347faabe329e9e Parents: f819dd4 Author: dianfu <dia...@apache.org> Authored: Tue Apr 26 11:27:08 2016 +0800 Committer: dianfu <dia...@apache.org> Committed: Tue Apr 26 11:27:08 2016 +0800 ---------------------------------------------------------------------- README.md | 6 +- .../crypto/stream/CTRCipherInputStream.java | 423 +++++++++++++++++ .../crypto/stream/CTRCipherOutputStream.java | 230 ++++++++++ .../crypto/stream/CTRCryptoInputStream.java | 423 ----------------- .../crypto/stream/CTRCryptoOutputStream.java | 230 ---------- .../crypto/stream/CipherInputStream.java | 394 ++++++++++++++++ .../crypto/stream/CipherOutputStream.java | 283 ++++++++++++ .../crypto/stream/CryptoInputStream.java | 394 ---------------- .../crypto/stream/CryptoOutputStream.java | 283 ------------ .../stream/PositionedCipherInputStream.java | 311 +++++++++++++ .../stream/PositionedCryptoInputStream.java | 311 ------------- .../crypto/stream/input/ChannelInput.java | 2 +- .../commons/crypto/stream/input/Input.java | 2 +- .../crypto/stream/input/StreamInput.java | 2 +- .../crypto/stream/output/ChannelOutput.java | 2 +- .../commons/crypto/stream/output/Output.java | 2 +- .../crypto/stream/output/StreamOutput.java | 2 +- .../crypto/stream/AbstractCipherStreamTest.java | 456 +++++++++++++++++++ .../crypto/stream/AbstractCryptoStreamTest.java | 456 ------------------- .../stream/CBCNoPaddingCipherStreamTest.java | 31 ++ .../stream/CBCNoPaddingCryptoStreamTest.java | 31 -- .../stream/CBCPKCS5PaddingCipherStreamTest.java | 30 ++ .../stream/CBCPKCS5PaddingCryptoStreamTest.java | 30 -- .../crypto/stream/CTRCipherStreamTest.java | 58 +++ .../crypto/stream/CTRCryptoStreamTest.java | 58 --- .../stream/CTRNoPaddingCipherStreamTest.java | 31 ++ .../stream/CTRNoPaddingCryptoStreamTest.java | 31 -- .../stream/PositionedCipherInputStreamTest.java | 381 ++++++++++++++++ .../stream/PositionedCryptoInputStreamTest.java | 381 ---------------- 29 files changed, 2637 insertions(+), 2637 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 34845c0..adc9d63 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Chimera [ for high level stream encyrption/decryption. + * Java stream API (CipherInputStream/CipherOutputStream) for high level stream encyrption/decryption. * Both optimized with high performance AES encryption/decryption. (1400 MB/s - 1700 MB/s throughput in modern Xeon processors). * JNI-based implementation to achieve comparable performance to the native C++ version based on Openssl. * Portable across various operating systems (currently only Linux); Chimera loads the library according to your machine environment (It looks system properties, `os.name` and `os.arch`). @@ -47,13 +47,13 @@ String input = "hello world!"; byte[] decryptedData = new byte[1024]; // Encrypt ByteArrayOutputStream os = new ByteArrayOutputStream(); -CryptoOutputStream cos = new CryptoOutputStream(os, cipher, bufferSize, key, iv); +CipherOutputStream cos = new CipherOutputStream(os, cipher, bufferSize, key, iv); cos.write(input.getBytes("UTF-8")); cos.flush(); cos.close(); // Decrypt -CryptoInputStream cis = new CryptoInputStream(new ByteArrayInputStream(os.toByteArray()), cipher, bufferSize, key, iv); +CipherInputStream cis = new CipherInputStream(new ByteArrayInputStream(os.toByteArray()), cipher, bufferSize, key, iv); int decryptedLen = cis.read(decryptedData, 0, 1024); ``` http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/src/main/java/org/apache/commons/crypto/stream/CTRCipherInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CTRCipherInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CTRCipherInputStream.java new file mode 100644 index 0000000..6c3c657 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CTRCipherInputStream.java @@ -0,0 +1,423 @@ +/** + * 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.ReadableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +import org.apache.commons.crypto.cipher.Cipher; +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; + +/** + * CTRCipherInputStream decrypts data. AES CTR mode is required in order to + * ensure that the plain text and cipher text have a 1:1 mapping. CTR crypto + * stream has stream characteristic which is useful for implement features + * like random seek. The decryption is buffer based. The key points of the + * decryption are (1) calculating the counter and (2) padding through stream + * position: + * <p/> + * counter = base + pos/(algorithm blocksize); + * padding = pos%(algorithm blocksize); + * <p/> + * The underlying stream offset is maintained as state. It is not thread-safe. + */ +public class CTRCipherInputStream extends CipherInputStream { + /** + * Underlying stream offset + */ + protected long streamOffset = 0; + + /** + * Padding = pos%(algorithm blocksize); Padding is put into {@link #inBuffer} + * before any other data goes in. The purpose of padding is to put the input + * data at proper position. + */ + private byte padding; + + /** + * Flag to mark whether the cipher has been reset + */ + private boolean cipherReset = false; + + public CTRCipherInputStream(Properties props, InputStream in, + byte[] key, byte[] iv) + throws IOException { + this(props, in, key, iv, 0); + } + + public CTRCipherInputStream(Properties props, ReadableByteChannel in, + byte[] key, byte[] iv) + throws IOException { + this(props, in, key, iv, 0); + } + + public CTRCipherInputStream(InputStream in, Cipher cipher, int bufferSize, + byte[] key, byte[] iv) throws IOException { + this(in, cipher, bufferSize, key, iv, 0); + } + + public CTRCipherInputStream(ReadableByteChannel in, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(in, cipher, bufferSize, key, iv, 0); + } + + public CTRCipherInputStream( + Input input, + Cipher cipher, + int bufferSize, + byte[] key, + byte[] iv) throws IOException { + this(input, cipher, bufferSize, key, iv, 0); + } + + public CTRCipherInputStream(Properties props, InputStream in, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(in, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCipherInputStream(Properties props, ReadableByteChannel in, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(in, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCipherInputStream(InputStream in, Cipher cipher, int bufferSize, + byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new StreamInput(in, bufferSize), cipher, bufferSize, key, iv, streamOffset); + } + + public CTRCipherInputStream(ReadableByteChannel in, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new ChannelInput(in), cipher, bufferSize, key, iv, streamOffset); + } + + public CTRCipherInputStream( + Input input, + Cipher cipher, + int bufferSize, + byte[] key, + byte[] iv, + long streamOffset) throws IOException { + super(input, cipher, bufferSize, key, iv); + + Utils.checkStreamCipher(cipher); + + resetStreamOffset(streamOffset); + } + + /** Skip n bytes */ + @Override + public long skip(long n) throws IOException { + Utils.checkArgument(n >= 0, "Negative skip length."); + checkStream(); + + if (n == 0) { + return 0; + } else if (n <= outBuffer.remaining()) { + int pos = outBuffer.position() + (int) n; + outBuffer.position(pos); + return n; + } else { + /* + * Subtract outBuffer.remaining() to see how many bytes we need to + * skip in the underlying stream. Add outBuffer.remaining() to the + * actual number of skipped bytes in the underlying stream to get the + * number of skipped bytes from the user's point of view. + */ + n -= outBuffer.remaining(); + long skipped = input.skip(n); + if (skipped < 0) { + skipped = 0; + } + long pos = streamOffset + skipped; + skipped += outBuffer.remaining(); + resetStreamOffset(pos); + return skipped; + } + } + + /** ByteBuffer read. */ + @Override + public int read(ByteBuffer buf) throws IOException { + checkStream(); + int unread = outBuffer.remaining(); + if (unread <= 0) { // Fill the unread decrypted data buffer firstly + final int n = input.read(inBuffer); + if (n <= 0) { + return n; + } + + streamOffset += n; // Read n bytes + if (buf.isDirect() && buf.remaining() >= inBuffer.position() && padding == 0) { + // Use buf as the output buffer directly + decryptInPlace(buf); + padding = postDecryption(streamOffset); + return n; + } else { + // Use outBuffer as the output buffer + decrypt(); + padding = postDecryption(streamOffset); + } + } + + // Copy decrypted data from outBuffer to buf + unread = outBuffer.remaining(); + final int toRead = buf.remaining(); + if (toRead <= unread) { + final int limit = outBuffer.limit(); + outBuffer.limit(outBuffer.position() + toRead); + buf.put(outBuffer); + outBuffer.limit(limit); + return toRead; + } else { + buf.put(outBuffer); + return unread; + } + } + + /** + * Seek the stream to a specific position relative to start of the under layer stream. + * + * @param position The position to seek to + * @throws IOException if seek failed + */ + public void seek(long position) throws IOException { + Utils.checkArgument(position >= 0, "Cannot seek to negative offset."); + checkStream(); + /* + * If data of target pos in the underlying stream has already been read + * and decrypted in outBuffer, we just need to re-position outBuffer. + */ + if (position >= getStreamPosition() && position <= getStreamOffset()) { + int forward = (int) (position - getStreamPosition()); + if (forward > 0) { + outBuffer.position(outBuffer.position() + forward); + } + } else { + input.seek(position); + resetStreamOffset(position); + } + } + + protected long getStreamOffset() { + return streamOffset; + } + + protected long getStreamPosition() { + return streamOffset - outBuffer.remaining(); + } + + /** + * Decrypt more data by reading the under layer stream. The decrypted data will + * be put in the output buffer. + * + * @return The number of decrypted data. -1 if end of the decrypted stream + */ + protected int decryptMore() throws IOException { + int n = input.read(inBuffer); + if (n <= 0) { + return n; + } + + streamOffset += n; // Read n bytes + decrypt(); + padding = postDecryption(streamOffset); + return outBuffer.remaining(); + } + + /** + * Do 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(); + */ + protected void decrypt() throws IOException { + Utils.checkState(inBuffer.position() >= padding); + if(inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + + inBuffer.flip(); + outBuffer.clear(); + decryptBuffer(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); + } + } + + /** + * Do the decryption using inBuffer as input and buf as output. + * Upon return, inBuffer is cleared; the buf's position will be equal to + * <i>p</i> <tt>+</tt> <i>n</i> where <i>p</i> is the position before + * decryption, <i>n</i> is the number of bytes decrypted. + * The buf's limit will not have changed. + */ + protected void decryptInPlace(ByteBuffer buf) throws IOException { + Utils.checkState(inBuffer.position() >= padding); + Utils.checkState(buf.isDirect()); + Utils.checkState(buf.remaining() >= inBuffer.position()); + Utils.checkState(padding == 0); + + if(inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + inBuffer.flip(); + decryptBuffer(buf); + inBuffer.clear(); + } + + /** + * Decrypt all data in buf: total n bytes from given start position. + * Output is also buf and same start position. + * buf.position() and buf.limit() should be unchanged after decryption. + */ + protected void decrypt(ByteBuffer buf, int offset, int len) + throws IOException { + final int pos = buf.position(); + final int limit = buf.limit(); + int n = 0; + while (n < len) { + buf.position(offset + n); + buf.limit(offset + n + Math.min(len - n, inBuffer.remaining())); + inBuffer.put(buf); + // Do decryption + try { + decrypt(); + buf.position(offset + n); + buf.limit(limit); + n += outBuffer.remaining(); + buf.put(outBuffer); + } finally { + padding = postDecryption(streamOffset - (len - n)); + } + } + buf.position(pos); + } + + /** + * This method is executed immediately after decryption. Check whether + * cipher should be updated and recalculate padding if needed. + */ + protected byte postDecryption(long position) throws IOException { + byte padding = 0; + if (cipherReset) { + /* + * 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(position); + padding = getPadding(position); + inBuffer.position(padding); + } + return padding; + } + + protected long getCounter(long position) { + return position / cipher.getTransformation().getAlgorithmBlockSize(); + } + + protected byte getPadding(long position) { + return (byte)(position % cipher.getTransformation().getAlgorithmBlockSize()); + } + + /** Initialize the cipher. */ + @Override + protected void initCipher() { + // Do nothing for initCipher + // Will reset the cipher when reset the stream offset + } + + /** Calculate the counter and iv, reset the cipher. */ + protected void resetCipher(long position) + throws IOException { + final long counter = getCounter(position); + Utils.calculateIV(initIV, counter, iv); + try { + cipher.init(Cipher.DECRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + cipherReset = false; + } + + /** + * Reset the underlying stream offset; clear {@link #inBuffer} and + * {@link #outBuffer}. This Typically happens during {@link #skip(long)}. + */ + protected void resetStreamOffset(long offset) throws IOException { + streamOffset = offset; + inBuffer.clear(); + outBuffer.clear(); + outBuffer.limit(0); + resetCipher(offset); + padding = getPadding(offset); + inBuffer.position(padding); // Set proper position for input data. + } + + protected void decryptBuffer(ByteBuffer out) + throws IOException { + int inputSize = inBuffer.remaining(); + try { + int n = cipher.update(inBuffer, out); + 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. + */ + cipher.doFinal(inBuffer, out); + cipherReset = true; + } + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } catch (BadPaddingException e) { + throw new IOException(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/src/main/java/org/apache/commons/crypto/stream/CTRCipherOutputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CTRCipherOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CTRCipherOutputStream.java new file mode 100644 index 0000000..13ac256 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CTRCipherOutputStream.java @@ -0,0 +1,230 @@ +/** + * 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.WritableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +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; + +/** + * CTRCipherOutputStream encrypts data. It is not thread-safe. AES CTR mode is + * required in order to ensure that the plain text and cipher text have a 1:1 + * mapping. The encryption is buffer based. The key points of the encryption are + * (1) calculating counter and (2) padding through stream position. + * <p/> + * counter = base + pos/(algorithm blocksize); + * padding = pos%(algorithm blocksize); + * <p/> + * The underlying stream offset is maintained as state. + */ +public class CTRCipherOutputStream extends CipherOutputStream { + /** + * Underlying stream offset. + */ + protected long streamOffset = 0; + + /** + * Padding = pos%(algorithm blocksize); Padding is put into {@link #inBuffer} + * before any other data goes in. The purpose of padding is to put input data + * at proper position. + */ + private byte padding; + + /** + * Flag to mark whether the cipher has been reset + */ + private boolean cipherReset = false; + + public CTRCipherOutputStream(Properties props, OutputStream out, + byte[] key, byte[] iv) + throws IOException { + this(props, out, key, iv, 0); + } + + public CTRCipherOutputStream(Properties props, WritableByteChannel out, + byte[] key, byte[] iv) + throws IOException { + this(props, out, key, iv, 0); + } + + public CTRCipherOutputStream(OutputStream out, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(out, cipher, bufferSize, key, iv, 0); + } + + public CTRCipherOutputStream(WritableByteChannel channel, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(channel, cipher, bufferSize, key, iv, 0); + } + + public CTRCipherOutputStream(Output output, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) + throws IOException { + this(output, cipher, bufferSize, key, iv, 0); + } + + public CTRCipherOutputStream(Properties props, OutputStream out, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(out, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCipherOutputStream(Properties props, WritableByteChannel out, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(out, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCipherOutputStream(OutputStream out, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new StreamOutput(out, bufferSize), cipher, + bufferSize, key, iv, streamOffset); + } + + public CTRCipherOutputStream(WritableByteChannel channel, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new ChannelOutput(channel), cipher, + bufferSize, key, iv, streamOffset); + } + + public CTRCipherOutputStream(Output output, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) + throws IOException { + super(output, cipher, bufferSize, key, iv); + + Utils.checkStreamCipher(cipher); + this.streamOffset = streamOffset; + + resetCipher(); + } + + /** + * Do the encryption, input is {@link #inBuffer} and output is + * {@link #outBuffer}. + */ + @Override + protected void encrypt() throws IOException { + Utils.checkState(inBuffer.position() >= padding); + if (inBuffer.position() == padding) { + // There is no real data in the inBuffer. + return; + } + + inBuffer.flip(); + outBuffer.clear(); + encryptBuffer(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); + padding = 0; + } + + final int len = output.write(outBuffer); + streamOffset += len; + if (cipherReset) { + /* + * This code is generally not executed since the encryptor usually + * maintains encryption context (e.g. the counter) internally. However, + * some implementations can't maintain context so a re-init is necessary + * after each encryption call. + */ + resetCipher(); + } + } + + /** + * Do final encryption of the last data + */ + @Override + protected void encryptFinal() throws IOException { + // The same as the normal encryption for Counter mode + encrypt(); + } + + /** Initialize the cipher. */ + @Override + protected void initCipher() { + // Do nothing for initCipher + // Will reset the cipher considering the stream offset + } + + /** Reset the {@link #cipher}: calculate counter and {@link #padding}. */ + private void resetCipher() throws IOException { + final long counter = + streamOffset / cipher.getTransformation().getAlgorithmBlockSize(); + padding = + (byte)(streamOffset % cipher.getTransformation().getAlgorithmBlockSize()); + inBuffer.position(padding); // Set proper position for input data. + + Utils.calculateIV(initIV, counter, iv); + try { + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + }catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + cipherReset = false; + } + + private void encryptBuffer(ByteBuffer out) + throws IOException { + int inputSize = inBuffer.remaining(); + try { + int n = cipher.update(inBuffer, out); + 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. + */ + cipher.doFinal(inBuffer, out); + cipherReset = true; + } + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (BadPaddingException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java deleted file mode 100644 index fc76f16..0000000 --- a/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java +++ /dev/null @@ -1,423 +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.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.util.Properties; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.ShortBufferException; - -import org.apache.commons.crypto.cipher.Cipher; -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; - -/** - * CTRCryptoInputStream decrypts data. AES CTR mode is required in order to - * ensure that the plain text and cipher text have a 1:1 mapping. CTR crypto - * stream has stream characteristic which is useful for implement features - * like random seek. The decryption is buffer based. The key points of the - * decryption are (1) calculating the counter and (2) padding through stream - * position: - * <p/> - * counter = base + pos/(algorithm blocksize); - * padding = pos%(algorithm blocksize); - * <p/> - * The underlying stream offset is maintained as state. It is not thread-safe. - */ -public class CTRCryptoInputStream extends CryptoInputStream { - /** - * Underlying stream offset - */ - protected long streamOffset = 0; - - /** - * Padding = pos%(algorithm blocksize); Padding is put into {@link #inBuffer} - * before any other data goes in. The purpose of padding is to put the input - * data at proper position. - */ - private byte padding; - - /** - * Flag to mark whether the cipher has been reset - */ - private boolean cipherReset = false; - - public CTRCryptoInputStream(Properties props, InputStream in, - byte[] key, byte[] iv) - throws IOException { - this(props, in, key, iv, 0); - } - - public CTRCryptoInputStream(Properties props, ReadableByteChannel in, - byte[] key, byte[] iv) - throws IOException { - this(props, in, key, iv, 0); - } - - public CTRCryptoInputStream(InputStream in, Cipher cipher, int bufferSize, - byte[] key, byte[] iv) throws IOException { - this(in, cipher, bufferSize, key, iv, 0); - } - - public CTRCryptoInputStream(ReadableByteChannel in, Cipher cipher, - int bufferSize, byte[] key, byte[] iv) throws IOException { - this(in, cipher, bufferSize, key, iv, 0); - } - - public CTRCryptoInputStream( - Input input, - Cipher cipher, - int bufferSize, - byte[] key, - byte[] iv) throws IOException { - this(input, cipher, bufferSize, key, iv, 0); - } - - public CTRCryptoInputStream(Properties props, InputStream in, - byte[] key, byte[] iv, long streamOffset) - throws IOException { - this(in, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), - Utils.getBufferSize(props), key, iv, streamOffset); - } - - public CTRCryptoInputStream(Properties props, ReadableByteChannel in, - byte[] key, byte[] iv, long streamOffset) - throws IOException { - this(in, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), - Utils.getBufferSize(props), key, iv, streamOffset); - } - - public CTRCryptoInputStream(InputStream in, Cipher cipher, int bufferSize, - byte[] key, byte[] iv, long streamOffset) throws IOException { - this(new StreamInput(in, bufferSize), cipher, bufferSize, key, iv, streamOffset); - } - - public CTRCryptoInputStream(ReadableByteChannel in, Cipher cipher, - int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { - this(new ChannelInput(in), cipher, bufferSize, key, iv, streamOffset); - } - - public CTRCryptoInputStream( - Input input, - Cipher cipher, - int bufferSize, - byte[] key, - byte[] iv, - long streamOffset) throws IOException { - super(input, cipher, bufferSize, key, iv); - - Utils.checkStreamCipher(cipher); - - resetStreamOffset(streamOffset); - } - - /** Skip n bytes */ - @Override - public long skip(long n) throws IOException { - Utils.checkArgument(n >= 0, "Negative skip length."); - checkStream(); - - if (n == 0) { - return 0; - } else if (n <= outBuffer.remaining()) { - int pos = outBuffer.position() + (int) n; - outBuffer.position(pos); - return n; - } else { - /* - * Subtract outBuffer.remaining() to see how many bytes we need to - * skip in the underlying stream. Add outBuffer.remaining() to the - * actual number of skipped bytes in the underlying stream to get the - * number of skipped bytes from the user's point of view. - */ - n -= outBuffer.remaining(); - long skipped = input.skip(n); - if (skipped < 0) { - skipped = 0; - } - long pos = streamOffset + skipped; - skipped += outBuffer.remaining(); - resetStreamOffset(pos); - return skipped; - } - } - - /** ByteBuffer read. */ - @Override - public int read(ByteBuffer buf) throws IOException { - checkStream(); - int unread = outBuffer.remaining(); - if (unread <= 0) { // Fill the unread decrypted data buffer firstly - final int n = input.read(inBuffer); - if (n <= 0) { - return n; - } - - streamOffset += n; // Read n bytes - if (buf.isDirect() && buf.remaining() >= inBuffer.position() && padding == 0) { - // Use buf as the output buffer directly - decryptInPlace(buf); - padding = postDecryption(streamOffset); - return n; - } else { - // Use outBuffer as the output buffer - decrypt(); - padding = postDecryption(streamOffset); - } - } - - // Copy decrypted data from outBuffer to buf - unread = outBuffer.remaining(); - final int toRead = buf.remaining(); - if (toRead <= unread) { - final int limit = outBuffer.limit(); - outBuffer.limit(outBuffer.position() + toRead); - buf.put(outBuffer); - outBuffer.limit(limit); - return toRead; - } else { - buf.put(outBuffer); - return unread; - } - } - - /** - * Seek the stream to a specific position relative to start of the under layer stream. - * - * @param position The position to seek to - * @throws IOException if seek failed - */ - public void seek(long position) throws IOException { - Utils.checkArgument(position >= 0, "Cannot seek to negative offset."); - checkStream(); - /* - * If data of target pos in the underlying stream has already been read - * and decrypted in outBuffer, we just need to re-position outBuffer. - */ - if (position >= getStreamPosition() && position <= getStreamOffset()) { - int forward = (int) (position - getStreamPosition()); - if (forward > 0) { - outBuffer.position(outBuffer.position() + forward); - } - } else { - input.seek(position); - resetStreamOffset(position); - } - } - - protected long getStreamOffset() { - return streamOffset; - } - - protected long getStreamPosition() { - return streamOffset - outBuffer.remaining(); - } - - /** - * Decrypt more data by reading the under layer stream. The decrypted data will - * be put in the output buffer. - * - * @return The number of decrypted data. -1 if end of the decrypted stream - */ - protected int decryptMore() throws IOException { - int n = input.read(inBuffer); - if (n <= 0) { - return n; - } - - streamOffset += n; // Read n bytes - decrypt(); - padding = postDecryption(streamOffset); - return outBuffer.remaining(); - } - - /** - * Do 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(); - */ - protected void decrypt() throws IOException { - Utils.checkState(inBuffer.position() >= padding); - if(inBuffer.position() == padding) { - // There is no real data in inBuffer. - return; - } - - inBuffer.flip(); - outBuffer.clear(); - decryptBuffer(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); - } - } - - /** - * Do the decryption using inBuffer as input and buf as output. - * Upon return, inBuffer is cleared; the buf's position will be equal to - * <i>p</i> <tt>+</tt> <i>n</i> where <i>p</i> is the position before - * decryption, <i>n</i> is the number of bytes decrypted. - * The buf's limit will not have changed. - */ - protected void decryptInPlace(ByteBuffer buf) throws IOException { - Utils.checkState(inBuffer.position() >= padding); - Utils.checkState(buf.isDirect()); - Utils.checkState(buf.remaining() >= inBuffer.position()); - Utils.checkState(padding == 0); - - if(inBuffer.position() == padding) { - // There is no real data in inBuffer. - return; - } - inBuffer.flip(); - decryptBuffer(buf); - inBuffer.clear(); - } - - /** - * Decrypt all data in buf: total n bytes from given start position. - * Output is also buf and same start position. - * buf.position() and buf.limit() should be unchanged after decryption. - */ - protected void decrypt(ByteBuffer buf, int offset, int len) - throws IOException { - final int pos = buf.position(); - final int limit = buf.limit(); - int n = 0; - while (n < len) { - buf.position(offset + n); - buf.limit(offset + n + Math.min(len - n, inBuffer.remaining())); - inBuffer.put(buf); - // Do decryption - try { - decrypt(); - buf.position(offset + n); - buf.limit(limit); - n += outBuffer.remaining(); - buf.put(outBuffer); - } finally { - padding = postDecryption(streamOffset - (len - n)); - } - } - buf.position(pos); - } - - /** - * This method is executed immediately after decryption. Check whether - * cipher should be updated and recalculate padding if needed. - */ - protected byte postDecryption(long position) throws IOException { - byte padding = 0; - if (cipherReset) { - /* - * 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(position); - padding = getPadding(position); - inBuffer.position(padding); - } - return padding; - } - - protected long getCounter(long position) { - return position / cipher.getTransformation().getAlgorithmBlockSize(); - } - - protected byte getPadding(long position) { - return (byte)(position % cipher.getTransformation().getAlgorithmBlockSize()); - } - - /** Initialize the cipher. */ - @Override - protected void initCipher() { - // Do nothing for initCipher - // Will reset the cipher when reset the stream offset - } - - /** Calculate the counter and iv, reset the cipher. */ - protected void resetCipher(long position) - throws IOException { - final long counter = getCounter(position); - Utils.calculateIV(initIV, counter, iv); - try { - cipher.init(Cipher.DECRYPT_MODE, key, iv); - } catch (InvalidKeyException e) { - throw new IOException(e); - } catch (InvalidAlgorithmParameterException e) { - throw new IOException(e); - } - cipherReset = false; - } - - /** - * Reset the underlying stream offset; clear {@link #inBuffer} and - * {@link #outBuffer}. This Typically happens during {@link #skip(long)}. - */ - protected void resetStreamOffset(long offset) throws IOException { - streamOffset = offset; - inBuffer.clear(); - outBuffer.clear(); - outBuffer.limit(0); - resetCipher(offset); - padding = getPadding(offset); - inBuffer.position(padding); // Set proper position for input data. - } - - protected void decryptBuffer(ByteBuffer out) - throws IOException { - int inputSize = inBuffer.remaining(); - try { - int n = cipher.update(inBuffer, out); - 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. - */ - cipher.doFinal(inBuffer, out); - cipherReset = true; - } - } catch (ShortBufferException e) { - throw new IOException(e); - } catch (IllegalBlockSizeException e) { - throw new IOException(e); - } catch (BadPaddingException e) { - throw new IOException(e); - } - } - -} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java deleted file mode 100644 index cb282e3..0000000 --- a/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java +++ /dev/null @@ -1,230 +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.WritableByteChannel; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.util.Properties; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.ShortBufferException; - -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; - -/** - * CTRCryptoOutputStream encrypts data. It is not thread-safe. AES CTR mode is - * required in order to ensure that the plain text and cipher text have a 1:1 - * mapping. The encryption is buffer based. The key points of the encryption are - * (1) calculating counter and (2) padding through stream position. - * <p/> - * counter = base + pos/(algorithm blocksize); - * padding = pos%(algorithm blocksize); - * <p/> - * The underlying stream offset is maintained as state. - */ -public class CTRCryptoOutputStream extends CryptoOutputStream { - /** - * Underlying stream offset. - */ - protected long streamOffset = 0; - - /** - * Padding = pos%(algorithm blocksize); Padding is put into {@link #inBuffer} - * before any other data goes in. The purpose of padding is to put input data - * at proper position. - */ - private byte padding; - - /** - * Flag to mark whether the cipher has been reset - */ - private boolean cipherReset = false; - - public CTRCryptoOutputStream(Properties props, OutputStream out, - byte[] key, byte[] iv) - throws IOException { - this(props, out, key, iv, 0); - } - - public CTRCryptoOutputStream(Properties props, WritableByteChannel out, - byte[] key, byte[] iv) - throws IOException { - this(props, out, key, iv, 0); - } - - public CTRCryptoOutputStream(OutputStream out, Cipher cipher, - int bufferSize, byte[] key, byte[] iv) throws IOException { - this(out, cipher, bufferSize, key, iv, 0); - } - - public CTRCryptoOutputStream(WritableByteChannel channel, Cipher cipher, - int bufferSize, byte[] key, byte[] iv) throws IOException { - this(channel, cipher, bufferSize, key, iv, 0); - } - - public CTRCryptoOutputStream(Output output, Cipher cipher, - int bufferSize, byte[] key, byte[] iv) - throws IOException { - this(output, cipher, bufferSize, key, iv, 0); - } - - public CTRCryptoOutputStream(Properties props, OutputStream out, - byte[] key, byte[] iv, long streamOffset) - throws IOException { - this(out, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), - Utils.getBufferSize(props), key, iv, streamOffset); - } - - public CTRCryptoOutputStream(Properties props, WritableByteChannel out, - byte[] key, byte[] iv, long streamOffset) - throws IOException { - this(out, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), - Utils.getBufferSize(props), key, iv, streamOffset); - } - - public CTRCryptoOutputStream(OutputStream out, Cipher cipher, - int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { - this(new StreamOutput(out, bufferSize), cipher, - bufferSize, key, iv, streamOffset); - } - - public CTRCryptoOutputStream(WritableByteChannel channel, Cipher cipher, - int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { - this(new ChannelOutput(channel), cipher, - bufferSize, key, iv, streamOffset); - } - - public CTRCryptoOutputStream(Output output, Cipher cipher, - int bufferSize, byte[] key, byte[] iv, long streamOffset) - throws IOException { - super(output, cipher, bufferSize, key, iv); - - Utils.checkStreamCipher(cipher); - this.streamOffset = streamOffset; - - resetCipher(); - } - - /** - * Do the encryption, input is {@link #inBuffer} and output is - * {@link #outBuffer}. - */ - @Override - protected void encrypt() throws IOException { - Utils.checkState(inBuffer.position() >= padding); - if (inBuffer.position() == padding) { - // There is no real data in the inBuffer. - return; - } - - inBuffer.flip(); - outBuffer.clear(); - encryptBuffer(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); - padding = 0; - } - - final int len = output.write(outBuffer); - streamOffset += len; - if (cipherReset) { - /* - * This code is generally not executed since the encryptor usually - * maintains encryption context (e.g. the counter) internally. However, - * some implementations can't maintain context so a re-init is necessary - * after each encryption call. - */ - resetCipher(); - } - } - - /** - * Do final encryption of the last data - */ - @Override - protected void encryptFinal() throws IOException { - // The same as the normal encryption for Counter mode - encrypt(); - } - - /** Initialize the cipher. */ - @Override - protected void initCipher() { - // Do nothing for initCipher - // Will reset the cipher considering the stream offset - } - - /** Reset the {@link #cipher}: calculate counter and {@link #padding}. */ - private void resetCipher() throws IOException { - final long counter = - streamOffset / cipher.getTransformation().getAlgorithmBlockSize(); - padding = - (byte)(streamOffset % cipher.getTransformation().getAlgorithmBlockSize()); - inBuffer.position(padding); // Set proper position for input data. - - Utils.calculateIV(initIV, counter, iv); - try { - cipher.init(Cipher.ENCRYPT_MODE, key, iv); - } catch (InvalidKeyException e) { - throw new IOException(e); - }catch (InvalidAlgorithmParameterException e) { - throw new IOException(e); - } - cipherReset = false; - } - - private void encryptBuffer(ByteBuffer out) - throws IOException { - int inputSize = inBuffer.remaining(); - try { - int n = cipher.update(inBuffer, out); - 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. - */ - cipher.doFinal(inBuffer, out); - cipherReset = true; - } - } catch (ShortBufferException e) { - throw new IOException(e); - } catch (BadPaddingException e) { - throw new IOException(e); - } catch (IllegalBlockSizeException e) { - throw new IOException(e); - } - } -} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/src/main/java/org/apache/commons/crypto/stream/CipherInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CipherInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CipherInputStream.java new file mode 100644 index 0000000..47a1c4b --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CipherInputStream.java @@ -0,0 +1,394 @@ +/** + * 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.ReadableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +import org.apache.commons.crypto.cipher.Cipher; +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; + +/** + * CipherInputStream 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 CipherInputStream extends InputStream implements + ReadableByteChannel { + private final byte[] oneByteBuf = new byte[1]; + + protected final Cipher cipher; + protected final int bufferSize; + + protected final byte[] key; + protected final byte[] initIV; + protected byte[] iv; + + protected boolean closed; + protected boolean finalDone = false; + + protected 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; + + public CipherInputStream(CipherTransformation transformation, + Properties props, InputStream in, byte[] key, byte[] iv) + throws IOException { + this(in, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CipherInputStream(CipherTransformation transformation, + Properties props, ReadableByteChannel in, byte[] key, byte[] iv) + throws IOException { + this(in, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CipherInputStream(InputStream in, Cipher cipher, int bufferSize, + byte[] key, byte[] iv) throws IOException { + this(new StreamInput(in, bufferSize), cipher, bufferSize, key, iv); + } + + public CipherInputStream(ReadableByteChannel in, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(new ChannelInput(in), cipher, bufferSize, key, iv); + } + + public CipherInputStream( + Input input, + Cipher cipher, + int bufferSize, + byte[] key, + byte[] iv) throws IOException { + this.input = input; + this.cipher = cipher; + this.bufferSize = Utils.checkBufferSize(cipher, bufferSize); + this.key = key.clone(); + this.initIV = iv.clone(); + this.iv = iv.clone(); + + inBuffer = ByteBuffer.allocateDirect(this.bufferSize); + outBuffer = ByteBuffer.allocateDirect(this.bufferSize + + cipher.getTransformation().getAlgorithmBlockSize()); + outBuffer.limit(0); + + initCipher(); + } + + @Override + public int read() throws IOException { + int n; + while ((n = read(oneByteBuf, 0, 1)) == 0) ; + return (n == -1) ? -1 : oneByteBuf[0] & 0xff; + } + + /** + * 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 + */ + @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; + } + } + + @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; + } + + @Override + public int available() throws IOException { + checkStream(); + + return input.available() + outBuffer.remaining(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + input.close(); + freeBuffers(); + cipher.close(); + super.close(); + closed = true; + } + + @Override + public void mark(int readlimit) { + } + + @Override + public void reset() throws IOException { + throw new IOException("Mark/reset not supported"); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public boolean isOpen() { + return !closed; + } + + @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; + } + } + + /** + * Get the buffer size + */ + protected int getBufferSize() { + return bufferSize; + } + + /** + * Get the key + */ + protected byte[] getKey() { + return key; + } + + /** + * Get the initialization vector + */ + protected byte[] getInitIV() { + return initIV; + } + + /** + * Get the internal Cipher + */ + protected Cipher getCipher() { + return cipher; + } + + /** Initialize the cipher. */ + protected void initCipher() + throws IOException { + try { + cipher.init(Cipher.DECRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch(InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + } + + /** + * Decrypt 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(); + } + } + + /** + * Do 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(); + */ + 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(); + } + + /** + * Do final of the cipher to end the decrypting stream + */ + 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(); + } + + 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); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/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 new file mode 100644 index 0000000..b0a0f96 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CipherOutputStream.java @@ -0,0 +1,283 @@ +/** + * 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.WritableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +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; + +/** + * 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]; + + protected Output output; + protected final Cipher cipher; + protected final int bufferSize; + + protected final byte[] key; + protected final byte[] initIV; + protected byte[] iv; + + protected boolean closed; + + /** + * Input data buffer. The data starts at inBuffer.position() and ends at + * inBuffer.limit(). + */ + protected ByteBuffer inBuffer; + + /** + * Encrypted data buffer. The data starts at outBuffer.position() and ends at + * outBuffer.limit(); + */ + protected ByteBuffer outBuffer; + + public CipherOutputStream(CipherTransformation transformation, + Properties props, OutputStream out, byte[] key, byte[] iv) + throws IOException { + this(out, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CipherOutputStream(CipherTransformation transformation, + Properties props, WritableByteChannel out, byte[] key, byte[] iv) + throws IOException { + this(out, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CipherOutputStream(OutputStream out, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(new StreamOutput(out, bufferSize), cipher, bufferSize, key, iv); + } + + public CipherOutputStream(WritableByteChannel channel, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(new ChannelOutput(channel), cipher, bufferSize, key, iv); + } + + protected CipherOutputStream(Output output, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) + throws IOException { + + this.output = output; + this.bufferSize = Utils.checkBufferSize(cipher, bufferSize); + this.cipher = cipher; + this.key = key.clone(); + this.initIV = iv.clone(); + this.iv = iv.clone(); + inBuffer = ByteBuffer.allocateDirect(this.bufferSize); + outBuffer = ByteBuffer.allocateDirect(this.bufferSize + + cipher.getTransformation().getAlgorithmBlockSize()); + + initCipher(); + } + + @Override + public void write(int b) throws IOException { + oneByteBuf[0] = (byte)(b & 0xff); + write(oneByteBuf, 0, oneByteBuf.length); + } + + /** + * 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 + */ + 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(); + } + } + } + + /** + * To flush, we need to encrypt the data in the buffer and write to the + * underlying stream, then do the flush. + */ + @Override + public void flush() throws IOException { + checkStream(); + encrypt(); + output.flush(); + super.flush(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + try { + encryptFinal(); + output.close(); + freeBuffers(); + cipher.close(); + super.close(); + } finally { + closed = true; + } + } + + @Override + public boolean isOpen() { + return !closed; + } + + @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; + } + + /** Initialize the cipher. */ + protected void initCipher() + throws IOException { + try { + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch(InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + } + + /** + * Do the encryption, input is {@link #inBuffer} and output is + * {@link #outBuffer}. + */ + 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); + } + + /** + * Do final encryption of the last data + */ + 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); + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ad81d236/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 deleted file mode 100644 index 6498273..0000000 --- a/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java +++ /dev/null @@ -1,394 +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.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.util.Properties; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.ShortBufferException; - -import org.apache.commons.crypto.cipher.Cipher; -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]; - - protected final Cipher cipher; - protected final int bufferSize; - - protected final byte[] key; - protected final byte[] initIV; - protected byte[] iv; - - protected boolean closed; - protected boolean finalDone = false; - - protected 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; - - public CryptoInputStream(CipherTransformation transformation, - Properties props, InputStream in, byte[] key, byte[] iv) - throws IOException { - this(in, Utils.getCipherInstance(transformation, props), - Utils.getBufferSize(props), key, iv); - } - - public CryptoInputStream(CipherTransformation transformation, - Properties props, ReadableByteChannel in, byte[] key, byte[] iv) - throws IOException { - this(in, Utils.getCipherInstance(transformation, props), - Utils.getBufferSize(props), key, iv); - } - - public CryptoInputStream(InputStream in, Cipher cipher, int bufferSize, - byte[] key, byte[] iv) throws IOException { - this(new StreamInput(in, bufferSize), cipher, bufferSize, key, iv); - } - - public CryptoInputStream(ReadableByteChannel in, Cipher cipher, - int bufferSize, byte[] key, byte[] iv) throws IOException { - this(new ChannelInput(in), cipher, bufferSize, key, iv); - } - - public CryptoInputStream( - Input input, - Cipher cipher, - int bufferSize, - byte[] key, - byte[] iv) throws IOException { - this.input = input; - this.cipher = cipher; - this.bufferSize = Utils.checkBufferSize(cipher, bufferSize); - this.key = key.clone(); - this.initIV = iv.clone(); - this.iv = iv.clone(); - - inBuffer = ByteBuffer.allocateDirect(this.bufferSize); - outBuffer = ByteBuffer.allocateDirect(this.bufferSize + - cipher.getTransformation().getAlgorithmBlockSize()); - outBuffer.limit(0); - - initCipher(); - } - - @Override - public int read() throws IOException { - int n; - while ((n = read(oneByteBuf, 0, 1)) == 0) ; - return (n == -1) ? -1 : oneByteBuf[0] & 0xff; - } - - /** - * 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 - */ - @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; - } - } - - @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; - } - - @Override - public int available() throws IOException { - checkStream(); - - return input.available() + outBuffer.remaining(); - } - - @Override - public void close() throws IOException { - if (closed) { - return; - } - - input.close(); - freeBuffers(); - cipher.close(); - super.close(); - closed = true; - } - - @Override - public void mark(int readlimit) { - } - - @Override - public void reset() throws IOException { - throw new IOException("Mark/reset not supported"); - } - - @Override - public boolean markSupported() { - return false; - } - - @Override - public boolean isOpen() { - return !closed; - } - - @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; - } - } - - /** - * Get the buffer size - */ - protected int getBufferSize() { - return bufferSize; - } - - /** - * Get the key - */ - protected byte[] getKey() { - return key; - } - - /** - * Get the initialization vector - */ - protected byte[] getInitIV() { - return initIV; - } - - /** - * Get the internal Cipher - */ - protected Cipher getCipher() { - return cipher; - } - - /** Initialize the cipher. */ - protected void initCipher() - throws IOException { - try { - cipher.init(Cipher.DECRYPT_MODE, key, iv); - } catch (InvalidKeyException e) { - throw new IOException(e); - } catch(InvalidAlgorithmParameterException e) { - throw new IOException(e); - } - } - - /** - * Decrypt 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(); - } - } - - /** - * Do 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(); - */ - 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(); - } - - /** - * Do final of the cipher to end the decrypting stream - */ - 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(); - } - - 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); - } -} \ No newline at end of file