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.engines.DESEngine;
7import org.bouncycastle.crypto.modes.CBCBlockCipher;
8import org.bouncycastle.crypto.paddings.BlockCipherPadding;
9import org.bouncycastle.crypto.params.KeyParameter;
10
11/**
12 * DES based CBC Block Cipher MAC according to ISO9797, algorithm 3 (ANSI X9.19 Retail MAC)
13 *
14 * This could as well be derived from CBCBlockCipherMac, but then the property mac in the base
15 * class must be changed to protected
16 */
17
18public class ISO9797Alg3Mac
19    implements Mac
20{
21    private byte[]              mac;
22
23    private byte[]              buf;
24    private int                 bufOff;
25    private BlockCipher         cipher;
26    private BlockCipherPadding  padding;
27
28    private int                 macSize;
29    private KeyParameter        lastKey2;
30    private KeyParameter        lastKey3;
31
32    /**
33     * create a Retail-MAC based on a CBC block cipher. This will produce an
34     * authentication code of the length of the block size of the cipher.
35     *
36     * @param cipher the cipher to be used as the basis of the MAC generation. This must
37     * be DESEngine.
38     */
39    public ISO9797Alg3Mac(
40            BlockCipher     cipher)
41    {
42        this(cipher, cipher.getBlockSize() * 8, null);
43    }
44
45    /**
46     * create a Retail-MAC based on a CBC block cipher. This will produce an
47     * authentication code of the length of the block size of the cipher.
48     *
49     * @param cipher the cipher to be used as the basis of the MAC generation.
50     * @param padding the padding to be used to complete the last block.
51     */
52    public ISO9797Alg3Mac(
53        BlockCipher         cipher,
54        BlockCipherPadding  padding)
55    {
56        this(cipher, cipher.getBlockSize() * 8, padding);
57    }
58
59    /**
60     * create a Retail-MAC based on a block cipher with the size of the
61     * MAC been given in bits. This class uses single DES CBC mode as the basis for the
62     * MAC generation.
63     * <p>
64     * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
65     * or 16 bits if being used as a data authenticator (FIPS Publication 113),
66     * and in general should be less than the size of the block cipher as it reduces
67     * the chance of an exhaustive attack (see Handbook of Applied Cryptography).
68     *
69     * @param cipher the cipher to be used as the basis of the MAC generation.
70     * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
71     */
72    public ISO9797Alg3Mac(
73        BlockCipher     cipher,
74        int             macSizeInBits)
75    {
76        this(cipher, macSizeInBits, null);
77    }
78
79    /**
80     * create a standard MAC based on a block cipher with the size of the
81     * MAC been given in bits. This class uses single DES CBC mode as the basis for the
82     * MAC generation. The final block is decrypted and then encrypted using the
83     * middle and right part of the key.
84     * <p>
85     * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
86     * or 16 bits if being used as a data authenticator (FIPS Publication 113),
87     * and in general should be less than the size of the block cipher as it reduces
88     * the chance of an exhaustive attack (see Handbook of Applied Cryptography).
89     *
90     * @param cipher the cipher to be used as the basis of the MAC generation.
91     * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
92     * @param padding the padding to be used to complete the last block.
93     */
94    public ISO9797Alg3Mac(
95        BlockCipher         cipher,
96        int                 macSizeInBits,
97        BlockCipherPadding  padding)
98    {
99        if ((macSizeInBits % 8) != 0)
100        {
101            throw new IllegalArgumentException("MAC size must be multiple of 8");
102        }
103
104        if (!(cipher instanceof DESEngine))
105        {
106            throw new IllegalArgumentException("cipher must be instance of DESEngine");
107        }
108
109        this.cipher = new CBCBlockCipher(cipher);
110        this.padding = padding;
111        this.macSize = macSizeInBits / 8;
112
113        mac = new byte[cipher.getBlockSize()];
114
115        buf = new byte[cipher.getBlockSize()];
116        bufOff = 0;
117    }
118
119    public String getAlgorithmName()
120    {
121        return "ISO9797Alg3";
122    }
123
124    public void init(CipherParameters params)
125    {
126        reset();
127
128        if (!(params instanceof KeyParameter))
129        {
130            throw new IllegalArgumentException(
131                    "params must be an instance of KeyParameter");
132        }
133
134        // KeyParameter must contain a double or triple length DES key,
135        // however the underlying cipher is a single DES. The middle and
136        // right key are used only in the final step.
137
138        KeyParameter kp = (KeyParameter)params;
139        KeyParameter key1;
140        byte[] keyvalue = kp.getKey();
141
142        if (keyvalue.length == 16)
143        { // Double length DES key
144            key1 = new KeyParameter(keyvalue, 0, 8);
145            this.lastKey2 = new KeyParameter(keyvalue, 8, 8);
146            this.lastKey3 = key1;
147        }
148        else if (keyvalue.length == 24)
149        { // Triple length DES key
150            key1 = new KeyParameter(keyvalue, 0, 8);
151            this.lastKey2 = new KeyParameter(keyvalue, 8, 8);
152            this.lastKey3 = new KeyParameter(keyvalue, 16, 8);
153        }
154        else
155        {
156            throw new IllegalArgumentException(
157                    "Key must be either 112 or 168 bit long");
158        }
159
160        cipher.init(true, key1);
161    }
162
163    public int getMacSize()
164    {
165        return macSize;
166    }
167
168    public void update(
169            byte        in)
170    {
171        int         resultLen = 0;
172
173        if (bufOff == buf.length)
174        {
175            resultLen = cipher.processBlock(buf, 0, mac, 0);
176            bufOff = 0;
177        }
178
179        buf[bufOff++] = in;
180    }
181
182
183    public void update(
184            byte[]      in,
185            int         inOff,
186            int         len)
187    {
188        if (len < 0)
189        {
190            throw new IllegalArgumentException("Can't have a negative input length!");
191        }
192
193        int blockSize = cipher.getBlockSize();
194        int resultLen = 0;
195        int gapLen = blockSize - bufOff;
196
197        if (len > gapLen)
198        {
199            System.arraycopy(in, inOff, buf, bufOff, gapLen);
200
201            resultLen += cipher.processBlock(buf, 0, mac, 0);
202
203            bufOff = 0;
204            len -= gapLen;
205            inOff += gapLen;
206
207            while (len > blockSize)
208            {
209                resultLen += cipher.processBlock(in, inOff, mac, 0);
210
211                len -= blockSize;
212                inOff += blockSize;
213            }
214        }
215
216        System.arraycopy(in, inOff, buf, bufOff, len);
217
218        bufOff += len;
219    }
220
221    public int doFinal(
222            byte[]  out,
223            int     outOff)
224    {
225        int blockSize = cipher.getBlockSize();
226
227        if (padding == null)
228        {
229            //
230            // pad with zeroes
231            //
232            while (bufOff < blockSize)
233            {
234                buf[bufOff] = 0;
235                bufOff++;
236            }
237        }
238        else
239        {
240            if (bufOff == blockSize)
241            {
242                cipher.processBlock(buf, 0, mac, 0);
243                bufOff = 0;
244            }
245
246            padding.addPadding(buf, bufOff);
247        }
248
249        cipher.processBlock(buf, 0, mac, 0);
250
251        // Added to code from base class
252        DESEngine deseng = new DESEngine();
253
254        deseng.init(false, this.lastKey2);
255        deseng.processBlock(mac, 0, mac, 0);
256
257        deseng.init(true, this.lastKey3);
258        deseng.processBlock(mac, 0, mac, 0);
259        // ****
260
261        System.arraycopy(mac, 0, out, outOff, macSize);
262
263        reset();
264
265        return macSize;
266    }
267
268
269    /**
270     * Reset the mac generator.
271     */
272    public void reset()
273    {
274        /*
275         * clean the buffer.
276         */
277        for (int i = 0; i < buf.length; i++)
278        {
279            buf[i] = 0;
280        }
281
282        bufOff = 0;
283
284        /*
285         * reset the underlying cipher.
286         */
287        cipher.reset();
288    }
289}
290