CMac calculation in C# using BouncyCastle

97 Views Asked by At

I'm trying to implement in c# PACE PIN general Mapping. At the end of the protocol, we need to calculate tokens by doing this :

• Derive session keys Kenc and Kmac from Hmap

• Calculate token: TPICC = MAC(Kmac , PKPCD,map), TPCD = MAC(Kmac, PKPICC,map)

I have already completed the first point, obtaining a key Kenc = SHA1(Kshared || 00000001) and a key Kmac = SHA1(Kshared || 00000002). Now I have to complete the second point, which consists of calculating the token that will be sent to the chip:

  • Calculate token: TPICC = MAC(Kmac , PKPCD,map), TPCD = MAC(Kmac, PKPICC,map)

I implemented a method that I found here: AES CMAC Calculation C# But when I try to calculate the MAC of a known result, I do not find the same values, the values are not even the same size."

Here is a log from PlatinumReader, a software that has implemented what I want to implement in my application:

PKPCD,map :

04 b6 cb e1 30 44 9b 32 a6 bf e7 86 f2 56 41 a7
c9 c4 a4 99 a6 1b d4 98 69 67 2d f0 3f ba 82 07
26 14 ce d3 2b f2 1d 06 d6 f6 d0 69 6c 80 0e 8c
e4 57 79 94 1a 32 38 b9 28 f0 52 48 af 21 10 ce de  

PKPICC,map

04 b5 05 d4 67 8c 5d 7d 1a a8 7b cc c3 d4 24 82
e3 6d df 84 19 5c f9 b0 f4 bb e0 de 69 de d3 04
2c 52 ea b6 b2 64 03 88 4a ff fa 46 01 82 fc bc 
f5 bb 2a aa 64 97 ec e3 59 92 a9 45 1c ef 91 e3 02

Key: K_MAC - derived MAC key (K_MAC [PACE])

b5 fe b9 48 8f 17 be 03 e5 4c 7d 80 90 7e 8a 1f

Key: T_PCD - PCD's calculated authentication token (T_PCD [PACE]) : the result

3e 93 48 fc ca 2c 5d dc

On my side, I just need the TPCD, then I send it as a request to the chip, and the chip should normally respond with its token which should correspond to the TPICC that I can calculate manually. This ends the process.like this

APDU: Request sending my token (tcpd) to the chip

00 86 00 00 0c 7c 0a 85 08 3e 93 48 fc ca 2c 5d dc 00

                                       

Response : getting the chip token (tpicc) : 8b 9e 28 f9 81 be 50 86

7c 0a 86 08 8b 9e 28 f9 81 be 50 86 90 00

And here is my code :

