VC中PC/SC智能卡接口的编程
[摘要]本文介绍了如何在VC中通过PC/SC接口实现对智能卡读写器的操作,并给出了详细的例子代码。
[关键词] 智能卡、PC/SC、智能卡读写器
1 引言
完整的智能卡应用系统由后台服务程序、主机或终端应用程序和智能卡等组成。其中,后台服务程序提供了支持智能卡的服务。例如,在一个电子付款系统中,后台服务程序可以提供到信用卡和帐户信息的访问;主机或终端应用程序一般存在于台式机或者终端、电子付款终端、手机或者一个安全子系统中,终端应用程序要处理用户、智能卡和后台服务程序之间的通讯;智能卡则存储用户的一些信息。
终端应用程序需要通过读卡器来访问智能卡,在一个系统中,通常存在多家厂商提供的读卡器,因此需要一个统一的读卡器设备驱动接口。
    随着智能卡的广泛应用,为解决计算机与各种读卡器之间的互操作性问题,人们提出了PC/SCPersonal Computer/Smart Card)规范,PC/SC规范作为读卡器和卡与计算机之间有一个标准接口,实现不同生产商的卡和读卡器之间的互操作性,其独立于设备的 API使得应用程序开发人员不必考虑当前实现形式和将来实现形式之间的差异,并避免了由于基本硬件改变而引起的应用程序变更,从而降低了软件开发成本。
Microsoft在其Platform SDK中实现了PC/SC,作为连接智能卡读卡器与计算机的一个标准模型,提供了独立于设备的 API,并与Windows平台集成。因此,我们可以用PC/SC接口来访问智能卡。
2 PC/SC概述
    PC/SC接口包含30多个以Scard为前缀的函数,所有函数的原型都在winscard.h中声明,应用程序需要包含winscard.lib,所有函数的正常返回值都是SCARD_S_SUCCESS。在这30多个函数中,常用的函数只有几个,与智能卡的访问流程对应,下面将详细介绍这些常用函数。
3 PC/SC的使用
3.1建立资源管理器的上下文
    函数ScardEstablishContext()用于建立将在其中进行设备数据库操作的资源管理器上下文(范围)。
函数原型:LONG SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1, LPCVOID pvReserved2, LPSCARDCONTEXT phContext);
    各个参数的含义:
1dwScope:输入类型;表示资源管理器上下文范围,取值为:      SCARD_SCOPE_USER(在用户域中完成设备数据库操作)、SCARD_SCOPE_SYSTEM(在系统域中完成  设备数据库操作)。要求应用程序具有相应的操作权限。
2pvReserved1:输入类型;保留,必须为NULL
3pvReserved2:输入类型;保留,必须为NULL
4phContext:输出类型;建立的资源管理器上下文的句柄。
下面是建立资源管理器上下文的代码:
SCARDCONTEXT    hSC;
LONG    lReturn;
lReturn = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hSC);
if ( lReturn!=SCARD_S_SUCCESS )
printf("Failed SCardEstablishContext\n");
3.2 获得系统中安装的读卡器列表
    函数ScardListReaders()可以列出系统中安装的读卡器的名字。
