65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于 .NET 的智能卡框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (34投票s)

2007年1月4日

CPOL

12分钟阅读

viewsIcon

548573

downloadIcon

31865

描述了一个用于 .NET 编程智能卡应用程序的 XML 框架。

引言

第一部分 中,我描述了如何使用 C# 编写一组类来封装 Windows 的 PC/SC API。PC/SC 是一组用于与智能卡通信的 API。即使这些类比 PC/SC 函数更容易使用,您仍然需要编写一些不总是易于理解的代码。与智能卡的通信使用一种名为 APDU 的协议来向卡发送命令,这些命令被称为 APDU 命令。我提出的框架旨在简化智能卡应用程序的开发。大多数情况下,您需要链接多个命令来执行诸如读取或写入文件之类的操作。通过我在此描述的框架,您可以轻松地链接命令,然后使用 XML 描述来描述应用程序的智能卡元素。

背景

本文假设您已阅读了本文的 第一部分,并且对 XML 有一定的了解。即使不是必需的,对智能卡编程的基本了解也会有所帮助。

APDU 命令协议

大多数人都有手机,因此每天都在使用智能卡。例如,当您在手机上输入 PIN 码或读取 SIM 卡目录中的电话号码时,您的手机会向卡发送一组命令来执行操作。APDU 命令是一组发送到卡的字节,卡将以一个代码并可能以您想要读取的数据进行响应。

APDU 命令描述

APDU 命令基本上由以下字节组成

APDU 字节 字节长度 描述
1 类字节
Ins 1 指令字节
P1 1 参数 1
P2 1 参数 2
P3 1 此参数是发送数据的长度或期望数据的长度
Data N 发送到卡的数据

卡将以字节数组响应命令。

字节 长度 描述
Data 0 到 255 响应数据
SW1, SW2 2 命令状态

APDU 命令总是返回至少两个字节,即状态字节。当命令返回一些数据时,状态字节是命令返回的最后两个字节。

有五种不同的配置用于向卡发送命令。它们分为两组:发送数据的命令和接收数据的命令。

发送数据的 APDU

无数据发送,无数据接收

CLASS
INS
P1
P2
P3
SW1
SW2
lgth = 0
90
00

将数据发送到卡

CLASS
INS
P1
P2
P3
具有长度 datalgth 的 DATA
SW1
SW2
datalgth
90
00

如果 datalgth = 0,则发送 256 字节到卡。

在正常执行中,这些命令将以 9000 或错误代码响应。

接收数据的 APDU

接收已知长度的数据

CLASS
INS
P1
P2
P3
具有长度 datalgth 的 DATA
SW1
SW2
datalgth
90
00

接收未知长度的数据

当命令请求卡返回未知字节数的数据时,您必须首先发送长度为 0 的命令以获取卡将返回的字节数。然后,您可以使用小于或等于给定长度的请求长度调用 GET RESPONSE 命令。

1 - 以请求长度 0 发送命令。

CLASS
INS
P1
P2
P3
SW1
SW2
0
9F
lgth

2 - 发送 GET RESPONSEreq_lgth < lgth

CLASS
INS
P1
P2
P3
具有长度 req_lgth <= lgth 的 DATA
SW1
SW2
C0
0
0
req_lgth
90
00

发送数据和接收已知或未知长度的数据

这种情况与前一种非常相似。区别在于您首先发送一个将数据传输到卡的命令。该命令以 9FXX 代码进行响应,其中 XX 表示使用 GET RESPONSE 命令可以读取的最大数据长度。

1 - 将数据发送到卡

CLASS
INS
P1
P2
P3
具有长度 datalgth 的 DATA
SW1
SW2
datalgth
90
00

2 - 发送 GET RESPONSEreq_lgth < lgth

CLASS
INS
P1
P2
P3
具有长度 req_lgth <= lgth 的 DATA
SW1
SW2
C0
0
0
req_lgth
90
00

SIM 卡命令

