C# Implementation of Retail MAC Calculation (ISOIEC 9797-1 MAC algorithm 3)

6.7k views Asked by At

I am trying to calculate the MAC using

Cryptographic checksums are calculated using ISOIEC 9797-1 MAC algorithm 3 with block cipher DES, zero IV (8 bytes), and 1S09797-1 padding method 2. The MAC length MUST be 8 bytes

from Technical Report PM for Machine Readable Travel Documents offering ICC read-only access Release : 1.1 Date : October 01. 2004.

I am using the example values from the report:

Kenc: AB 94 FD EC F2 67 4F DF B9 B3 91 F8 5D 7F 76 F2

Kmac: 79 62 D9 EC E0 3D 1A CD 4C 76 08 9D CE 13 15 43

eIFD: 72 C2 9C 23 71 CC 9B DB 65 B7 79 B8 E8 D3 7B 29 EC C1 54 AA 56 A8 79 9F AE 2F 49 8F 76 ED 92 F2

However, I am not getting the same MAC and not sure how I need to go about it. My first attempt was:

                MACTripleDES mac = new System.Security.Cryptography.MACTripleDES(Kmac);
                mac.Initialize();
                mac.Padding = PaddingMode.None;
                mac.Key = Kmac;
                mIfd = mac.TransformFinalBlock(eIfd, 0, eIfd.Length);

Result:

mIFD:1C DE 09 70 4C 0D 9B 12

Expected:

mIFD:5F 14 48 EE A8 AD 90 A7