byte[] AESCMAC(byte[] key, byte[] data)
 {
     // SubKey generation
     // step 1, AES-128 with key K is applied to an all-zero input block.
     byte[] L = AESEncrypt(key, new byte[8], new byte[16]);

     // step 2, K1 is derived through the following operation:
     byte[] FirstSubkey = Rol(L); //If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit.
     if ((L[0] & 0x80) == 0x80)
         FirstSubkey[15] ^= 0x87; // Otherwise, K1 is the exclusive-OR of const_Rb and the left-shift of L by 1 bit.

     // step 3, K2 is derived through the following operation:
     byte[] SecondSubkey = Rol(FirstSubkey); // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit.
     if ((FirstSubkey[0] & 0x80) == 0x80)
         SecondSubkey[15] ^= 0x87; // Otherwise, K2 is the exclusive-OR of const_Rb and the left-shift of K1 by 1 bit.

     // MAC computing
     if (((data.Length != 0) && (data.Length % 16 == 0)) == true)
     {
         // If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits),
         // the last block shall be exclusive-OR'ed with K1 before processing
         for (int j = 0; j < FirstSubkey.Length; j++)
             data[data.Length - 16 + j] ^= FirstSubkey[j];
     }
     else
     {
         // Otherwise, the last block shall be padded with 10^i
         byte[] padding = new byte[16 - data.Length % 16];
         padding[0] = 0x80;

         data = data.Concat<byte>(padding.AsEnumerable()).ToArray();

         // and exclusive-OR'ed with K2
         for (int j = 0; j < SecondSubkey.Length; j++)
             data[data.Length - 16 + j] ^= SecondSubkey[j];
     }

     // The result of the previous process will be the input of the last encryption.
     byte[] encResult = AESEncrypt(key, new byte[8], data);

     byte[] HashValue = new byte[16];
     Array.Copy(encResult, encResult.Length - HashValue.Length, HashValue, 0, HashValue.Length);

     return HashValue;
 }

 byte[] Rol(byte[] b)
 {
     byte[] r = new byte[b.Length];
     byte carry = 0;

     for (int i = b.Length - 1; i >= 0; i--)
     {
         ushort u = (ushort)(b[i] << 1);
         r[i] = (byte)((u & 0xff) + carry);
         carry = (byte)((u & 0xff00) >> 8);
     }

     return r;
 }

 byte[] AESEncrypt(byte[] key, byte[] iv, byte[] data)
 {
     using (MemoryStream ms = new MemoryStream())
     {
         AesCryptoServiceProvider aes = new AesCryptoServiceProvider();

         aes.Mode = CipherMode.CBC;
         aes.Padding = PaddingMode.None;

         using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write))
         {
             cs.Write(data, 0, data.Length);
             cs.FlushFinalBlock();

             return ms.ToArray();
         }
     }
 }

And i call the function here :

byte[] tipcc = AESCMAC(StringToByteArray("b5feb9488f17be03e54c7d80907e8a1f"), StringToByteArray("b6cbe130449b32a6bfe786f25641a7c9c4a499a61bd49869672df03fba820726"));

addLogMsg("MAC : " + byteToHexStr(tipcc));

and the transformation methods are :

public static byte[] StringToByteArray(string hex)
 {
     return Enumerable.Range(0, hex.Length)
                      .Where(x => x % 2 == 0)
                      .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                      .ToArray();
 }

And :

public string byteToHexStr(byte[] bytes)
 {
     string returnStr = "";
     if (bytes != null)
     {
         for (int i = 0; i < bytes.Length; i++)
         {
             returnStr += bytes[i].ToString("X2");
         }
     }
     return returnStr;
 }
1

There are 1 best solutions below

5
On BEST ANSWER

The authentication token for id-PACE-ECDH-GM-AES-CBC-CMAC-128 (see PlatinumReader log) is a CMAC (see Doc 9303, p. 15), which can be easily determined with BouncyCastle:

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Parameters;
using System;
...
var k_mac_hex = "b5feb9488f17be03e54c7d80907e8a1f";
var prefix_hex = "7f494f060a04007f000702020402028641";
var pk_picc_map = "04b505d4678c5d7d1aa87bccc3d42482e36ddf84195cf9b0f4bbe0de69ded3042c52eab6b26403884afffa460182fcbcf5bb2aaa6497ece35992a9451cef91e302"; // change

var k_mac = Convert.FromHexString(k_mac_hex);
var cmac_t_pcd = Convert.FromHexString(prefix_hex + pk_picc_map);

var macBlock = new CMac(new AesEngine());
macBlock.Init(new KeyParameter(k_mac));
macBlock.BlockUpdate(cmac_t_pcd, 0, cmac_t_pcd.Length);
var t_pcd = new byte[16];
macBlock.DoFinal(t_pcd, 0);

Console.WriteLine(Convert.ToHexString(t_pcd[..8]).ToLower()); // 3e9348fcca2c5ddc

This returns the expected value for the T_PCD authentication token 0x3e9348fcca2c5ddc.

The prefix prefix_hex can be taken from the document Doc 9303, p. App G-8/G-9 and corresponds together with the public key to an ASN.1 encoding of OID and key, see here.