可以发送到 SIM 卡的命令在名为 3GPP TS 11.11 (又名 GSM 11.11) 的规范中有描述。我不会在此描述所有命令和 SIM 卡文件。如果您对此感兴趣,可以在 此处 找到 GSM1111 的版本。

以下是 SIM 卡命令的简短列表

GSM 的类字节是 **A0**,S 表示数据发送到卡,R 表示数据从卡接收。所有字节值均以十六进制给出。

命令
INS
p1
P2
P3
S/R
SELECT
A4
00
00
02
S/R
STATUS
F2
00
00
lght
R
READ BINARY
B0
offset_high
offset_low
lgth
R
UPDATE BINARY
D6
offset_high
offset_low
lgth
S
READ RECORD
B2
rec_No
模式
lgth
R
UPDATE RECORD
DC
rec_No
模式
lgth
S
VERIFY CHV
20
00
CHV_No
08
S
CHANGE CHV
24
00
CHV_No
10
S
RUN GSM ALGORITHM
88
00
00
10
S/R
GET RESPONSE
C0
00
00
lgth
R

现在,让我们看看如何编写一个 XML 框架来简化智能卡应用程序的编写。

APDU 命令的 XML 框架

基本思想是提供一个简单灵活的 XML 框架来描述 APDU 命令,并编写一个可以是一系列命令或序列的应用程序。命令是描述的原子单元,它可以单独使用,也可以从序列中使用,如果需要将参数传递给命令本身。

APDU 命令

原子 APDU 命令由 XML 元素表示。APDU 命令被组装在 ApduList 文档中。ApduListApdu 元素由以下模式定义

<xs:schema attributeFormDefault="unqualified" 
        elementFormDefault="qualified" 
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="ApduList">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="Apdu">
          <xs:complexType>
            <xs:attribute name="Name" type="xs:string" use="required" />
            <xs:attribute name="Class" type="xs:string" use="required" />
            <xs:attribute name="Ins" type="xs:string" use="required" />
            <xs:attribute name="P1" type="xs:unsignedByte" use="required" />
            <xs:attribute name="P2" type="xs:unsignedByte" use="required" />
            <xs:attribute name="P3" type="xs:string" use="required" />
            <xs:attribute name="Data" type="xs:string" use="optional" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

命令文件包含一组预定义的 APDU 命令。在加载 APDU 命令列表后,可以通过名称播放命令。有不同类型的命令;有些命令可以单独播放,有些需要使用前一个命令的结果。

P3 参数表示命令的预期数据长度时,其值可以是前一个命令调用的结果,或者命令必须重播以获取此可变值。以下是参数接受的语法,用于处理此情况

P3 = "R,0:SW1?xx"

R 表示在第一次调用 P3=0 后,必须在 SW1 值满足条件的情况下重播命令。如果 SW1 == xx,则命令将以 P3 = SW2 重播。

<Apdu Name="Get OTP ID" Class="A0" Ins="1A" 
     P1="80" P2="2" Lc="0" Le="R,0:SW1?6C" Data="" />

P3 = "R,xx:DRyy"

R 表示必须重播命令,但这次没有条件。第一次调用时,P3 = xx,然后命令重播为 P3 = xx + RespData[yy](响应数据的第 yy 个数据,第一个数据索引在规范中为 1)。

<Apdu Name="Get Status" Class="A0" Ins="F2" P1="0" 
      P2="0" Lc="0" Le="R,13:DR13" Data="" />

P3 = "SW2"

如果上一个命令的调用中的 SW1 == 0x9F,则使用上一个命令的 P3 = SW2 来播放此命令。

<Apdu Name="Get Response" Class="A0" Ins="C0" P1="0" P2="0" P3="SW2" />

P3 = "DRxx"

如果前一个命令有数据,则 xx 用作响应数据中的索引以获取 P3 的值。Le = RespData[xx]

<Apdu Name="Read Binary" Class="A0" Ins="B0" P1="0" P2="0" P3="DR15" />

命令序列