函数原型:LONG SCardListReaders(SCARDCONTEXT hContext, LPCTSTR mszGroups, LPTSTR mszReaders, LPDWORD pcchReaders);
各个参数的含义:
1hContext:输入类型;ScardEstablishContext()建立的资源管理器上下文的句柄,不能为NULL
2mszGroups:输入类型;读卡器组名,为NULL时,表示列出所有读卡器。(3mszReaders:输出类型;系统中安装的读卡器的名字,各个名字之间用\0分隔,最后一个名字后面为两个连续的\0
4pcchReaders:输入输出类型;mszReaders的长度。
系统中可能安装多个读卡器,因此,需要保存各个读卡器的名字,以便以后与需要的读卡器建立连接。
下面是获得系统中安装的读卡器列表的代码:
char    mszReaders[1024];
LPTSTR    pReader, pReaderName[2];
DWORD    dwLen=sizeof(mzsReaders);
int    nReaders=0;
lReturn = SCardListReaders(hSC, NULL, (LPTSTR)mszReaders, &dwLen);
if ( lReturn==SCARD_S_SUCCESS )
{
    pReader = (LPTSTR)mszReaders;
    while (*pReader !='\0' )
{
if ( nReaders<2 ) //使用系统中前2个读卡器
pReaderName[nReaders++]=pReader;
printf("Reader: %S\n", pReader );
//下一个读卡器名
pReader = pReader + strlen(pReader) + 1;
3.3 与读卡器(智能卡)连接
函数ScardConnect()在应用程序与读卡器上的智能卡之间建立一个连接。
函数原型:LONG SCardConnect(SCARDCONTEXT hContext, LPCTSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols, LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol);
各个参数的含义:
1hContext:输入类型;ScardEstablishContext()建立的资源管理器上下文的句柄。
2szReader:输入类型;包含智能卡的读卡器名称(读卡器名称由ScardListReaders()给出)。
3dwShareMode:输入类型;应用程序对智能卡的操作方式,SCARD_SHARE_SHARED(多个应用共享同一个智能卡)、SCARD_SHARE_EXCLUSIVE(应用独占智能卡)、
SCARD_SHARE_DIRECT(应用将智能卡作为私有用途,直接操纵智能卡,不允许其它应用访问智能卡)。
4dwPreferredProtocols:输入类型;连接使用的协议,
SCARD_PROTOCOL_T0(使用T=0协议)、
SCARD_PROTOCOL_T1(使用T=1协议)。
5phCard:输出类型;与智能卡连接的句柄。
6PdwActiveProtocol:输出类型;实际使用的协议。
下面是与智能卡建立连接的代码:
SCARDHANDLE    hCardHandle[2];
DWORD    dwAP;
lReturn = SCardConnect( hContext, pReaderName[0],    SCARD_SHARE_SHARED,
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCardHandle[0], &dwAP );
if ( lReturn!=SCARD_S_SUCCESS )
{
    printf("Failed SCardConnect\n");
    exit(1);
}
与智能卡建立连接后,就可以向智能卡发送指令,与其交换数据了。
3.4 向智能卡发送指令
    函数ScardTransmit()向智能卡发送指令,并接受返回的数据。
函数原型:LONG SCardTransmit(SCARDHANDLE hCard, LPCSCARD_I0_REQUEST pioSendPci, LPCBYTE pbSendBuffer, DWORD cbSendLength, LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength);
各个参数的含义:
1hCard:输入类型;与智能卡连接的句柄。
2pioSendPci:输入类型;指令的协议头结构的指针,由SCARD_IO_REQUEST结构定义。后面是使用的协议的协议控制信息。一般使用系统定义的结构,SCARD_PCI_T0T=0协议)、 SCARD_PCI_T1T=1recv函数协议)、SCARD_PCI_RAW(原始协议)。
3pbSendBuffer:输入类型;要发送到智能卡的数据的指针。
4cbSendLength:输入类型;pbSendBuffer的字节数目。
5pioRecvPci:输入输出类型;指令协议头结构的指针,后面是使用的协议的协议控制信息,如果不返回协议控制信息,可以为NULL
6pbRecvBuffer:输入输出类型;从智能卡返回的数据的指针。
7pcbRecvLength:输入输出类型;pbRecvBuffer的大小和实际大小。
对于T=0协议,收发缓冲的用法如下:
a)向智能卡发送数据:要向智能卡发送n>0字节数据时,pbSendBuffer 4字节分别为T=0CLAINSP1P2,第5字节是n,随后是n字节的数据;cbSendLength值为n+54字节头+1字节Lc+n字节数据)。PbRecvBuffer将接收SW1SW2状态码;pcbRecvLength值在调用时至少为2,返回后为2
BYTE  recvBuffer[260];
int    sendSize, recvSize;
BTYE  sw1, sw2;
BYTE  select_mf[]={0xC0, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00};
sendSize=7;
recvSize=sizeof(recvBuffer);
lReturn = SCardTransmit(hCardHandle[0], SCARD_PCI_T0, select_mf, sendSize,
NULL, recvBuffer, &recvSize);
if ( lReturn != SCARD_S_SUCCESS )
{
  printf("Failed SCardTransmit\n");
  exit(1);
}
//返回的数据,recvSize=2
sw1=recvBuffer[recvSize-2];
sw2=recvBuffer[recvSize-1];
b)从智能卡接收数据:为从智能卡接收n>0字节数据,pbSendBuffer 4字节分别为T=0CLAINSP1P2,第5字节是n(即Le),如果从智能卡接收256字节,则第5字节为0cbSendLength值为54字节头+1字节Le)。PbRecvBuffer将接收智能卡返回的n字节,随后是SW1SW2状态码;pcbRecvLength的值在调用时至少为 n+2,返回后为n+2
BYTE  get_challenge[]={0x00, 0x84, 0x00, 0x00, 0x08};
sendSize=5;
recvSize=sizeof(recvBuffer);
lReturn = SCardTransmit(hCardHandle[0], SCARD_PCI_T0, get_challenge,
sendSize, NULL, recvBuffer, &recvSize);
if ( lReturn != SCARD_S_SUCCESS )
{
  printf("Failed SCardTransmit\n");
  exit(1);
}
//返回的数据, recvSize=10
sw1=recvBuffer[recvSize-2];
sw2=recvBuffer[recvSize-1];
//data=recvBuffer[0]----recvBuffer[7]
c)向智能卡发送没有数据交换的命令:应用程序既不向智能卡发送数据,也不从智能卡接收数据,pbSendBuffer 4字节分别为T=0CLAINSP1P2,不发送P3cbSendLength 值必须为4PbRecvBuffer从智能卡接收SW1SW2状态码;pcbRecvLength值在调用时至少为2,返回后为2
BYTE  set_flag[]={0x80, 0xFE, 0x00, 0x00};
sendSize=4;
recvSize=sizeof(recvBuffer);
lReturn = SCardTransmit(hCardHandle[0], SCARD_PCI_T0, set_flag, sendSize,
NULL, recvBuffer, &recvSize);
if ( lReturn != SCARD_S_SUCCESS )
{
    printf("Failed SCardTransmit\n");
    exit(1);
}
//返回的数据,recvSize=2
sw1=recvBuffer[recvSize-2];
sw2=recvBuffer[recvSize-1];
d)向智能卡发送具有双向数据交换的命令:T=0协议中,应用程序不能同时向智能卡发送数据,并从智能卡接收数据,即发送到智能卡的指令中,不能同时有LcLe。这只能分两步实现:向智能卡发送数据,接收智能卡返回的状态码,其中,SW2是智能卡将要返回的数据字节数目;从智能卡接收数据(指令为0x000xC00x000x00Le)。
BYTE  get_response={0x00, 0xc0, 0x00, 0x00, 0x00};
BYTE  internal_auth[]={0x00, 0x88, 0x00, 0x00, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
sendSize=13;
recvSize=sizeof(recvBuffer);
lReturn = SCardTransmit(hCardHandle[0], SCARD_PCI_T0, internal_auth,
sendSize, NULL, recvBuffer, &recvSize);
if ( lReturn != SCARD_S_SUCCESS )
{
    printf("Failed SCardTransmit\n");
    exit(1);
}
//返回的数据,recvSize=2
sw1=recvBuffer[recvSize-2];
sw2=recvBuffer[recvSize-1];
if ( sw1!=0x61 )
{
  printf("Failed Command\n");
  exit(1);
}
get_response[4]=sw2;
sendSize=5;
recvSize=sizeof(recvBuffer);
lReturn = SCardTransmit(hCardHandle[0], SCARD_PCI_T0, get_response,
sendSize, NULL, recvBuffer, &recvSize);
if ( lReturn != SCARD_S_SUCCESS )
{
printf("Failed SCardTransmit\n");
exit(1);
}
//返回的数据,recvSize=10
sw1=recvBuffer[recvSize-2];
sw2=recvBuffer[recvSize-1];
//data=recvBuffer[0]----recvBuffer[7]
3.5 断开与读卡器(智能卡)的连接
    在与智能卡的数据交换完成后,可以使用函数ScardDisconnect()终止应用与智能卡之间的连接。
函数原型:LONG SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition);
各个参数的含义:
1hCard:输入类型;与智能卡连接的句柄。
2dwDisposition:输入类型;断开连接时,对智能卡的操作,SCARD_LEAVE_CARD(不做任何操作)、
SCARD_RESET_CARD(复位智能卡)、
SCARD_UNPOWER_CARD(给智能卡掉电)、
SCARD_EJECT_CARD(弹出智能卡)。
下面是断开与智能卡连接的代码:
lReturn = SCardDisconnect(hCardHandle[0], SCARD_LEAVE_CARD);
if ( lReturn != SCARD_S_SUCCESS )
{
  printf("Failed SCardDisconnect\n");
  exit(1);
}
3.6 释放资源管理上下文
    在应用程序终止前时,应该调用函数ScardReleaseContext()释放资源管理器的上下文。