Then I tried to manually do every step as I understand "ISO/IEC 9797-1 MAC algorithm 3 with block cipher DES, zero IV (8 bytes), and 1S09797-1 padding method 2" with the following: (I based this on Rasmus Faber's answer, but splitting the data into 64bit blocks for Iteration steps)

                byte[] key1 = new byte[8];
                Array.Copy(kMAC, 0, key1, 0, 8);
                byte[] key2 = new byte[8];
                Array.Copy(kMAC, 8, key2, 0, 8);
                Console.WriteLine("key1:{0}", Hex.BytesToSpacedHexString(key1));
                Console.WriteLine("key2:{0}", Hex.BytesToSpacedHexString(key2));

                // Plit the blocks
                byte[] d1 = new byte[8];
                byte[] d2 = new byte[8];
                byte[] d3 = new byte[8];
                byte[] d4 = new byte[8];
                Array.Copy(eIfd, 0, d1, 0, 8);
                Array.Copy(eIfd, 8, d2, 0, 8);
                Array.Copy(eIfd, 16, d3, 0, 8);
                Array.Copy(eIfd, 24, d4, 0, 8);

                DES des1 = DES.Create();
                des1.BlockSize = 64;
                des1.Key = key1;
                des1.Mode = CipherMode.CBC;
                des1.Padding = PaddingMode.None;
                des1.IV = new byte[8];

                DES des2 = DES.Create();
                des2.BlockSize = 64;
                des2.Key = key2;
                des2.Mode = CipherMode.CBC;
                des2.Padding = PaddingMode.None;
                des2.IV = new byte[8];

                // MAC Algorithm 3
                // Initial Transformation 1
                byte[] h1 = des1.CreateEncryptor().TransformFinalBlock(d1, 0, 8);
                // Iteration on the rest of blocks
                // XOR
                byte[] int2 = new byte[8];
                for (int i = 0; i < 8; i++)
                    int2[i] = (byte)(h1[i] ^ d2[i]);
                // Encrypt
                byte[] h2 = des1.CreateEncryptor().TransformFinalBlock(int2, 0, 8);
                // XOR
                byte[] int3 = new byte[8];
                for (int i = 0; i < 8; i++)
                    int3[i] = (byte)(h2[i] ^ d3[i]);
                // Encrypt
                byte[] h3 = des1.CreateEncryptor().TransformFinalBlock(int3, 0, 8);
                // XOR
                byte[] int4 = new byte[8];
                for (int i = 0; i < 8; i++)
                    int4[i] = (byte)(h3[i] ^ d4[i]);
                // Encrypt
                byte[] h4 = des1.CreateEncryptor().TransformFinalBlock(int4, 0, 8);

                // Output Transformation 3
                byte[] h4decrypt = des2.CreateDecryptor().TransformFinalBlock(h4, 0, 8);
                mIfd = des1.CreateEncryptor().TransformFinalBlock(h4decrypt, 0, 8);
                Console.WriteLine("mIFD:{0}", Hex.BytesToSpacedHexString(mIfd));

The output was:

eIFD:72 C2 9C 23 71 CC 9B DB 65 B7 79 B8 E8 D3 7B 29 EC C1 54 AA 56 A8 79 9F AE 2F 49 8F 76 ED 92 F2

key1:79 62 D9 EC E0 3D 1A CD

key2:4C 76 08 9D CE 13 15 43

Result:

mIFD:AA E3 F3 51 32 ED 34 65

Expected:

mIFD:5F 14 48 EE A8 AD 90 A7

In both cases it was different as expected. What am I missing?

Thank you for your time.

3

There are 3 answers

0
Jeff2022 On

Thanks for sharing the code. It is a great example. I have changed the code to address any size of block instead of 5 bytes only. Now in the following code, one can use from a single byte of block to any size of bytes in the block to get MAC.

    private static byte[] getCC_MACNbytes(string Key_MAC, byte[] eIFD, string Init_Vec)
    {
        byte[] Kmac = StringToByteArray(Key_MAC);

        // Split the 16 byte MAC key into two keys
        byte[] key1 = new byte[8];
        Array.Copy(Kmac, 0, key1, 0, 8);
        byte[] key2 = new byte[8];
        Array.Copy(Kmac, 8, key2, 0, 8);

        DES des1 = DES.Create();
        des1.BlockSize = 64;
        des1.Key = key1;
        des1.Mode = CipherMode.CBC;
        des1.Padding = PaddingMode.None;
        des1.IV = new byte[8];

        DES des2 = DES.Create();
        des2.BlockSize = 64;
        des2.Key = key2;
        des2.Mode = CipherMode.CBC;
        des2.Padding = PaddingMode.None;
        des2.IV = new byte[8];

        // Padd the data with Padding Method 2 (Bit Padding)
        System.IO.MemoryStream out_Renamed = new System.IO.MemoryStream();
        out_Renamed.Write(eIFD, 0, eIFD.Length);
        out_Renamed.WriteByte((byte)(0x80));
        while (out_Renamed.Length % 8 != 0)
        {
            out_Renamed.WriteByte((byte)0x00);
        }
        byte[] eIfd_padded = out_Renamed.ToArray();
        int N_bytes = eIfd_padded.Length/8;  // Number of Bytes 

        byte[] d1 = new byte[8];
        byte[] dN = new byte[8];
        byte[] hN = new byte[8];
        byte[] intN = new byte[8];

        // MAC Algorithm 3
        // Initial Transformation 1
        Array.Copy(eIfd_padded, 0, d1, 0, 8);
        hN = des1.CreateEncryptor().TransformFinalBlock(d1, 0, 8);

        // Split the blocks
        // Iteration on the rest of blocks
        for (int j = 1; j<N_bytes; j++)
        {
            Array.Copy(eIfd_padded, (8*j), dN, 0, 8);
            // XOR
            for (int i = 0; i < 8; i++)
                intN[i] = (byte)(hN[i] ^ dN[i]);

            // Encrypt
            hN = des1.CreateEncryptor().TransformFinalBlock(intN, 0, 8);
        }

        // Output Transformation 3
        byte[] hNdecrypt = des2.CreateDecryptor().TransformFinalBlock(hN, 0, 8);
        byte[] mIfd = des1.CreateEncryptor().TransformFinalBlock(hNdecrypt, 0, 8);

        //  Get check Sum CC
        return mIfd;
    }
6
Maarten Bodewes On

You are missing at least the padding mode. The ICAO technical specification uses bit padding (at least one byte valued 80, then one to seven 00 valued bytes until you reach the end of the block.

1
Cadburies On

Thanks to owlstead, the trick was that one has to pad even though the data string was exactly 32 bytes. For the people who need the full code. The code to MAC hash for

eIFD:72 C2 9C 23 71 CC 9B DB 65 B7 79 B8 E8 D3 7B 29 EC C1 54 AA 56 A8 79 9F AE 2F 49 8F 76 ED 92 F2

data string looks as follows:

                // Split the 16 byte MAC key into two keys
            byte[] key1 = new byte[8];
            Array.Copy(kMAC, 0, key1, 0, 8);
            byte[] key2 = new byte[8];
            Array.Copy(kMAC, 8, key2, 0, 8);
            Console.WriteLine("key1:{0}", Hex.BytesToSpacedHexString(key1));
            Console.WriteLine("key2:{0}", Hex.BytesToSpacedHexString(key2));

            // Padd the data with Padding Method 2 (Bit Padding)
            System.IO.MemoryStream out_Renamed = new System.IO.MemoryStream();
            out_Renamed.Write(eIfd, 0, eIfd.Length);
            out_Renamed.WriteByte((byte)(0x80));
            while (out_Renamed.Length % 8 != 0)
            {
                out_Renamed.WriteByte((byte)0x00);
            }
            byte[] eIfd_padded = out_Renamed.ToArray();
            Console.WriteLine("eIfd_padded:{0}", Hex.BytesToSpacedHexString(eIfd_padded));

            // Split the blocks
            byte[] d1 = new byte[8];
            byte[] d2 = new byte[8];
            byte[] d3 = new byte[8];
            byte[] d4 = new byte[8];
            byte[] d5 = new byte[8];
            Array.Copy(eIfd_padded, 0, d1, 0, 8);
            Array.Copy(eIfd_padded, 8, d2, 0, 8);
            Array.Copy(eIfd_padded, 16, d3, 0, 8);
            Array.Copy(eIfd_padded, 24, d4, 0, 8);
            Array.Copy(eIfd_padded, 32, d5, 0, 8);

            DES des1 = DES.Create();
            des1.BlockSize = 64;
            des1.Key = key1;
            des1.Mode = CipherMode.CBC;
            des1.Padding = PaddingMode.None;
            des1.IV = new byte[8];

            DES des2 = DES.Create();
            des2.BlockSize = 64;
            des2.Key = key2;
            des2.Mode = CipherMode.CBC;
            des2.Padding = PaddingMode.None;
            des2.IV = new byte[8];

            // MAC Algorithm 3
            // Initial Transformation 1
            byte[] h1 = des1.CreateEncryptor().TransformFinalBlock(d1, 0, 8);
            // Iteration on the rest of blocks
            // XOR
            byte[] int2 = new byte[8];
            for (int i = 0; i < 8; i++)
                int2[i] = (byte)(h1[i] ^ d2[i]);
            // Encrypt
            byte[] h2 = des1.CreateEncryptor().TransformFinalBlock(int2, 0, 8);
            // XOR
            byte[] int3 = new byte[8];
            for (int i = 0; i < 8; i++)
                int3[i] = (byte)(h2[i] ^ d3[i]);
            // Encrypt
            byte[] h3 = des1.CreateEncryptor().TransformFinalBlock(int3, 0, 8);
            // XOR
            byte[] int4 = new byte[8];
            for (int i = 0; i < 8; i++)
                int4[i] = (byte)(h3[i] ^ d4[i]);
            // Encrypt
            byte[] h4 = des1.CreateEncryptor().TransformFinalBlock(int4, 0, 8);
            // XOR
            byte[] int5 = new byte[8];
            for (int i = 0; i < 8; i++)
                int5[i] = (byte)(h4[i] ^ d5[i]);
            // Encrypt
            byte[] h5 = des1.CreateEncryptor().TransformFinalBlock(int5, 0, 8);

            // Output Transformation 3
            byte[] h5decrypt = des2.CreateDecryptor().TransformFinalBlock(h5, 0, 8);
            byte[] mIfd = des1.CreateEncryptor().TransformFinalBlock(h5decrypt, 0, 8);
            Console.WriteLine("mIFD:{0}", Hex.BytesToSpacedHexString(mIfd));