命令序列用于链接原子 APDU 命令或序列。序列可以使用传递给其组成命令的参数。SequenceList 描述了一组可以在应用程序中调用的 Sequence 元素。以下模式描述了 Sequence 元素的 SequenceList

<xs:schema attributeFormDefault="unqualified" 
        elementFormDefault="qualified" 
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="SequenceList">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="Sequence">
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" name="Command">
                <xs:complexType>
                  <xs:attribute name="Apdu" 
                        type="xs:string" use="optional" />
                  <xs:attribute name="P2" 
                        type="xs:unsignedByte" use="optional" />
                  <xs:attribute name="Data" 
                        type="xs:string" use="optional" />
                  <xs:attribute 
                        name="Sequence" type="xs:string" use="optional" />
                  <xs:attribute name="P1" 
                        type="xs:string" use="optional" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
            <xs:attribute name="Name" type="xs:string" use="required" />
            <xs:attribute name="PIN" type="xs:string" use="optional" />
            <xs:attribute name="Record" 
                        type="xs:unsignedByte" use="optional" />
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Sequence 元素允许您将参数传递给命令或其他 SequenceSequence 使用一个子元素 CommandCommand 用于调用带有或不带参数的 APDU。当在 Command 中提供参数时,它将覆盖 APDU 元素的默认参数。

例如,如果您想验证 PIN 码,可以使用以下 Sequence

<Sequence Name="Verify CHV1" PIN="FFFFFFFFFFFFFFFF">
  <Command Apdu="Verify CHV" P2="1" Data="PIN"/>
</Sequence>

用于调用此序列的类允许您为 PIN 参数提供一个值。当 Command 被执行时,参数 Data 将获得为参数 PIN 提供的​​值。Sequence 参数可以取任何名称。在此版本中,参数的值是一个字符串,表示一组字节值。

调用此 Sequence 的代码如下

SequenceParameter seqParam = new SequenceParameter();

// Process Apdu: VerifyCHV
Console.WriteLine("Sequence: Verify CHV1");

seqParam.Add("PIN", "31323334FFFFFFFF");
apduResp = player.ProcessSequence("Verify CHV1", seqParam);
Console.WriteLine(apduResp.ToString());

现在我们已经看到了如何声明命令并使用 Sequence 元素将它们链接起来,我将描述利用这个简单的“XML 语言”的代码。

APDUPlayer:一个用于 XML 描述的 C# 类

为了使用前面描述的 XML 格式,我开发了一个简单的 C# 类,该类能够处理 APDU 命令或 APDU 序列。

APDUPlayer 类有三个构造函数,并公开了一些公共方法。两个最重要的方法是用于执行 APDU 命令或 APDU 序列的方法。

以下方法用于执行单个 APDU 命令。APDUParam 参数可用于修改从 XML 描述中读取的 APDU 参数。

/// <summary>
/// Process a simple APDU command, Parameters
/// can be provided in the APDUParam object
/// </summary>
/// <param name="command">APDU command name</param>
/// <param name="apduParam">Parameters for the command</param>
/// <returns>An APDUResponse object with the response of the card </returns>
public APDUResponse ProcessCommand(string apduName, APDUParam apduParam);

此其他方法用于执行 APDU 序列。SequenceParameter 类是参数/值对的列表,用作要播放的序列的输入参数。

/// <summary>
/// Process an APDU sequence and execute each
/// of its commands in the sequence order
/// </summary>
/// <param name="apduSequenceName">Name of the sequence to play</param>
/// <param name="seqParam">An array of SequenceParam
/// object used as parameters for the sequence</param>
/// <returns>APDUResponse object of the last command executed</returns>
public APDUResponse ProcessSequence(string apduSequenceName, 
                    SequenceParameter seqParam);

使用此方法之前已显示过。

读取 SIM 卡电话簿的示例应用程序

在我之前的文章中,我们使用我描述的 PC/SC 包装器读取了 SIM 卡的电话簿,但该应用程序仅获取电话簿文件 (6F3A) 的原始内容。在此示例中,我们将解释每个记录的数据。电话记录的内容如下

