我试图在C#PACE PIN通用映射中实现. 在协议的最后,我们需要这样做来计算令牌:

• Derive session keys Kenc and Kmac from Hmap

• 计算令牌:TPICC = MAC(Kmac,PKPCD,map),TPCD = MAC(Kmac,PKPBC,map)

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

  • 计算令牌:TPICC = MAC(Kmac,PKPCD,map),TPCD = MAC(Kmac,PKPBC,map)

我在这里找到了一个方法:AES CMAC Calculation C# 但是当我试图计算已知结果的MAC时,我没有找到相同的值,这些值甚至不是相同的大小."

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  

100

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.这结束了www.example.com这个

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

下面是我的代码:

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

推荐答案

id-PACE-ECDH-GM-AES-CBC-CMAC-128的身份验证令牌(参见PlatinumReader日志(log))是一个CMAC(参见Doc 9303p. 15),这可以通过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

这将返回T_PCD个身份验证令牌0x3e9348fcca2c5ddc的预期值.

前缀prefix_hex可以取自文档Doc 9303p. App G-8/G-9,并且与公钥一起对应于前缀和密钥的ASN.1编码,参见here.

Csharp相关问答推荐

为什么我在PuppeteerSharp中运行StealthPlugin时会出现错误?

等待限制选项似乎不适用于频道

什么是通过反射创建类的泛型接口方法的正确方法?

通过EFCore上传大量数据.

在发布表单时绑定包含附加(嵌套)列表的对象列表的正确语法是什么

Unity中的刚体2D运动

ASP.NET Core AutoMapper:如何解决错误 CS0121调用在以下方法或属性之间不明确

C#DateTime.ParseExact不使用特定日期

单元测试:模拟返回空

BlockingCollection T引发意外InvalidOperationException

如何从另一个类的列表中按ID取值

为什么我不能从我的异步任务方法中返回异步任务方法?

.NET8->;并发词典总是比普通词典快...怎么回事?[包含基准结果和代码]

将FileStream的特定部分作为字节数组读取

如何让游戏对象在切换场景时被销毁,但在开始新游戏时重新锁定

C#中类库项目的源代码生成器

用MongoDB c#驱动程序删除和返回嵌套数组中的文档

如何使用.NET 8.0中新的CompositeFormat类?

从HTML元素获取 colored颜色

无效的Zip文件-Zip存档