函数原型:LONG SCardReleaseContext(SCARDCONTEXT hContext);
各个参数含义:
1hContext:输入类型;ScardEstablishContext()建立的资源管理器上下文的句柄,不能为NULL
下面是释放资源管理上下文的代码:
lReturn = SCardReleaseContext(hSC);
if ( lReturn!=SCARD_S_SUCCESS )
printf("Failed SCardReleaseContext\n");
4 小结
  以上介绍的通过PC/SC来操作智能卡的流程,可以封装在一个类中。例如,我们可以设计一个类:
class CSmartReader
{
  private:
      SCARDCONTEXT      hSC;
      LONG    lReturn;
      Char  mszReaders[1024];
      LPTSTR      pReader, pReaderName[2];
      DWORD      dwLen;
      Int  nReaders, nCurrentReader;
      SCARDHANDLE    hCardHandle[2];
      DWORD  dwAP;
  public:
    CSmartReader(); //建立上下文、取读卡器列表
    ~CSmartReader(); //释放上下文
    void SetCurrentReader(int currentReader);
    int GetReaders(); //获得读卡器数目
    int ConnectReader(); //与当前读卡器建立连接
    int DisConnectReader(); //与当前读卡器断开连接
      int SendCommand(BYTE command[], int commandLength, BYTE result[], int *resultLength); //向读卡器发送命令,并接收返回的数据。返回值为sw
};
    这样,我们就可以方便地使用PC/SC接口了。
