package org.spongycastle.crypto.io; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import org.spongycastle.crypto.BufferedBlockCipher; import org.spongycastle.crypto.StreamCipher; /** * A CipherInputStream is composed of an InputStream and a BufferedBlockCipher so * that read() methods return data that are read in from the * underlying InputStream but have been additionally processed by the * Cipher. The Cipher must be fully initialized before being used by * a CipherInputStream. * <p> * For example, if the Cipher is initialized for decryption, the * CipherInputStream will attempt to read in data and decrypt them, * before returning the decrypted data. */ public class CipherInputStream extends FilterInputStream { private BufferedBlockCipher bufferedBlockCipher; private StreamCipher streamCipher; private byte[] buf; private byte[] inBuf; private int bufOff; private int maxBuf; private boolean finalized; private static final int INPUT_BUF_SIZE = 2048; /** * Constructs a CipherInputStream from an InputStream and a * BufferedBlockCipher. */ public CipherInputStream( InputStream is, BufferedBlockCipher cipher) { super(is); this.bufferedBlockCipher = cipher; buf = new byte[cipher.getOutputSize(INPUT_BUF_SIZE)]; inBuf = new byte[INPUT_BUF_SIZE]; } public CipherInputStream( InputStream is, StreamCipher cipher) { super(is); this.streamCipher = cipher; buf = new byte[INPUT_BUF_SIZE]; inBuf = new byte[INPUT_BUF_SIZE]; } /** * grab the next chunk of input from the underlying input stream */ private int nextChunk() throws IOException { int available = super.available(); // must always try to read 1 byte! // some buggy InputStreams return < 0! if (available <= 0) { available = 1; } if (available > inBuf.length) { available = super.read(inBuf, 0, inBuf.length); } else { available = super.read(inBuf, 0, available); } if (available < 0) { if (finalized) { return -1; } try { if (bufferedBlockCipher != null) { maxBuf = bufferedBlockCipher.doFinal(buf, 0); } else { maxBuf = 0; // a stream cipher } } catch (Exception e) { throw new IOException("error processing stream: " + e.toString()); } bufOff = 0; finalized = true; if (bufOff == maxBuf) { return -1; } } else { bufOff = 0; try { if (bufferedBlockCipher != null) { maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, available, buf, 0); } else { streamCipher.processBytes(inBuf, 0, available, buf, 0); maxBuf = available; } } catch (Exception e) { throw new IOException("error processing stream: " + e.toString()); } if (maxBuf == 0) // not enough bytes read for first block... { return nextChunk(); } } return maxBuf; } public int read() throws IOException { if (bufOff == maxBuf) { if (nextChunk() < 0) { return -1; } } return buf[bufOff++] & 0xff; } public int read( byte[] b) throws IOException { return read(b, 0, b.length); } public int read( byte[] b, int off, int len) throws IOException { if (bufOff == maxBuf) { if (nextChunk() < 0) { return -1; } } int available = maxBuf - bufOff; if (len > available) { System.arraycopy(buf, bufOff, b, off, available); bufOff = maxBuf; return available; } else { System.arraycopy(buf, bufOff, b, off, len); bufOff += len; return len; } } public long skip( long n) throws IOException { if (n <= 0) { return 0; } int available = maxBuf - bufOff; if (n > available) { bufOff = maxBuf; return available; } else { bufOff += (int)n; return (int)n; } } public int available() throws IOException { return maxBuf - bufOff; } public void close() throws IOException { super.close(); } public boolean markSupported() { return false; } }