使用 BouncyCastle 在 C# 中进行 CMac 计算

问题描述 投票:0回答:1

我正在尝试在 c# 中实现 PACE PIN 通用映射。 在协议的最后,我们需要通过这样做来计算代币:

• 从 Hmap 派生会话密钥 Kenc 和 Kmac

• 计算代币:TPICC = MAC(Kmac , PKPCD,map), TPCD = MAC(Kmac, PKPICC,map)

我已经完成了第一点,获得了密钥 Kenc = SHA1(Kshared || 00000001) 和密钥 Kmac = SHA1(Kshared || 00000002)。 现在我必须完成第二点,其中包括计算将发送到芯片的令牌:

  • 计算代币:TPICC = MAC(Kmac , PKPCD,map), TPCD = MAC(Kmac, PKPICC,map)

我实现了在这里找到的方法:AES CMAC Calculation C# 但是当我尝试计算已知结果的 MAC 时,我没有找到相同的值,这些值甚至大小都不一样。”

这是来自 PlatinumReader 的日志,该软件已经实现了我想要在我的应用程序中实现的功能

PKPCD,地图

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,地图

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

密钥:K_MAC - 派生的 MAC 密钥(K_MAC [PACE])

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

密钥:T_PCD - PCD 计算出的身份验证令牌(T_PCD [PACE]):结果

3e 93 48 fc ca 2c 5d dc

在我这边,我只需要 TPCD,然后我将其作为请求发送到芯片,芯片通常应使用其令牌进行响应,该令牌应与我可以手动计算的 TPICC 相对应。整个过程就结束了。像这样

APDU:请求将我的令牌(tcpd)发送到芯片

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

                                       

响应:获取芯片令牌(tpicc):8b 9e 28 f9 81 be 50 86

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

这是我的代码:

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();
         }
     }
 }

我在这里调用该函数:

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

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

变换方法有:

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();
 }

还有:

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;
 }
c# aes elliptic-curve ecdh cmac
1个回答
0
投票

id-PACE-ECDH-GM-AES-CBC-CMAC-128(请参阅 PlatinumReader 日志)的身份验证令牌是 CMAC(请参阅Doc 9303第 15 页),可以通过以下方式轻松确定:充气城堡:

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

这将返回

T_PCD
身份验证令牌 0x3e9348fcca2c5ddc 的预期值。

前缀

prefix_hex
可以从文档Doc 9303p中获取。应用程序 G-8/G-9 并与公钥一起对应于 OID 和密钥的 ASN.1 编码,请参阅此处

© www.soinside.com 2019 - 2024. All rights reserved.