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