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