Retail MAC Calculation in Java

The following java code shows Cryptographic checksum calculated using ISO/IEC 9797-1 MAC algorithm 3 with block cipher DES, zero IV (8 bytes), and ISO/IEC 9797-1 padding method 2. No external library like Bouncy Castle, used.

public byte[] retailMac(byte[] key, byte[] data) {
 int loc = 0;
 byte[] edata;
 // Create Keys
 byte[] key1 = Arrays.copyOf(key, 8);
 byte[] key2 = Arrays.copyOfRange(key, 8, 16);

 // ISO/IEC 9797-1 or ISO 7816d4 Padding for data (adding 80 00 ..)
 byte[] pdata = pad-iso-7816d4(data, 8);

 try {
  SecretKey ka = new SecretKeySpec(key1, "DES");
  Cipher cipherA = Cipher.getInstance("DES/CBC/NoPadding");
  cipherA.init(Cipher.ENCRYPT_MODE, ka, new IvParameterSpec(new byte[8]));

  SecretKey kb = new SecretKeySpec(key2, "DES");
  Cipher cipherB = Cipher.getInstance("DES/CBC/NoPadding");
  cipherB.init(Cipher.DECRYPT_MODE, kb, new IvParameterSpec(new byte[8]));

  // Encrypt block by block with Key-A
  edata = cipherA.doFinal(pdata);

  byte[] x = new byte[8];
  for (loc = 0; loc < pdata.length; loc += 8) {
    System.arraycopy(pdata, loc, x, 0, 8);
    byte[] y = xor-array(edata, x);
    edata = cipherA.doFinal(y);
  }

  // Decrypt the resulting block with Key-B
  edata = cipherB.doFinal(edata);
  // Encrypt the resulting block with Key-A
  edata = cipherA.doFinal(edata);
 } catch (Exception e) {
  e.printStackTrace();
  return null;
 }
 return edata;
} 

This is not a perfect code, like repeated XOR part can be replaced by single doFinal.

7 thoughts on “Retail MAC Calculation in Java

  1. Hey thanks for sharing the code. Have a couple of doubts –
    1. What is SSC here? You have it in line 21. Should it be there?
    2. What is data? Is it same as the the info for which MAC has to be calculated?
    3. Can you share the algorithm for padit()

    1. 1. It should be pdata (Not SSC).
      2. data – It is an argument
      3. Padding is simple. If byte array length is not 8, then pad 80,00, .. to make it exactly 8. If length is already 8, then also add extra pad 80, 00, 00, 00, 00, 00, 00, 00.

  2. I think your algo is not 100% correct. In order to obtain the same outcome as the BouncyCastle implementation I had to replace your lines 21-28 with following code:

    byte[] x = new byte[8];
    System.arraycopy(pData, loc, x, 0, 8);

    edata = cipherA.doFinal(x);

    for (loc = 8; loc < pData.length; loc += 8)
    {
    System.arraycopy(pData, loc, x, 0, 8);
    byte[] y = xor_array(edata, x);
    edata = cipherA.doFinal(y);
    }

    (note: first crypto operation is only done on the first block of the input data, the loop starts then with the second block).

    where xor_array is implemented as:

    private byte[] xor_array( byte[] aFirstArray, byte[] aSecondArray)
    {
    byte[] result = new byte[aFirstArray.length];
    for ( int i = 0; i < result.length; i++ )
    {
    result[i] = (byte) ( aFirstArray[i] ^ aSecondArray[i] );
    }
    return result;
    }

  3. Hello!

    Thank you very much for this code example.

    I am not exactly sure how to implement the pad-iso-7816d4(data, 8);

    May I ask you to share this code example as well?

    Thank you very much!

    1. Need to search the code. But here is the logic with an example:

      dataLen: 44
      blk Size: 8
      padLen: (blkSize – (dataLen % blkSize)) = 4

      Create paddedData = new byte[dataLen + padLen]
      copy data to paddedData
      set paddedData[dataLen] = 0x80, remain 3 bytes will zero by default.

      If padLen is zero, no need for any padding.

      HTH

  4. I’ve modified this to work with a 24 byte key.

    public static byte[] retailMac(byte[] key, byte[] data) {
    int loc = 0;
    byte[] edata;
    // Create Keys
    byte[] key1 = Arrays.copyOf(key, 8);
    byte[] key2 = Arrays.copyOfRange(key, 8, 16);
    byte[] key3 = key.length == 24 ? Arrays.copyOfRange(key, 16, 24) : key1;

    // ISO/IEC 9797-1 or ISO 7816d4 Padding for data (adding 80 00 ..)
    byte[] pData = Padder.padIso7816d4(data);

    try {
    SecretKey ka = new SecretKeySpec(key1, “DES”);
    Cipher cipherA = Cipher.getInstance(“DES/CBC/NoPadding”);
    cipherA.init(Cipher.ENCRYPT_MODE, ka, new IvParameterSpec(new byte[8]));

    SecretKey kb = new SecretKeySpec(key2, “DES”);
    Cipher cipherB = Cipher.getInstance(“DES/CBC/NoPadding”);
    cipherB.init(Cipher.DECRYPT_MODE, kb, new IvParameterSpec(new byte[8]));

    SecretKey kc = new SecretKeySpec(key3, “DES”);
    Cipher cipherC = Cipher.getInstance(“DES/CBC/NoPadding”);
    cipherC.init(Cipher.ENCRYPT_MODE, kc, new IvParameterSpec(new byte[8]));

    // Encrypt block by block with Key-A
    byte[] x = new byte[8];
    System.arraycopy(pData, loc, x, 0, 8);
    edata = cipherA.doFinal(x);

    //not sure what this is
    for (loc = 8; loc < pData.length; loc += 8) {
    System.arraycopy(pData, loc, x, 0, 8);
    byte[] y = xor_array(edata, x);
    edata = cipherA.doFinal(y);
    }

    // Decrypt the resulting block with Key-B
    edata = cipherB.doFinal(edata);
    // Encrypt the resulting block with Key-C
    edata = cipherC.doFinal(edata);
    } catch (Exception e) {
    //TODO: throw instead?
    e.printStackTrace();
    return null;
    }
    return edata;
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: