我正在尝试在 c# 中实现 PACE PIN 通用映射。 在协议的最后,我们需要通过这样做来计算代币:
• 从 Hmap 派生会话密钥 Kenc 和 Kmac
• 计算代币:TPICC = MAC(Kmac , PKPCD,map), TPCD = MAC(Kmac, PKPICC,map)
我已经完成了第一点,获得了密钥 Kenc = SHA1(Kshared || 00000001) 和密钥 Kmac = SHA1(Kshared || 00000002)。 现在我必须完成第二点,其中包括计算将发送到芯片的令牌:
我实现了在这里找到的方法: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;
}
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 9303,p中获取。应用程序 G-8/G-9 并与公钥一起对应于 OID 和密钥的 ASN.1 编码,请参阅此处。