1package org.bouncycastle.crypto.modes;
2
3import org.bouncycastle.crypto.BlockCipher;
4import org.bouncycastle.crypto.BufferedBlockCipher;
5import org.bouncycastle.crypto.DataLengthException;
6import org.bouncycastle.crypto.InvalidCipherTextException;
7
8/**
9 * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to
10 * be used to produce cipher text which is the same length as the plain text.
11 */
12public class CTSBlockCipher
13    extends BufferedBlockCipher
14{
15    private int     blockSize;
16
17    /**
18     * Create a buffered block cipher that uses Cipher Text Stealing
19     *
20     * @param cipher the underlying block cipher this buffering object wraps.
21     */
22    public CTSBlockCipher(
23        BlockCipher     cipher)
24    {
25        if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher))
26        {
27            throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers");
28        }
29
30        this.cipher = cipher;
31
32        blockSize = cipher.getBlockSize();
33
34        buf = new byte[blockSize * 2];
35        bufOff = 0;
36    }
37
38    /**
39     * return the size of the output buffer required for an update
40     * an input of len bytes.
41     *
42     * @param len the length of the input.
43     * @return the space required to accommodate a call to update
44     * with len bytes of input.
45     */
46    public int getUpdateOutputSize(
47        int len)
48    {
49        int total       = len + bufOff;
50        int leftOver    = total % buf.length;
51
52        if (leftOver == 0)
53        {
54            return total - buf.length;
55        }
56
57        return total - leftOver;
58    }
59
60    /**
61     * return the size of the output buffer required for an update plus a
62     * doFinal with an input of len bytes.
63     *
64     * @param len the length of the input.
65     * @return the space required to accommodate a call to update and doFinal
66     * with len bytes of input.
67     */
68    public int getOutputSize(
69        int len)
70    {
71        return len + bufOff;
72    }
73
74    /**
75     * process a single byte, producing an output block if neccessary.
76     *
77     * @param in the input byte.
78     * @param out the space for any output that might be produced.
79     * @param outOff the offset from which the output will be copied.
80     * @return the number of output bytes copied to out.
81     * @exception DataLengthException if there isn't enough space in out.
82     * @exception IllegalStateException if the cipher isn't initialised.
83     */
84    public int processByte(
85        byte        in,
86        byte[]      out,
87        int         outOff)
88        throws DataLengthException, IllegalStateException
89    {
90        int         resultLen = 0;
91
92        if (bufOff == buf.length)
93        {
94            resultLen = cipher.processBlock(buf, 0, out, outOff);
95            System.arraycopy(buf, blockSize, buf, 0, blockSize);
96
97            bufOff = blockSize;
98        }
99
100        buf[bufOff++] = in;
101
102        return resultLen;
103    }
104
105    /**
106     * process an array of bytes, producing output if necessary.
107     *
108     * @param in the input byte array.
109     * @param inOff the offset at which the input data starts.
110     * @param len the number of bytes to be copied out of the input array.
111     * @param out the space for any output that might be produced.
112     * @param outOff the offset from which the output will be copied.
113     * @return the number of output bytes copied to out.
114     * @exception DataLengthException if there isn't enough space in out.
115     * @exception IllegalStateException if the cipher isn't initialised.
116     */
117    public int processBytes(
118        byte[]      in,
119        int         inOff,
120        int         len,
121        byte[]      out,
122        int         outOff)
123        throws DataLengthException, IllegalStateException
124    {
125        if (len < 0)
126        {
127            throw new IllegalArgumentException("Can't have a negative input length!");
128        }
129
130        int blockSize   = getBlockSize();
131        int length      = getUpdateOutputSize(len);
132
133        if (length > 0)
134        {
135            if ((outOff + length) > out.length)
136            {
137                throw new DataLengthException("output buffer too short");
138            }
139        }
140
141        int resultLen = 0;
142        int gapLen = buf.length - bufOff;
143
144        if (len > gapLen)
145        {
146            System.arraycopy(in, inOff, buf, bufOff, gapLen);
147
148            resultLen += cipher.processBlock(buf, 0, out, outOff);
149            System.arraycopy(buf, blockSize, buf, 0, blockSize);
150
151            bufOff = blockSize;
152
153            len -= gapLen;
154            inOff += gapLen;
155
156            while (len > blockSize)
157            {
158                System.arraycopy(in, inOff, buf, bufOff, blockSize);
159                resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);
160                System.arraycopy(buf, blockSize, buf, 0, blockSize);
161
162                len -= blockSize;
163                inOff += blockSize;
164            }
165        }
166
167        System.arraycopy(in, inOff, buf, bufOff, len);
168
169        bufOff += len;
170
171        return resultLen;
172    }
173
174    /**
175     * Process the last block in the buffer.
176     *
177     * @param out the array the block currently being held is copied into.
178     * @param outOff the offset at which the copying starts.
179     * @return the number of output bytes copied to out.
180     * @exception DataLengthException if there is insufficient space in out for
181     * the output.
182     * @exception IllegalStateException if the underlying cipher is not
183     * initialised.
184     * @exception InvalidCipherTextException if cipher text decrypts wrongly (in
185     * case the exception will never get thrown).
186     */
187    public int doFinal(
188        byte[]  out,
189        int     outOff)
190        throws DataLengthException, IllegalStateException, InvalidCipherTextException
191    {
192        if (bufOff + outOff > out.length)
193        {
194            throw new DataLengthException("output buffer to small in doFinal");
195        }
196
197        int     blockSize = cipher.getBlockSize();
198        int     len = bufOff - blockSize;
199        byte[]  block = new byte[blockSize];
200
201        if (forEncryption)
202        {
203            cipher.processBlock(buf, 0, block, 0);
204
205            if (bufOff < blockSize)
206            {
207                throw new DataLengthException("need at least one block of input for CTS");
208            }
209
210            for (int i = bufOff; i != buf.length; i++)
211            {
212                buf[i] = block[i - blockSize];
213            }
214
215            for (int i = blockSize; i != bufOff; i++)
216            {
217                buf[i] ^= block[i - blockSize];
218            }
219
220            if (cipher instanceof CBCBlockCipher)
221            {
222                BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
223
224                c.processBlock(buf, blockSize, out, outOff);
225            }
226            else
227            {
228                cipher.processBlock(buf, blockSize, out, outOff);
229            }
230
231            System.arraycopy(block, 0, out, outOff + blockSize, len);
232        }
233        else
234        {
235            byte[]  lastBlock = new byte[blockSize];
236
237            if (cipher instanceof CBCBlockCipher)
238            {
239                BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
240
241                c.processBlock(buf, 0, block, 0);
242            }
243            else
244            {
245                cipher.processBlock(buf, 0, block, 0);
246            }
247
248            for (int i = blockSize; i != bufOff; i++)
249            {
250                lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]);
251            }
252
253            System.arraycopy(buf, blockSize, block, 0, len);
254
255            cipher.processBlock(block, 0, out, outOff);
256            System.arraycopy(lastBlock, 0, out, outOff + blockSize, len);
257        }
258
259        int offset = bufOff;
260
261        reset();
262
263        return offset;
264    }
265}
266