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