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        this.encrypting = encrypting;
65
66        if (params instanceof ParametersWithIV)
67        {
68                ParametersWithIV ivParam = (ParametersWithIV)params;
69                byte[]      iv = ivParam.getIV();
70
71                if (iv.length != blockSize)
72                {
73                    throw new IllegalArgumentException("initialisation vector must be the same length as block size");
74                }
75
76                System.arraycopy(iv, 0, IV, 0, iv.length);
77
78                reset();
79
80                cipher.init(encrypting, ivParam.getParameters());
81        }
82        else
83        {
84                reset();
85
86                cipher.init(encrypting, params);
87        }
88    }
89
90    /**
91     * return the algorithm name and mode.
92     *
93     * @return the name of the underlying algorithm followed by "/CBC".
94     */
95    public String getAlgorithmName()
96    {
97        return cipher.getAlgorithmName() + "/CBC";
98    }
99
100    /**
101     * return the block size of the underlying cipher.
102     *
103     * @return the block size of the underlying cipher.
104     */
105    public int getBlockSize()
106    {
107        return cipher.getBlockSize();
108    }
109
110    /**
111     * Process one block of input from the array in and write it to
112     * the out array.
113     *
114     * @param in the array containing the input data.
115     * @param inOff offset into the in array the data starts at.
116     * @param out the array the output data will be copied into.
117     * @param outOff the offset into the out array the output will start at.
118     * @exception DataLengthException if there isn't enough data in in, or
119     * space in out.
120     * @exception IllegalStateException if the cipher isn't initialised.
121     * @return the number of bytes processed and produced.
122     */
123    public int processBlock(
124        byte[]      in,
125        int         inOff,
126        byte[]      out,
127        int         outOff)
128        throws DataLengthException, IllegalStateException
129    {
130        return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
131    }
132
133    /**
134     * reset the chaining vector back to the IV and reset the underlying
135     * cipher.
136     */
137    public void reset()
138    {
139        System.arraycopy(IV, 0, cbcV, 0, IV.length);
140        Arrays.fill(cbcNextV, (byte)0);
141
142        cipher.reset();
143    }
144
145    /**
146     * Do the appropriate chaining step for CBC mode encryption.
147     *
148     * @param in the array containing the data to be encrypted.
149     * @param inOff offset into the in array the data starts at.
150     * @param out the array the encrypted data will be copied into.
151     * @param outOff the offset into the out array the output will start at.
152     * @exception DataLengthException if there isn't enough data in in, or
153     * space in out.
154     * @exception IllegalStateException if the cipher isn't initialised.
155     * @return the number of bytes processed and produced.
156     */
157    private int encryptBlock(
158        byte[]      in,
159        int         inOff,
160        byte[]      out,
161        int         outOff)
162        throws DataLengthException, IllegalStateException
163    {
164        if ((inOff + blockSize) > in.length)
165        {
166            throw new DataLengthException("input buffer too short");
167        }
168
169        /*
170         * XOR the cbcV and the input,
171         * then encrypt the cbcV
172         */
173        for (int i = 0; i < blockSize; i++)
174        {
175            cbcV[i] ^= in[inOff + i];
176        }
177
178        int length = cipher.processBlock(cbcV, 0, out, outOff);
179
180        /*
181         * copy ciphertext to cbcV
182         */
183        System.arraycopy(out, outOff, cbcV, 0, cbcV.length);
184
185        return length;
186    }
187
188    /**
189     * Do the appropriate chaining step for CBC mode decryption.
190     *
191     * @param in the array containing the data to be decrypted.
192     * @param inOff offset into the in array the data starts at.
193     * @param out the array the decrypted data will be copied into.
194     * @param outOff the offset into the out array the output will start at.
195     * @exception DataLengthException if there isn't enough data in in, or
196     * space in out.
197     * @exception IllegalStateException if the cipher isn't initialised.
198     * @return the number of bytes processed and produced.
199     */
200    private int decryptBlock(
201        byte[]      in,
202        int         inOff,
203        byte[]      out,
204        int         outOff)
205        throws DataLengthException, IllegalStateException
206    {
207        if ((inOff + blockSize) > in.length)
208        {
209            throw new DataLengthException("input buffer too short");
210        }
211
212        System.arraycopy(in, inOff, cbcNextV, 0, blockSize);
213
214        int length = cipher.processBlock(in, inOff, out, outOff);
215
216        /*
217         * XOR the cbcV and the output
218         */
219        for (int i = 0; i < blockSize; i++)
220        {
221            out[outOff + i] ^= cbcV[i];
222        }
223
224        /*
225         * swap the back up buffer into next position
226         */
227        byte[]  tmp;
228
229        tmp = cbcV;
230        cbcV = cbcNextV;
231        cbcNextV = tmp;
232
233        return length;
234    }
235}
236