CBCBlockCipher.java revision e1142c149e244797ce73b0e7fad40816e447a817
1package org.bouncycastle.crypto.modes;
2
3import org.bouncycastle.crypto.BlockCipher;
4import org.bouncycastle.crypto.CipherParameters;
5import org.bouncycastle.crypto.DataLengthException;
6import org.bouncycastle.crypto.params.ParametersWithIV;
7import org.bouncycastle.util.Arrays;
8
9/**
10 * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher.
11 */
12public class CBCBlockCipher
13    implements BlockCipher
14{
15    private byte[]          IV;
16    private byte[]          cbcV;
17    private byte[]          cbcNextV;
18
19    private int             blockSize;
20    private BlockCipher     cipher = null;
21    private boolean         encrypting;
22
23    /**
24     * Basic constructor.
25     *
26     * @param cipher the block cipher to be used as the basis of chaining.
27     */
28    public CBCBlockCipher(
29        BlockCipher cipher)
30    {
31        this.cipher = cipher;
32        this.blockSize = cipher.getBlockSize();
33
34        this.IV = new byte[blockSize];
35        this.cbcV = new byte[blockSize];
36        this.cbcNextV = new byte[blockSize];
37    }
38
39    /**
40     * return the underlying block cipher that we are wrapping.
41     *
42     * @return the underlying block cipher that we are wrapping.
43     */
44    public BlockCipher getUnderlyingCipher()
45    {
46        return cipher;
47    }
48
49    /**
50     * Initialise the cipher and, possibly, the initialisation vector (IV).
51     * If an IV isn't passed as part of the parameter, the IV will be all zeros.
52     *
53     * @param encrypting if true the cipher is initialised for
54     *  encryption, if false for decryption.
55     * @param params the key and other data required by the cipher.
56     * @exception IllegalArgumentException if the params argument is
57     * inappropriate.
58     */
59    public void init(
60        boolean             encrypting,
61        CipherParameters    params)
62        throws IllegalArgumentException
63    {
64        boolean oldEncrypting = this.encrypting;
65
66        this.encrypting = encrypting;
67
68        if (params instanceof ParametersWithIV)
69        {
70            ParametersWithIV ivParam = (ParametersWithIV)params;
71            byte[] iv = ivParam.getIV();
72
73            if (iv.length != blockSize)
74            {
75                throw new IllegalArgumentException("initialisation vector must be the same length as block size");
76            }
77
78            System.arraycopy(iv, 0, IV, 0, iv.length);
79
80            reset();
81
82            // if null it's an IV changed only.
83            if (ivParam.getParameters() != null)
84            {
85                cipher.init(encrypting, ivParam.getParameters());
86            }
87            else if (oldEncrypting != encrypting)
88            {
89                throw new IllegalArgumentException("cannot change encrypting state without providing key.");
90            }
91        }
92        else
93        {
94            reset();
95
96            // if it's null, key is to be reused.
97            if (params != null)
98            {
99                cipher.init(encrypting, params);
100            }
101            else if (oldEncrypting != encrypting)
102            {
103                throw new IllegalArgumentException("cannot change encrypting state without providing key.");
104            }
105        }
106    }
107
108    /**
109     * return the algorithm name and mode.
110     *
111     * @return the name of the underlying algorithm followed by "/CBC".
112     */
113    public String getAlgorithmName()
114    {
115        return cipher.getAlgorithmName() + "/CBC";
116    }
117
118    /**
119     * return the block size of the underlying cipher.
120     *
121     * @return the block size of the underlying cipher.
122     */
123    public int getBlockSize()
124    {
125        return cipher.getBlockSize();
126    }
127
128    /**
129     * Process one block of input from the array in and write it to
130     * the out array.
131     *
132     * @param in the array containing the input data.
133     * @param inOff offset into the in array the data starts at.
134     * @param out the array the output data will be copied into.
135     * @param outOff the offset into the out array the output will start at.
136     * @exception DataLengthException if there isn't enough data in in, or
137     * space in out.
138     * @exception IllegalStateException if the cipher isn't initialised.
139     * @return the number of bytes processed and produced.
140     */
141    public int processBlock(
142        byte[]      in,
143        int         inOff,
144        byte[]      out,
145        int         outOff)
146        throws DataLengthException, IllegalStateException
147    {
148        return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
149    }
150
151    /**
152     * reset the chaining vector back to the IV and reset the underlying
153     * cipher.
154     */
155    public void reset()
156    {
157        System.arraycopy(IV, 0, cbcV, 0, IV.length);
158        Arrays.fill(cbcNextV, (byte)0);
159
160        cipher.reset();
161    }
162
163    /**
164     * Do the appropriate chaining step for CBC mode encryption.
165     *
166     * @param in the array containing the data to be encrypted.
167     * @param inOff offset into the in array the data starts at.
168     * @param out the array the encrypted data will be copied into.
169     * @param outOff the offset into the out array the output will start at.
170     * @exception DataLengthException if there isn't enough data in in, or
171     * space in out.
172     * @exception IllegalStateException if the cipher isn't initialised.
173     * @return the number of bytes processed and produced.
174     */
175    private int encryptBlock(
176        byte[]      in,
177        int         inOff,
178        byte[]      out,
179        int         outOff)
180        throws DataLengthException, IllegalStateException
181    {
182        if ((inOff + blockSize) > in.length)
183        {
184            throw new DataLengthException("input buffer too short");
185        }
186
187        /*
188         * XOR the cbcV and the input,
189         * then encrypt the cbcV
190         */
191        for (int i = 0; i < blockSize; i++)
192        {
193            cbcV[i] ^= in[inOff + i];
194        }
195
196        int length = cipher.processBlock(cbcV, 0, out, outOff);
197
198        /*
199         * copy ciphertext to cbcV
200         */
201        System.arraycopy(out, outOff, cbcV, 0, cbcV.length);
202
203        return length;
204    }
205
206    /**
207     * Do the appropriate chaining step for CBC mode decryption.
208     *
209     * @param in the array containing the data to be decrypted.
210     * @param inOff offset into the in array the data starts at.
211     * @param out the array the decrypted data will be copied into.
212     * @param outOff the offset into the out array the output will start at.
213     * @exception DataLengthException if there isn't enough data in in, or
214     * space in out.
215     * @exception IllegalStateException if the cipher isn't initialised.
216     * @return the number of bytes processed and produced.
217     */
218    private int decryptBlock(
219        byte[]      in,
220        int         inOff,
221        byte[]      out,
222        int         outOff)
223        throws DataLengthException, IllegalStateException
224    {
225        if ((inOff + blockSize) > in.length)
226        {
227            throw new DataLengthException("input buffer too short");
228        }
229
230        System.arraycopy(in, inOff, cbcNextV, 0, blockSize);
231
232        int length = cipher.processBlock(in, inOff, out, outOff);
233
234        /*
235         * XOR the cbcV and the output
236         */
237        for (int i = 0; i < blockSize; i++)
238        {
239            out[outOff + i] ^= cbcV[i];
240        }
241
242        /*
243         * swap the back up buffer into next position
244         */
245        byte[]  tmp;
246
247        tmp = cbcV;
248        cbcV = cbcNextV;
249        cbcNextV = tmp;
250
251        return length;
252    }
253}
254