经典笑话
有一天,神创造了一头牛。
神对牛说:你要整天在田里替农夫耕田,供应牛奶给人类饮用。你要工作直至日落,而你只能吃草。我给你50年的寿命。
牛抗议道:我这么辛苦,还只能吃草,我只要20年寿命,余下的还给你。
神答应了。
第二天,神创造了猴子。
神跟猴子说:你要娱乐人类,令他们欢笑,你要表演翻斤斗,而你只能吃香蕉。我给你20年的寿命。
猴子抗议:要引人发笑,表演杂技,还要翻斤斗,这么辛苦,我活10年好了。
神答应了。


第三天,神创造了狗。
神对狗说:你要站在门口吠,吃主人吃剩的东西。我给你25年的寿命。
狗抗议道:整天坐在门口吠,我要15年好了,余下的还给你。
神答应了。
第四天,神创造了人。
神对人说:你只需要睡觉、吃东西和玩耍,不用做任何事情,只需要尽情地享受生命,我给你20年的寿命。
人抗议道:这么好的生活只有20年,太短!
神没说话。
人对神说:这样吧。牛还了30年给你,猴子还了10年,狗也还了10年,这些都给我好了,那我就能活到70岁。
神答应了。


这就是为什么我们的头20年只需吃饭、睡觉和玩耍;之后的30年,我们像一条牛整天工作养家;接着的10年,我们退休了,不得不像只猴子表演杂耍来娱乐自己的孙儿;最后的10年,整天留在家里,像一条狗坐在门口看门……

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。