CCMBlockCipher.java revision e6bf3e8dfa2804891a82075cb469b736321b4827
1package org.bouncycastle.crypto.modes;
2
3import java.io.ByteArrayOutputStream;
4
5import org.bouncycastle.crypto.BlockCipher;
6import org.bouncycastle.crypto.CipherParameters;
7import org.bouncycastle.crypto.DataLengthException;
8import org.bouncycastle.crypto.InvalidCipherTextException;
9import org.bouncycastle.crypto.Mac;
10import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
11import org.bouncycastle.crypto.params.AEADParameters;
12import org.bouncycastle.crypto.params.ParametersWithIV;
13import org.bouncycastle.util.Arrays;
14
15/**
16 * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
17 * NIST Special Publication 800-38C.
18 * <p>
19 * <b>Note</b>: this mode is a packet mode - it needs all the data up front.
20 */
21public class CCMBlockCipher
22    implements AEADBlockCipher
23{
24    private BlockCipher           cipher;
25    private int                   blockSize;
26    private boolean               forEncryption;
27    private byte[]                nonce;
28    private byte[]                associatedText;
29    private int                   macSize;
30    private CipherParameters      keyParam;
31    private byte[]                macBlock;
32    private ByteArrayOutputStream data = new ByteArrayOutputStream();
33
34    /**
35     * Basic constructor.
36     *
37     * @param c the block cipher to be used.
38     */
39    public CCMBlockCipher(BlockCipher c)
40    {
41        this.cipher = c;
42        this.blockSize = c.getBlockSize();
43        this.macBlock = new byte[blockSize];
44
45        if (blockSize != 16)
46        {
47            throw new IllegalArgumentException("cipher required with a block size of 16.");
48        }
49    }
50
51    /**
52     * return the underlying block cipher that we are wrapping.
53     *
54     * @return the underlying block cipher that we are wrapping.
55     */
56    public BlockCipher getUnderlyingCipher()
57    {
58        return cipher;
59    }
60
61
62    public void init(boolean forEncryption, CipherParameters params)
63          throws IllegalArgumentException
64    {
65        this.forEncryption = forEncryption;
66
67        if (params instanceof AEADParameters)
68        {
69            AEADParameters param = (AEADParameters)params;
70
71            nonce = param.getNonce();
72            associatedText = param.getAssociatedText();
73            macSize = param.getMacSize() / 8;
74            keyParam = param.getKey();
75        }
76        else if (params instanceof ParametersWithIV)
77        {
78            ParametersWithIV param = (ParametersWithIV)params;
79
80            nonce = param.getIV();
81            associatedText = null;
82            macSize = macBlock.length / 2;
83            keyParam = param.getParameters();
84        }
85        else
86        {
87            throw new IllegalArgumentException("invalid parameters passed to CCM");
88        }
89    }
90
91    public String getAlgorithmName()
92    {
93        return cipher.getAlgorithmName() + "/CCM";
94    }
95
96    public int processByte(byte in, byte[] out, int outOff)
97        throws DataLengthException, IllegalStateException
98    {
99        data.write(in);
100
101        return 0;
102    }
103
104    public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff)
105        throws DataLengthException, IllegalStateException
106    {
107        data.write(in, inOff, inLen);
108
109        return 0;
110    }
111
112    public int doFinal(byte[] out, int outOff)
113        throws IllegalStateException, InvalidCipherTextException
114    {
115        byte[] text = data.toByteArray();
116        byte[] enc = processPacket(text, 0, text.length);
117
118        System.arraycopy(enc, 0, out, outOff, enc.length);
119
120        reset();
121
122        return enc.length;
123    }
124
125    public void reset()
126    {
127        cipher.reset();
128        data.reset();
129    }
130
131    /**
132     * Returns a byte array containing the mac calculated as part of the
133     * last encrypt or decrypt operation.
134     *
135     * @return the last mac calculated.
136     */
137    public byte[] getMac()
138    {
139        byte[] mac = new byte[macSize];
140
141        System.arraycopy(macBlock, 0, mac, 0, mac.length);
142
143        return mac;
144    }
145
146    public int getUpdateOutputSize(int len)
147    {
148        return 0;
149    }
150
151    public int getOutputSize(int len)
152    {
153        if (forEncryption)
154        {
155            return data.size() + len + macSize;
156        }
157        else
158        {
159            return data.size() + len - macSize;
160        }
161    }
162
163    public byte[] processPacket(byte[] in, int inOff, int inLen)
164        throws IllegalStateException, InvalidCipherTextException
165    {
166        if (keyParam == null)
167        {
168            throw new IllegalStateException("CCM cipher unitialized.");
169        }
170
171        BlockCipher ctrCipher = new SICBlockCipher(cipher);
172        byte[] iv = new byte[blockSize];
173        byte[] out;
174
175        iv[0] = (byte)(((15 - nonce.length) - 1) & 0x7);
176
177        System.arraycopy(nonce, 0, iv, 1, nonce.length);
178
179        ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv));
180
181        if (forEncryption)
182        {
183            int index = inOff;
184            int outOff = 0;
185
186            out = new byte[inLen + macSize];
187
188            calculateMac(in, inOff, inLen, macBlock);
189
190            ctrCipher.processBlock(macBlock, 0, macBlock, 0);   // S0
191
192            while (index < inLen - blockSize)                   // S1...
193            {
194                ctrCipher.processBlock(in, index, out, outOff);
195                outOff += blockSize;
196                index += blockSize;
197            }
198
199            byte[] block = new byte[blockSize];
200
201            System.arraycopy(in, index, block, 0, inLen - index);
202
203            ctrCipher.processBlock(block, 0, block, 0);
204
205            System.arraycopy(block, 0, out, outOff, inLen - index);
206
207            outOff += inLen - index;
208
209            System.arraycopy(macBlock, 0, out, outOff, out.length - outOff);
210        }
211        else
212        {
213            int index = inOff;
214            int outOff = 0;
215
216            out = new byte[inLen - macSize];
217
218            System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize);
219
220            ctrCipher.processBlock(macBlock, 0, macBlock, 0);
221
222            for (int i = macSize; i != macBlock.length; i++)
223            {
224                macBlock[i] = 0;
225            }
226
227            while (outOff < out.length - blockSize)
228            {
229                ctrCipher.processBlock(in, index, out, outOff);
230                outOff += blockSize;
231                index += blockSize;
232            }
233
234            byte[] block = new byte[blockSize];
235
236            System.arraycopy(in, index, block, 0, out.length - outOff);
237
238            ctrCipher.processBlock(block, 0, block, 0);
239
240            System.arraycopy(block, 0, out, outOff, out.length - outOff);
241
242            byte[] calculatedMacBlock = new byte[blockSize];
243
244            calculateMac(out, 0, out.length, calculatedMacBlock);
245
246            if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock))
247            {
248                throw new InvalidCipherTextException("mac check in CCM failed");
249            }
250        }
251
252        return out;
253    }
254
255    private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
256    {
257        Mac    cMac = new CBCBlockCipherMac(cipher, macSize * 8);
258
259        cMac.init(keyParam);
260
261        //
262        // build b0
263        //
264        byte[] b0 = new byte[16];
265
266        if (hasAssociatedText())
267        {
268            b0[0] |= 0x40;
269        }
270
271        b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3;
272
273        b0[0] |= ((15 - nonce.length) - 1) & 0x7;
274
275        System.arraycopy(nonce, 0, b0, 1, nonce.length);
276
277        int q = dataLen;
278        int count = 1;
279        while (q > 0)
280        {
281            b0[b0.length - count] = (byte)(q & 0xff);
282            q >>>= 8;
283            count++;
284        }
285
286        cMac.update(b0, 0, b0.length);
287
288        //
289        // process associated text
290        //
291        if (hasAssociatedText())
292        {
293            int extra;
294
295            if (associatedText.length < ((1 << 16) - (1 << 8)))
296            {
297                cMac.update((byte)(associatedText.length >> 8));
298                cMac.update((byte)associatedText.length);
299
300                extra = 2;
301            }
302            else // can't go any higher than 2^32
303            {
304                cMac.update((byte)0xff);
305                cMac.update((byte)0xfe);
306                cMac.update((byte)(associatedText.length >> 24));
307                cMac.update((byte)(associatedText.length >> 16));
308                cMac.update((byte)(associatedText.length >> 8));
309                cMac.update((byte)associatedText.length);
310
311                extra = 6;
312            }
313
314            cMac.update(associatedText, 0, associatedText.length);
315
316            extra = (extra + associatedText.length) % 16;
317            if (extra != 0)
318            {
319                for (int i = 0; i != 16 - extra; i++)
320                {
321                    cMac.update((byte)0x00);
322                }
323            }
324        }
325
326        //
327        // add the text
328        //
329        cMac.update(data, dataOff, dataLen);
330
331        return cMac.doFinal(macBlock, 0);
332    }
333
334    private boolean hasAssociatedText()
335    {
336        return associatedText != null && associatedText.length != 0;
337    }
338}
339