001: package org.bouncycastle.crypto;
002:
003: /**
004: * A wrapper class that allows block ciphers to be used to process data in
005: * a piecemeal fashion. The BufferedBlockCipher outputs a block only when the
006: * buffer is full and more data is being added, or on a doFinal.
007: * <p>
008: * Note: in the case where the underlying cipher is either a CFB cipher or an
009: * OFB one the last block may not be a multiple of the block size.
010: */
011: public class BufferedBlockCipher {
012: protected byte[] buf;
013: protected int bufOff;
014:
015: protected boolean forEncryption;
016: protected BlockCipher cipher;
017:
018: protected boolean partialBlockOkay;
019: protected boolean pgpCFB;
020:
021: /**
022: * constructor for subclasses
023: */
024: protected BufferedBlockCipher() {
025: }
026:
027: /**
028: * Create a buffered block cipher without padding.
029: *
030: * @param cipher the underlying block cipher this buffering object wraps.
031: */
032: public BufferedBlockCipher(BlockCipher cipher) {
033: this .cipher = cipher;
034:
035: buf = new byte[cipher.getBlockSize()];
036: bufOff = 0;
037:
038: //
039: // check if we can handle partial blocks on doFinal.
040: //
041: String name = cipher.getAlgorithmName();
042: int idx = name.indexOf('/') + 1;
043:
044: pgpCFB = (idx > 0 && name.startsWith("PGP", idx));
045:
046: if (pgpCFB) {
047: partialBlockOkay = true;
048: } else {
049: partialBlockOkay = (idx > 0 && (name.startsWith("CFB", idx)
050: || name.startsWith("OFB", idx)
051: || name.startsWith("OpenPGP", idx)
052: || name.startsWith("SIC", idx) || name.startsWith(
053: "GCTR", idx)));
054: }
055: }
056:
057: /**
058: * return the cipher this object wraps.
059: *
060: * @return the cipher this object wraps.
061: */
062: public BlockCipher getUnderlyingCipher() {
063: return cipher;
064: }
065:
066: /**
067: * initialise the cipher.
068: *
069: * @param forEncryption if true the cipher is initialised for
070: * encryption, if false for decryption.
071: * @param params the key and other data required by the cipher.
072: * @exception IllegalArgumentException if the params argument is
073: * inappropriate.
074: */
075: public void init(boolean forEncryption, CipherParameters params)
076: throws IllegalArgumentException {
077: this .forEncryption = forEncryption;
078:
079: reset();
080:
081: cipher.init(forEncryption, params);
082: }
083:
084: /**
085: * return the blocksize for the underlying cipher.
086: *
087: * @return the blocksize for the underlying cipher.
088: */
089: public int getBlockSize() {
090: return cipher.getBlockSize();
091: }
092:
093: /**
094: * return the size of the output buffer required for an update
095: * an input of len bytes.
096: *
097: * @param len the length of the input.
098: * @return the space required to accommodate a call to update
099: * with len bytes of input.
100: */
101: public int getUpdateOutputSize(int len) {
102: int total = len + bufOff;
103: int leftOver;
104:
105: if (pgpCFB) {
106: leftOver = total % buf.length - (cipher.getBlockSize() + 2);
107: } else {
108: leftOver = total % buf.length;
109: }
110:
111: return total - leftOver;
112: }
113:
114: /**
115: * return the size of the output buffer required for an update plus a
116: * doFinal with an input of len bytes.
117: *
118: * @param len the length of the input.
119: * @return the space required to accommodate a call to update and doFinal
120: * with len bytes of input.
121: */
122: public int getOutputSize(int len) {
123: int total = len + bufOff;
124: int leftOver;
125:
126: if (pgpCFB) {
127: leftOver = total % buf.length - (cipher.getBlockSize() + 2);
128: } else {
129: leftOver = total % buf.length;
130: if (leftOver == 0) {
131: return total;
132: }
133: }
134:
135: return total - leftOver + buf.length;
136: }
137:
138: /**
139: * process a single byte, producing an output block if neccessary.
140: *
141: * @param in the input byte.
142: * @param out the space for any output that might be produced.
143: * @param outOff the offset from which the output will be copied.
144: * @return the number of output bytes copied to out.
145: * @exception DataLengthException if there isn't enough space in out.
146: * @exception IllegalStateException if the cipher isn't initialised.
147: */
148: public int processByte(byte in, byte[] out, int outOff)
149: throws DataLengthException, IllegalStateException {
150: int resultLen = 0;
151:
152: buf[bufOff++] = in;
153:
154: if (bufOff == buf.length) {
155: resultLen = cipher.processBlock(buf, 0, out, outOff);
156: bufOff = 0;
157: }
158:
159: return resultLen;
160: }
161:
162: /**
163: * process an array of bytes, producing output if necessary.
164: *
165: * @param in the input byte array.
166: * @param inOff the offset at which the input data starts.
167: * @param len the number of bytes to be copied out of the input array.
168: * @param out the space for any output that might be produced.
169: * @param outOff the offset from which the output will be copied.
170: * @return the number of output bytes copied to out.
171: * @exception DataLengthException if there isn't enough space in out.
172: * @exception IllegalStateException if the cipher isn't initialised.
173: */
174: public int processBytes(byte[] in, int inOff, int len, byte[] out,
175: int outOff) throws DataLengthException,
176: IllegalStateException {
177: if (len < 0) {
178: throw new IllegalArgumentException(
179: "Can't have a negative input length!");
180: }
181:
182: int blockSize = getBlockSize();
183: int length = getUpdateOutputSize(len);
184:
185: if (length > 0) {
186: if ((outOff + length) > out.length) {
187: throw new DataLengthException("output buffer too short");
188: }
189: }
190:
191: int resultLen = 0;
192: int gapLen = buf.length - bufOff;
193:
194: if (len > gapLen) {
195: System.arraycopy(in, inOff, buf, bufOff, gapLen);
196:
197: resultLen += cipher.processBlock(buf, 0, out, outOff);
198:
199: bufOff = 0;
200: len -= gapLen;
201: inOff += gapLen;
202:
203: while (len > buf.length) {
204: resultLen += cipher.processBlock(in, inOff, out, outOff
205: + resultLen);
206:
207: len -= blockSize;
208: inOff += blockSize;
209: }
210: }
211:
212: System.arraycopy(in, inOff, buf, bufOff, len);
213:
214: bufOff += len;
215:
216: if (bufOff == buf.length) {
217: resultLen += cipher.processBlock(buf, 0, out, outOff
218: + resultLen);
219: bufOff = 0;
220: }
221:
222: return resultLen;
223: }
224:
225: /**
226: * Process the last block in the buffer.
227: *
228: * @param out the array the block currently being held is copied into.
229: * @param outOff the offset at which the copying starts.
230: * @return the number of output bytes copied to out.
231: * @exception DataLengthException if there is insufficient space in out for
232: * the output, or the input is not block size aligned and should be.
233: * @exception IllegalStateException if the underlying cipher is not
234: * initialised.
235: * @exception InvalidCipherTextException if padding is expected and not found.
236: * @exception DataLengthException if the input is not block size
237: * aligned.
238: */
239: public int doFinal(byte[] out, int outOff)
240: throws DataLengthException, IllegalStateException,
241: InvalidCipherTextException {
242: int resultLen = 0;
243:
244: if (outOff + bufOff > out.length) {
245: throw new DataLengthException(
246: "output buffer too short for doFinal()");
247: }
248:
249: if (bufOff != 0 && partialBlockOkay) {
250: cipher.processBlock(buf, 0, buf, 0);
251: resultLen = bufOff;
252: bufOff = 0;
253: System.arraycopy(buf, 0, out, outOff, resultLen);
254: } else if (bufOff != 0) {
255: throw new DataLengthException("data not block size aligned");
256: }
257:
258: reset();
259:
260: return resultLen;
261: }
262:
263: /**
264: * Reset the buffer and cipher. After resetting the object is in the same
265: * state as it was after the last init (if there was one).
266: */
267: public void reset() {
268: //
269: // clean the buffer.
270: //
271: for (int i = 0; i < buf.length; i++) {
272: buf[i] = 0;
273: }
274:
275: bufOff = 0;
276:
277: //
278: // reset the underlying cipher.
279: //
280: cipher.reset();
281: }
282: }
|