1package org.bouncycastle.crypto.macs;
2
3import org.bouncycastle.crypto.BlockCipher;
4import org.bouncycastle.crypto.CipherParameters;
5import org.bouncycastle.crypto.Mac;
6import org.bouncycastle.crypto.modes.CBCBlockCipher;
7import org.bouncycastle.crypto.paddings.BlockCipherPadding;
8
9/**
10 * standard CBC Block Cipher MAC - if no padding is specified the default of
11 * pad of zeroes is used.
12 */
13public class CBCBlockCipherMac
14    implements Mac
15{
16    private byte[]              mac;
17
18    private byte[]              buf;
19    private int                 bufOff;
20    private BlockCipher         cipher;
21    private BlockCipherPadding  padding;
22
23    private int                 macSize;
24
25    /**
26     * create a standard MAC based on a CBC block cipher. This will produce an
27     * authentication code half the length of the block size of the cipher.
28     *
29     * @param cipher the cipher to be used as the basis of the MAC generation.
30     */
31    public CBCBlockCipherMac(
32        BlockCipher     cipher)
33    {
34        this(cipher, (cipher.getBlockSize() * 8) / 2, null);
35    }
36
37    /**
38     * create a standard MAC based on a CBC block cipher. This will produce an
39     * authentication code half the length of the block size of the cipher.
40     *
41     * @param cipher the cipher to be used as the basis of the MAC generation.
42     * @param padding the padding to be used to complete the last block.
43     */
44    public CBCBlockCipherMac(
45        BlockCipher         cipher,
46        BlockCipherPadding  padding)
47    {
48        this(cipher, (cipher.getBlockSize() * 8) / 2, padding);
49    }
50
51    /**
52     * create a standard MAC based on a block cipher with the size of the
53     * MAC been given in bits. This class uses CBC mode as the basis for the
54     * MAC generation.
55     * <p>
56     * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
57     * or 16 bits if being used as a data authenticator (FIPS Publication 113),
58     * and in general should be less than the size of the block cipher as it reduces
59     * the chance of an exhaustive attack (see Handbook of Applied Cryptography).
60     *
61     * @param cipher the cipher to be used as the basis of the MAC generation.
62     * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
63     */
64    public CBCBlockCipherMac(
65        BlockCipher     cipher,
66        int             macSizeInBits)
67    {
68        this(cipher, macSizeInBits, null);
69    }
70
71    /**
72     * create a standard MAC based on a block cipher with the size of the
73     * MAC been given in bits. This class uses CBC mode as the basis for the
74     * MAC generation.
75     * <p>
76     * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
77     * or 16 bits if being used as a data authenticator (FIPS Publication 113),
78     * and in general should be less than the size of the block cipher as it reduces
79     * the chance of an exhaustive attack (see Handbook of Applied Cryptography).
80     *
81     * @param cipher the cipher to be used as the basis of the MAC generation.
82     * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
83     * @param padding the padding to be used to complete the last block.
84     */
85    public CBCBlockCipherMac(
86        BlockCipher         cipher,
87        int                 macSizeInBits,
88        BlockCipherPadding  padding)
89    {
90        if ((macSizeInBits % 8) != 0)
91        {
92            throw new IllegalArgumentException("MAC size must be multiple of 8");
93        }
94
95        this.cipher = new CBCBlockCipher(cipher);
96        this.padding = padding;
97        this.macSize = macSizeInBits / 8;
98
99        mac = new byte[cipher.getBlockSize()];
100
101        buf = new byte[cipher.getBlockSize()];
102        bufOff = 0;
103    }
104
105    public String getAlgorithmName()
106    {
107        return cipher.getAlgorithmName();
108    }
109
110    public void init(
111        CipherParameters    params)
112    {
113        reset();
114
115        cipher.init(true, params);
116    }
117
118    public int getMacSize()
119    {
120        return macSize;
121    }
122
123    public void update(
124        byte        in)
125    {
126        if (bufOff == buf.length)
127        {
128            cipher.processBlock(buf, 0, mac, 0);
129            bufOff = 0;
130        }
131
132        buf[bufOff++] = in;
133    }
134
135    public void update(
136        byte[]      in,
137        int         inOff,
138        int         len)
139    {
140        if (len < 0)
141        {
142            throw new IllegalArgumentException("Can't have a negative input length!");
143        }
144
145        int blockSize = cipher.getBlockSize();
146        int gapLen = blockSize - bufOff;
147
148        if (len > gapLen)
149        {
150            System.arraycopy(in, inOff, buf, bufOff, gapLen);
151
152            cipher.processBlock(buf, 0, mac, 0);
153
154            bufOff = 0;
155            len -= gapLen;
156            inOff += gapLen;
157
158            while (len > blockSize)
159            {
160                cipher.processBlock(in, inOff, mac, 0);
161
162                len -= blockSize;
163                inOff += blockSize;
164            }
165        }
166
167        System.arraycopy(in, inOff, buf, bufOff, len);
168
169        bufOff += len;
170    }
171
172    public int doFinal(
173        byte[]  out,
174        int     outOff)
175    {
176        int blockSize = cipher.getBlockSize();
177
178        if (padding == null)
179        {
180            //
181            // pad with zeroes
182            //
183            while (bufOff < blockSize)
184            {
185                buf[bufOff] = 0;
186                bufOff++;
187            }
188        }
189        else
190        {
191            if (bufOff == blockSize)
192            {
193                cipher.processBlock(buf, 0, mac, 0);
194                bufOff = 0;
195            }
196
197            padding.addPadding(buf, bufOff);
198        }
199
200        cipher.processBlock(buf, 0, mac, 0);
201
202        System.arraycopy(mac, 0, out, outOff, macSize);
203
204        reset();
205
206        return macSize;
207    }
208
209    /**
210     * Reset the mac generator.
211     */
212    public void reset()
213    {
214        /*
215         * clean the buffer.
216         */
217        for (int i = 0; i < buf.length; i++)
218        {
219            buf[i] = 0;
220        }
221
222        bufOff = 0;
223
224        /*
225         * reset the underlying cipher.
226         */
227        cipher.reset();
228    }
229}
230