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)
52        {
53            partialBlockOkay = true;
54        }
55        else
56        {
57            partialBlockOkay = (idx > 0 && (name.startsWith("CFB", idx) || name.startsWith("OFB", idx) || name.startsWith("OpenPGP", idx) || name.startsWith("SIC", idx) || name.startsWith("GCTR", 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            leftOver    = total % buf.length - (cipher.getBlockSize() + 2);
119        }
120        else
121        {
122            leftOver    = total % buf.length;
123        }
124
125        return total - leftOver;
126    }
127
128    /**
129     * return the size of the output buffer required for an update plus a
130     * doFinal with an input of 'length' bytes.
131     *
132     * @param length the length of the input.
133     * @return the space required to accommodate a call to update and doFinal
134     * with 'length' bytes of input.
135     */
136    public int getOutputSize(
137        int length)
138    {
139        // Note: Can assume partialBlockOkay is true for purposes of this calculation
140        return length + bufOff;
141    }
142
143    /**
144     * process a single byte, producing an output block if neccessary.
145     *
146     * @param in the input byte.
147     * @param out the space for any output that might be produced.
148     * @param outOff the offset from which the output will be copied.
149     * @return the number of output bytes copied to out.
150     * @exception DataLengthException if there isn't enough space in out.
151     * @exception IllegalStateException if the cipher isn't initialised.
152     */
153    public int processByte(
154        byte        in,
155        byte[]      out,
156        int         outOff)
157        throws DataLengthException, IllegalStateException
158    {
159        int         resultLen = 0;
160
161        buf[bufOff++] = in;
162
163        if (bufOff == buf.length)
164        {
165            resultLen = cipher.processBlock(buf, 0, out, outOff);
166            bufOff = 0;
167        }
168
169        return resultLen;
170    }
171
172    /**
173     * process an array of bytes, producing output if necessary.
174     *
175     * @param in the input byte array.
176     * @param inOff the offset at which the input data starts.
177     * @param len the number of bytes to be copied out of the input array.
178     * @param out the space for any output that might be produced.
179     * @param outOff the offset from which the output will be copied.
180     * @return the number of output bytes copied to out.
181     * @exception DataLengthException if there isn't enough space in out.
182     * @exception IllegalStateException if the cipher isn't initialised.
183     */
184    public int processBytes(
185        byte[]      in,
186        int         inOff,
187        int         len,
188        byte[]      out,
189        int         outOff)
190        throws DataLengthException, IllegalStateException
191    {
192        if (len < 0)
193        {
194            throw new IllegalArgumentException("Can't have a negative input length!");
195        }
196
197        int blockSize   = getBlockSize();
198        int length      = getUpdateOutputSize(len);
199
200        if (length > 0)
201        {
202            if ((outOff + length) > out.length)
203            {
204                throw new DataLengthException("output buffer too short");
205            }
206        }
207
208        int resultLen = 0;
209        int gapLen = buf.length - bufOff;
210
211        if (len > gapLen)
212        {
213            System.arraycopy(in, inOff, buf, bufOff, gapLen);
214
215            resultLen += cipher.processBlock(buf, 0, out, outOff);
216
217            bufOff = 0;
218            len -= gapLen;
219            inOff += gapLen;
220
221            while (len > buf.length)
222            {
223                resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen);
224
225                len -= blockSize;
226                inOff += blockSize;
227            }
228        }
229
230        System.arraycopy(in, inOff, buf, bufOff, len);
231
232        bufOff += len;
233
234        if (bufOff == buf.length)
235        {
236            resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);
237            bufOff = 0;
238        }
239
240        return resultLen;
241    }
242
243    /**
244     * Process the last block in the buffer.
245     *
246     * @param out the array the block currently being held is copied into.
247     * @param outOff the offset at which the copying starts.
248     * @return the number of output bytes copied to out.
249     * @exception DataLengthException if there is insufficient space in out for
250     * the output, or the input is not block size aligned and should be.
251     * @exception IllegalStateException if the underlying cipher is not
252     * initialised.
253     * @exception InvalidCipherTextException if padding is expected and not found.
254     * @exception DataLengthException if the input is not block size
255     * aligned.
256     */
257    public int doFinal(
258        byte[]  out,
259        int     outOff)
260        throws DataLengthException, IllegalStateException, InvalidCipherTextException
261    {
262        try
263        {
264            int resultLen = 0;
265
266            if (outOff + bufOff > out.length)
267            {
268                throw new DataLengthException("output buffer too short for doFinal()");
269            }
270
271            if (bufOff != 0)
272            {
273                if (!partialBlockOkay)
274                {
275                    throw new DataLengthException("data not block size aligned");
276                }
277
278                cipher.processBlock(buf, 0, buf, 0);
279                resultLen = bufOff;
280                bufOff = 0;
281                System.arraycopy(buf, 0, out, outOff, resultLen);
282            }
283
284            return resultLen;
285        }
286        finally
287        {
288            reset();
289        }
290    }
291
292    /**
293     * Reset the buffer and cipher. After resetting the object is in the same
294     * state as it was after the last init (if there was one).
295     */
296    public void reset()
297    {
298        //
299        // clean the buffer.
300        //
301        for (int i = 0; i < buf.length; i++)
302        {
303            buf[i] = 0;
304        }
305
306        bufOff = 0;
307
308        //
309        // reset the underlying cipher.
310        //
311        cipher.reset();
312    }
313}
314