建立 PACE PIN 身份验证后,我尝试实现安全消息传递以便能够发送安全 APDU 命令。我的实施基于 ICAO 9303 第 11 部分手册,第 9.8 章,第 63 页:https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf,
以及我使用的Java代码:https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol /pace/SecureMessaging.java,以及来自已实现安全消息传递的 PlatinumReader 的日志。 这是我的代码: APDU的计算方法:
public string chipherAPDU(string apdu, string keyEnc, string keyMac)
{
string chipherApdu = "";
byte[] apduBytes = StringToByteArray(apdu);
byte[] KeyENC = StringToByteArray(keyEnc);
byte[] KeyMAC = StringToByteArray(keyMac);
byte[] secureMessagingSSC = StringToByteArray("00000000000000000000000000000001");
SecureMessaging sm = new SecureMessaging(KeyMAC, KeyENC);
chipherApdu = byteToHexStr ( sm.encrypt(apduBytes, secureMessagingSSC) );
return chipherApdu;
}
安全消息传递类:
internal class SecureMessaging
{
private readonly byte[] NULL = new byte[] { 0x00 };
private const byte PAD = 0x80;
private readonly byte[] secureMessagingSSC;
private readonly byte[] keyMAC;
private readonly byte[] keyENC;
public SecureMessaging(byte[] keyMAC, byte[] keyENC)
{
this.keyENC = keyENC;
this.keyMAC = keyMAC;
secureMessagingSSC = new byte[16];
}
public byte[] encrypt(byte[] apdu)
{
incrementSSC(secureMessagingSSC);
byte[] commandAPDU = encrypt(apdu, secureMessagingSSC);
incrementSSC(secureMessagingSSC);
return commandAPDU;
}
public byte[] encrypt(byte[] apdu, byte[] secureMessagingSSC)
{
MemoryStream outputStream = new MemoryStream();
CardCommandAPDU cAPDU = new CardCommandAPDU(apdu);
if (cAPDU.isSecureMessaging())
{
throw new ArgumentException("Malformed APDU.");
}
byte[] data = cAPDU.data;
byte[] header = cAPDU.header;
int lc = cAPDU.lc;
int le = cAPDU.le;
if (data.Length > 0)
{
data = pad(data, 16);
using (Aes aes = Aes.Create())
{
aes.Key = keyENC;
aes.IV = getCipherIV(secureMessagingSSC);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] encryptedData = encryptor.TransformFinalBlock(data, 0, data.Length);
outputStream.Write(new byte[] { 0x01 }, 0, 1);
outputStream.Write(encryptedData, 0, encryptedData.Length);
}
}
return outputStream.ToArray();
}
public byte[] decrypt(byte[] response)
{
if (response.Length < 12)
{
throw new ArgumentException("Malformed Secure Messaging APDU.");
}
return decrypt(response, secureMessagingSSC);
}
private byte[] decrypt(byte[] response, byte[] secureMessagingSSC)
{
MemoryStream outputStream = new MemoryStream();
byte[] statusBytes = new byte[2];
byte[] dataObject = null;
byte[] macObject = new byte[8];
return outputStream.ToArray();
}
public static void incrementSSC(byte[] ssc)
{
for (int i = ssc.Length - 1; i >= 0; i--)
{
ssc[i]++;
if (ssc[i] != 0)
{
break;
}
}
}
// Other methods omitted for brevity
private byte[] pad(byte[] data, int blockSize)
{
byte[] result = new byte[data.Length + (blockSize - data.Length % blockSize)];
Array.Copy(data, 0, result, 0, data.Length);
result[data.Length] = PAD;
return result;
}
private byte[] getCipherIV(byte[] smssc)
{
using (Aes aes = Aes.Create())
{
aes.Key = keyENC;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, null);
return encryptor.TransformFinalBlock(smssc, 0, smssc.Length);
}
}
}
类 CardCommandAPDU :
public class CardCommandAPDU
{
public byte[] header = new byte[4];
public int le = -1;
public int lc = -1;
public byte[] data;
public CardCommandAPDU(byte[] commandAPDU)
{
Array.Copy(commandAPDU, 0, header, 0, 4);
setBody(copy(commandAPDU, 4, commandAPDU.Length - 4));
}
/**
* Définit le corps (LE, DATA, LC) de l'APDU.
*
* @param body Corps de l'APDU
*/
public void setBody(byte[] body)
{
/*
* Cas 1. : |CLA|INS|P1|P2|
* Cas 2. : |CLA|INS|P1|P2|LE|
* Cas 2.1: |CLA|INS|P1|P2|EXTLE|
* Cas 3. : |CLA|INS|P1|P2|LC|DATA|
* Cas 3.1: |CLA|INS|P1|P2|EXTLC|DATA|
* Cas 4. : |CLA|INS|P1|P2|LC|DATA|LE|
* Cas 4.1: |CLA|INS|P1|P2|EXTLC|DATA|LE|
* Cas 4.2: |CLA|INS|P1|P2|LC|DATA|EXTLE|
* Cas 4.3: |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
*/
try
{
using (MemoryStream stream = new MemoryStream(body))
{
int length = (int)stream.Length;
// Nettoyage
lc = -1;
le = -1;
data = new byte[0];
if (length == 0)
{
// Cas 1. : |CLA|INS|P1|P2|
}
else if (length == 1)
{
// Cas 2 |CLA|INS|P1|P2|LE|
le = stream.ReadByte() & 0xFF;
}
else if (length < 65536)
{
int tmp = stream.ReadByte();
if (tmp == 0)
{
// Cas 2.1, 3.1, 4.1, 4.3
if (stream.Length < 3)
{
// Cas 2.1 |CLA|INS|P1|P2|EXTLE|
le = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;
}
else
{
// Cas 3.1, 4.1, 4.3
lc = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;
data = new byte[lc];
stream.Read(data, 0, lc);
if (stream.Length == 1)
{
// Cas 4.1 |CLA|INS|P1|P2|EXTLC|DATA|LE|
le = stream.ReadByte() & 0xFF;
}
else if (stream.Length == 2)
{
// Cas 4.3 |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
le = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;
}
else if (stream.Length == 3)
{
if (stream.ReadByte() == 0)
{
// Cas 4.3 |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
le = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;
}
else
{
throw new ArgumentException("APDU malformée.");
}
}
else if (stream.Length > 3)
{
throw new ArgumentException("APDU malformée.");
}
}
}
else if (tmp > 0)
{
// Cas 3, 4, 4.2
lc = tmp & 0xFF;
data = new byte[lc];
stream.Read(data, 0, lc);
if (stream.Length == 1)
{
// Cas 4 |CLA|INS|P1|P2|LC|DATA|LE|
setLE((byte)stream.ReadByte());
}
else if (stream.Length == 3)
{
// Cas 4.2 |CLA|INS|P1|P2|LC|DATA|EXTLE|
stream.ReadByte(); // Ignorer le premier octet
setLE((short)((stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF));
}
else if (stream.Length == 2 || stream.Length > 3)
{
throw new ArgumentException("APDU malformée.");
}
}
else
{
throw new ArgumentException("APDU malformée.");
}
}
else
{
throw new ArgumentException("APDU malformée.");
}
}
}
catch (Exception e)
{
Console.WriteLine("Exception", e);
}
}
/**
* Définit le champ de longueur attendue (LE) de l'APDU.
*
* @param le Champ de longueur attendue (LE)
*/
public void setLE(short le)
{
if (le == (short)0x0000)
{
setLE(65536);
}
else
{
setLE((int)le & 0xFFFF);
}
}
/**
* Définit le champ de longueur attendue (LE) de l'APDU.
*
* @param le Champ de longueur attendue (LE)
*/
public void setLE(int le)
{
if (le < 0 || le > 65536)
{
throw new ArgumentException("La longueur doit être comprise entre '1' et '65535'.");
}
else
{
this.le = le;
}
}
/**
* Copie une plage de bytes.
*
* @param input l'entrée
* @param offset l'offset
* @param length la longueur
* @return le tableau de bytes
*/
public static byte[] copy(byte[] input, int offset, int length)
{
if (input == null)
{
return null;
}
byte[] tmp = new byte[length];
Array.Copy(input, offset, tmp, 0, length);
return tmp;
}
public bool isSecureMessaging()
{
return (header[0] & 0x0F) == 0x0C;
}
}
以下是我使用的 PlatinumReader 的数据,它运行良好,我正在尝试获得相同的结果:
APDU: Request: 00 a4 04 0c 07 a0 00 00 02 47 10 01
GENERIC P7896 T36084 : APDU: SMRequest - 00000000000000000000000000000001
0c a4 04 0c 1d 87 11 01 a9 48 e4 f1 a8 23 56 c1
1c f1 a6 a0 b1 8d 0c 8f 8e 08 71 b6 0b b9 3b 7a
f1 08 00
这是我使用相同值进行的测试:
string apduSM = chipherAPDU("00a4040c07a0000002471001", "3d7155f3791c313c2924f51ae60f1ac9" , "b5feb9488f17be03e54c7d80907e8a1f");
addLogMsg("ADPU SM : " + apduSM);
这是我的结果:
ADPU SM : 01A948E4F1A82356C11CF1A6A0B18D0C8F
使用的密钥是短暂的,因此我可以发布它们,这只是一个测试阶段:
密钥:K_MAC - 派生的 MAC 密钥(K_MAC [PACE]) b5 fe b9 48 8f 17 be 03 e5 4c 7d 80 90 7e 8a 1f
密钥:K_Enc - 派生加密密钥(K_Enc [PACE]) 3d 71 55 f3 79 1c 31 3c 29 24 f5 1a e6 0f 1a c9
这是测试:https://dotnetfiddle.net/l3z7P1
我有部分加密数据,我应该有这样的结构: Header Lc' '87' L '01' 'Encrypted Data' Le' 其中标题是:0c a4 04 0c
C# 代码生成的结果与参考数据之间的差异是由于 Java 参考代码到 C# 的移植不完整。
特别是,缺少 MAC 的实现和执行 ASN.1/DER 编码的
TLV
类的实现。如果将 Java 代码完全移植到 C#,则可以重现参考数据。
在以下 C# 代码中,
encrypt(byte[], byte[])
方法已补充了生成参考数据所需的 Java 代码功能(Java 参考代码的相应行在 C# 代码中指定):
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
...
public byte[] encrypt(byte[] apdu, byte[] secureMessagingSSC)
{
CardCommandAPDU cAPDU = new CardCommandAPDU(apdu);
if (cAPDU.isSecureMessaging())
{
throw new ArgumentException("Malformed APDU.");
}
byte[] data = cAPDU.data;
byte[] header = cAPDU.header;
int lc = cAPDU.lc;
int le = cAPDU.le;
byte[] dataEncrypted = [];
if (data.Length > 0)
{
data = pad(data, 16);
using (Aes aes = Aes.Create())
{
aes.Key = keyENC;
aes.IV = getCipherIV(secureMessagingSSC);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
dataEncrypted = encryptor.TransformFinalBlock(data, 0, data.Length);
// Add padding indicator 0x01
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L114
dataEncrypted = [0x01, ..dataEncrypted];
// ASN.1/DER (TLV) - encrypted data
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L117
dataEncrypted = [0x87, (byte)dataEncrypted.Length, ..dataEncrypted];
}
}
// Write protected LE: skipped as le < 0
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L123
if (le >= 0)
{
// ...
}
// Indicate Secure Messaging
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L137
header[0] |= 0x0C;
// Calculate MAC
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L142
byte[] mac = new byte[16];
CMac cmac = getCMAC(secureMessagingSSC);
byte[] paddedHeader = pad(header, 16);
cmac.BlockUpdate(paddedHeader, 0, paddedHeader.Length);
if (dataEncrypted.Length > 0)
{
byte[] paddedData = pad(dataEncrypted, 16);
cmac.BlockUpdate(paddedData, 0, paddedData.Length);
}
cmac.DoFinal(mac, 0);
mac = mac[..8];
// ASN.1/DER (TLV) - MAC
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L160
mac = [0x8e, (byte)mac.Length, ..mac];
// Concate encrypted data and MAC
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L163
byte[] secureData = [..dataEncrypted, ..mac];
// Add header and footer
// Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L165
byte[] secureCommand = [..header, (byte)secureData.Length, ..secureData, 0x00];
return secureCommand;
}
private CMac getCMAC(byte[] smssc)
{
CMac cmac = new CMac(new AesEngine());
cmac.Init(new KeyParameter(keyMAC));
cmac.BlockUpdate(smssc, 0, smssc.Length);
return cmac;
}
如果在发布的 C# 代码中替换/添加这些方法,则可以重现参考数据
0x0ca4040c1d871101a948e4f1a82356c11cf1a6a0b18d0c8f8e0871b60bb93b7af10800
。
请注意,上述更改是不完整,仅实现此特定参考数据所需的功能。为了使 C# 代码能够一般地工作,必须
完全迁移 Java 代码中的
encrypt(byte[], byte[])
方法!