1package org.bouncycastle.crypto;
2
3
4/**
5 * A wrapper class that allows block ciphers to be used to process data in
6 * a piecemeal fashion. The BufferedBlockCipher outputs a block only when the
7 * buffer is full and more data is being added, or on a doFinal.
8 * <p>
9 * Note: in the case where the underlying cipher is either a CFB cipher or an
10 * OFB one the last block may not be a multiple of the block size.
11 */
12public class BufferedBlockCipher
13{
14    protected byte[]        buf;
15    protected int           bufOff;
16
17    protected boolean       forEncryption;
18    protected BlockCipher   cipher;
19
20    protected boolean       partialBlockOkay;
21    protected boolean       pgpCFB;
22
23    /**
24     * constructor for subclasses
25     */
26    protected BufferedBlockCipher()
27    {
28    }
29
30    /**
31     * Create a buffered block cipher without padding.
32     *
33     * @param cipher the underlying block cipher this buffering object wraps.
34     */
35    public BufferedBlockCipher(
36        BlockCipher     cipher)
37    {
38        this.cipher = cipher;
39
40        buf = new byte[cipher.getBlockSize()];
41        bufOff = 0;
42
43        //
44        // check if we can handle partial blocks on doFinal.
45        //
46        String  name = cipher.getAlgorithmName();
47        int     idx = name.indexOf('/') + 1;
48
49        pgpCFB = (idx > 0 && name.startsWith("PGP", idx));
50
51        if (pgpCFB || cipher instanceof StreamCipher)
52        {
53            partialBlockOkay = true;
54        }
55        else
56        {
57            partialBlockOkay = (idx > 0 && (name.startsWith("OpenPGP", idx)));
58        }
59    }
60
61    /**
62     * return the cipher this object wraps.
63     *
64     * @return the cipher this object wraps.
65     */
66    public BlockCipher getUnderlyingCipher()
67    {
68        return cipher;
69    }
70
71    /**
72     * initialise the cipher.
73     *
74     * @param forEncryption if true the cipher is initialised for
75     *  encryption, if false for decryption.
76     * @param params the key and other data required by the cipher.
77     * @exception IllegalArgumentException if the params argument is
78     * inappropriate.
79     */
80    public void init(
81        boolean             forEncryption,
82        CipherParameters    params)
83        throws IllegalArgumentException
84    {
85        this.forEncryption = forEncryption;
86
87        reset();
88
89        cipher.init(forEncryption, params);
90    }
91
92    /**
93     * return the blocksize for the underlying cipher.
94     *
95     * @return the blocksize for the underlying cipher.
96     */
97    public int getBlockSize()
98    {
99        return cipher.getBlockSize();
100    }
101
102    /**
103     * return the size of the output buffer required for an update
104     * an input of len bytes.
105     *
106     * @param len the length of the input.
107     * @return the space required to accommodate a call to update
108     * with len bytes of input.
109     */
110    public int getUpdateOutputSize(
111        int len)
112    {
113        int total       = len + bufOff;
114        int leftOver;
115
116        if (pgpCFB)
117        {
118            if (forEncryption)
119            {
120                leftOver = total % buf.length - (cipher.getBlockSize() + 2);
121            }
122            else
123            {
124                leftOver = total % buf.length;
125            }
126        }
127        else
128        {
129            leftOver    = total % buf.length;
130        }
131
132        return total - leftOver;
133    }
134
135    /**
136     * return the size of the output buffer required for an update plus a
137     * doFinal with an input of 'length' bytes.
138     *
139     * @param length the length of the input.
140     * @return the space required to accommodate a call to update and doFinal
141     * with 'length' bytes of input.
142     */
143    public int getOutputSize(
144        int length)
145    {
146        // Note: Can assume partialBlockOkay is true for purposes of this calculation
147        return length + bufOff;
148    }
149
150    /**
151     * process a single byte, producing an output block if necessary.
152     *
153     * @param in the input byte.
154     * @param out the space for any output that might be produced.
155     * @param outOff the offset from which the output will be copied.
156     * @return the number of output bytes copied to out.
157     * @exception DataLengthException if there isn't enough space in out.
158     * @exception IllegalStateException if the cipher isn't initialised.
159     */
160    public int processByte(
161        byte        in,
162        byte[]      out,
163        int         outOff)
164        throws DataLengthException, IllegalStateException
165    {
166        int         resultLen = 0;
167
168        buf[bufOff++] = in;
169
170        if (bufOff == buf.length)
171        {
172            resultLen = cipher.processBlock(buf, 0, out, outOff);
173            bufOff = 0;
174        }
175
176        return resultLen;
177    }
178
179    /**
180     * process an array of bytes, producing output if necessary.
181     *
182     * @param in the input byte array.
183     * @param inOff the offset at which the input data starts.
184     * @param len the number of bytes to be copied out of the input array.
185     * @param out the space for any output that might be produced.
186     * @param outOff the offset from which the output will be copied.
187     * @return the number of output bytes copied to out.
188     * @exception DataLengthException if there isn't enough space in out.
189     * @exception IllegalStateException if the cipher isn't initialised.
190     */
191    public int processBytes(
192        byte[]      in,
193        int         inOff,
194        int         len,
195        byte[]      out,
196        int         outOff)
197        throws DataLengthException, IllegalStateException
198    {
199        if (len < 0)
200        {
201            throw new IllegalArgumentException("Can't have a negative input length!");
202        }
203
204        int blockSize   = getBlockSize();
205        int length      = getUpdateOutputSize(len);
206
207        if (length > 0)
208        {
209            if ((outOff + length) > out.length)
210            {
211                throw new OutputLengthException("output buffer too short");
212            }
213        }
214
215        int resultLen = 0;
216        int gapLen = buf.length - bufOff;
217
218        if (len > gapLen)
219        {
220            System.arraycopy(in, inOff, buf, bufOff, gapLen);
221
222            resultLen += cipher.processBlock(buf, 0, out, outOff);
223
224            bufOff = 0;
225            len -= gapLen;
226            inOff += gapLen;
227
228            while (len > buf.length)
229            {
230                resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen);
231
232                len -= blockSize;
233                inOff += blockSize;
234            }
235        }
236
237        System.arraycopy(in, inOff, buf, bufOff, len);
238
239        bufOff += len;
240
241        if (bufOff == buf.length)
242        {
243            resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);
244            bufOff = 0;
245        }
246
247        return resultLen;
248    }
249
250    /**
251     * Process the last block in the buffer.
252     *
253     * @param out the array the block currently being held is copied into.
254     * @param outOff the offset at which the copying starts.
255     * @return the number of output bytes copied to out.
256     * @exception DataLengthException if there is insufficient space in out for
257     * the output, or the input is not block size aligned and should be.
258     * @exception IllegalStateException if the underlying cipher is not
259     * initialised.
260     * @exception InvalidCipherTextException if padding is expected and not found.
261     * @exception DataLengthException if the input is not block size
262     * aligned.
263     */
264    public int doFinal(
265        byte[]  out,
266        int     outOff)
267        throws DataLengthException, IllegalStateException, InvalidCipherTextException
268    {
269        try
270        {
271            int resultLen = 0;
272
273            if (outOff + bufOff > out.length)
274            {
275                throw new OutputLengthException("output buffer too short for doFinal()");
276            }
277
278            if (bufOff != 0)
279            {
280                if (!partialBlockOkay)
281                {
282                    throw new DataLengthException("data not block size aligned");
283                }
284
285                cipher.processBlock(buf, 0, buf, 0);
286                resultLen = bufOff;
287                bufOff = 0;
288                System.arraycopy(buf, 0, out, outOff, resultLen);
289            }
290
291            return resultLen;
292        }
293        finally
294        {
295            reset();
296        }
297    }
298
299    /**
300     * Reset the buffer and cipher. After resetting the object is in the same
301     * state as it was after the last init (if there was one).
302     */
303    public void reset()
304    {
305        //
306        // clean the buffer.
307        //
308        for (int i = 0; i < buf.length; i++)
309        {
310            buf[i] = 0;
311        }
312
313        bufOff = 0;
314
315        //
316        // reset the underlying cipher.
317        //
318        cipher.reset();
319    }
320}
321