此 EF 包含缩写拨号号码 (ADN)。此外,它还包含相关网络/运营商能力和扩展记录的标识符。它也可能包含相关的 alpha 标签。

标识符:6F3A

记录长度:X + 14 字节
字节 描述 强制/可选 长度
1 到 X Alpha 标识符 O X 字节
X + 1 电话号码字符串长度 M 1 字节
X + 2 TON & NPI M 1 字节
X + 3 到 X + 12 电话号码字符串 M 10 字节
X + 13 CCP 标识符 M 1 字节
X + 14 扩展 1 记录

此规范摘自 GSM11.11 文档,该文档规定了 SIM 卡的逻辑。我们将从该描述中提取我们需要获取电话号码及其相关名称的信息。

前几个字节是在手机上与电话号码一起显示的名称。默认情况下,它是一个接近 ASCII 编码的字符串。

接下来的十四个字节包含电话号码本身以及一些描述字节。第一个字节是电话号码的字节长度,包括 TON & NPI 字节,该字节指示号码是国家号 (Ax, 8x) 还是国际号 (9x)。

该组的最后十个字节包含电话号码本身。编码是 BCD,字节顺序相反。如果数字位数是奇数,则最后一个字节包含 F 和数字。例如,数字 **015648327** 的编码方式为:**10658423F7**。

记录的最后两个字节包含很少使用的数据。

PhoneNumber 类是一个辅助类,用于解释电话号码记录的字节。它的使用方式如下

PhoneNumber phone = new PhoneNumber(apduResp.Data);
Console.WriteLine("ADN n°" + nI.ToString());
Console.WriteLine(phone.ToString());

ToString 方法以以下格式获取电话记录内容的字符串:<name> : <number>。

ReadPhonebook 程序是一个控制台应用程序,默认情况下读取 ADN 文件的前十个电话号码。您可以通过将其作为参数传递给程序来读取更多记录。

命令行:ReadPhonebook P <pincode> <nbRecord>

参数是可选的。

  • P <pincode>:提供给卡的 PIN 码。如果您不提供此参数,则不会向卡提供 PIN 码。
  • <nbRecord>:要读取的记录数。默认情况下,程序读取 10 条记录。

示例

  • ReadPhonebook P 1234 25,将提供 1234 作为 PIN 并读取 25 条记录。
  • ReadPhonebook 50,读取 50 条记录,不提供 PIN。
  • ReadPhonebook P 4567,提供 4567 作为 PIN 并读取 10 条记录。

APDExchange 应用程序

APDUExchange Windows Forms 应用程序是一个基本的 C# 应用程序,可用于向卡发送任何 APDU 命令。可以键入命令或使用 APDUList 文件预加载命令。

该应用程序会根据需要自动检测卡的插入或移除。它使用上一篇文章中描述的智能卡 API 的 NativeCard 实现。

Exchange APDU 窗体如下所示

如果您想发送的命令不在 APDU 列表中,您只需使用前面描述的格式将命令添加到文件中即可。

关注点

我在智能卡行业工作了相当长一段时间,在网上很少找到关于这个主题的文章。随着智能卡如今的广泛应用,甚至一些计算机都配备了嵌入式智能卡读卡器,我认为演示一个简单的 XML 框架如何简化智能卡应用程序的开发会很有趣。您可以根据需要使用此代码并扩展框架,我希望这些文章已经向您展示了使用智能卡可能非常简单。

历史

  • 2007 年 3 月 5 日 - 更新了 ExchangeAPDU 应用程序以显示插入卡的 ATR 值。另一个改进是为选定的读卡器激活卡事件,如果您的 PC 上安装了多个读卡器。错误消息以十六进制显示,使用 errorlookup 更容易理解其含义。
  • 2011 年 8 月 16 日 - 添加了 VS 2010 的项目更新
  • 2011 年 8 月 26 日 - 为 Visual Studio 2008、2010 添加了 64 位项目
© . All rights reserved.