智能卡访问技术在Windows中的实践

智能卡技术因其安全性和便携性,在许多领域得到广泛应用。本文旨在通过两个示例项目,展示如何在Windows系统中简单而有效地访问智能卡。如果对这一领域不太熟悉,本文将是一个很好的学习机会。

基本示例

基本示例非常简单,它展示了如何在系统中列出智能卡读卡器、连接/断开读卡器、获取卡片的ATR(Answer To Reset),以及传输一个APDU(Application Protocol Data Unit)命令给卡片。

需要注意的是,基本示例只接受没有非数字字符的单个APDU,例如:"0084000008"(从卡片获取8字节的挑战)。

高级示例

高级示例在基本层之上展示了如何更有效地使用智能卡:

  • APDU处理器:可以处理用户输入的APDU命令,支持空格分隔的单个APDU,如:"0084 0000 08"。
  • 多APDU命令:支持多个APDU命令的执行,如:"0084 0000 08; 00A4 0000 02 3F;"。
  • APDU脚本文件:支持从脚本文件中读取APDU命令,如:"c:\myapdu.txt"。
  • ATR分析:展示了T=0或T=1协议的分析。
  • 多APDU控制台:这个控制台对于测试非常有用,可以提示用户在出现问题时按任意键继续,并且可以通过鼠标选择控制台文本。

准备工作

如果想运行这些示例,需要准备一个智能卡读卡器,或者安装一个虚拟智能卡读卡器,并且至少需要一张智能卡。如果没有这些设备,也可以使用多APDU控制台在项目中运行脚本。

自2004年以来,在许多项目中使用了智能卡,但发现在CodeProject或互联网上找不到有用的类来访问它们。因此,决定编写几个类来展示通常的访问过程。

Windows的智能卡子系统中,需要知道:智能卡读卡器应该连接到系统,供应商应该提供PC/SC驱动程序,并且有一个所谓的“智能卡资源管理器”,用于Win32程序通过一组API访问各种读卡器。

要使用这些API,需要调用以下函数:

  • SCardEstablishContext():建立智能卡的上下文。
  • SCardListReaders():从资源管理器获取读卡器列表。
  • SCardConnect():通过提供读卡器名称连接读卡器。
  • SCardStatus():获取已连接读卡器中选定卡片的ATR。
  • SCardTransmit():在卡片和程序之间传输APDU。
  • SCardDisconnect():断开已连接的读卡器。
  • SCardReleaseContext():释放上下文。

其中第4个函数是可选的。

使用代码

基本示例的核心类是CSCardmgr,其接口非常简单:

BOOL SCardGetPcscList(); // 获取读卡器列表 BOOL SCardOpen(int nInx); // 使用索引打开读卡器 BOOL SCardReset(LPCTSTR strResp); // 从卡片获取ATR BOOL SCardTransmit(LPCTSTR strApdu, LPCTSTR strResp, UINT *nSW); // 传输APDU BOOL SCardClose(); // 关闭已连接的读卡器

以下是两个非常有用的函数,用于字符串与十六进制之间的转换,由领导之一Mr. Yu提供:

int Hex2Asc(char *Dest, char *Src, int SrcLen) { for (int i = 0; i < SrcLen; i++) { sprintf(Dest + i * 2, "%02X", (unsigned char)Src[i]); } Dest[SrcLen * 2] = 0; return TRUE; } int Asc2Hex(char *Dest, char *Src, int SrcLen) { for (int i = 0; i < SrcLen / 2; i++) { sscanf(Src + i * 2, "%02X", (unsigned char*)&Dest[i]); } return TRUE; }

高级示例的改进

在高级示例中,改进了ATR处理和APDU处理,这些在智能卡应用中非常有用。首先,ATR处理:提取智能卡支持的协议,并在ATR字符串的末尾显示其协议。

void CSCardDemoDlg::OnReset() { CHAR szATR[ATR_MAX_SIZE * 2 + 1] = {0}; BYTE byATR[ATR_MAX_SIZE] = {0}; CHAR szFullAtr[MAX_RESPONSE] = {0}; UINT np = 0; BYTE protocol = ATR_PROTOCOL_TYPE_T0; ZeroMemory(szATR, sizeof(szATR)); if (m_SCard.SCardReset(szATR)) { int nLenAtr = strlen(szATR); Asc2Hex((char*)byATR, szATR, nLenAtr); CAtr objATR(byATR, nLenAtr/2); objATR.ATR_GetNumberOfProtocols(&np); INT nType = objATR.ATR_GetProtocolType(np, &protocol); sprintf(szFullAtr, "%s (T=%d).", szATR, protocol); SetDlgItemText(IDS_MSG, szFullAtr); } }

其次,提供了CApduProcesserCConsoleWindow来改进APDU传输体验:用户输入更加灵活,使用CApduProcesser来处理。使用以下代码块来消除非数字字符:

for (int i = 0, j = 0; i < strlen(szSLBuf); i++) { if (szSLBuf[i] >= '0' && szSLBuf[i] <= '9') { szString[j] = szSLBuf[i]; j++; } else if (szSLBuf[i] >= 'A' && szSLBuf[i] <= 'F') { szString[j] = szSLBuf[i]; j++; } else if (szSLBuf[i] >= 'a' && szSLBuf[i] <= 'f') { szString[j] = szSLBuf[i]; j++; } else { continue; } }

CConsoleWindow用于控制台输出。它的亮点是“任意键继续”,以及鼠标选择:

void CConsoleWindow::WaitForAnyKeyEx() { // ... for (;;) { bSuccess = ReadConsoleInput(hStdIn, &inputBuffer, 1, &dwInputEvents); PERR(bSuccess, "ReadConsoleInput"); switch (inputBuffer.EventType) { case KEY_EVENT: // ... break; case MOUSE_EVENT: // ... break; } } // ... }

'KEY_EVENT'用于“任意键继续”,而'MOUSE_EVENT'则用于鼠标选择。

智能卡简介

智能卡是什么?不太清楚,但确定的是,iPhone有一个SIM卡,它就是一张智能卡。^_^

如果有一个智能卡读卡器,可以通过这个示例读取SIM卡的联系人列表。

此外,智能卡不仅仅用于读取SIM卡,PKI(公钥基础设施)也需要它:CSP(加密服务提供者)和PKCS#11都需要它(但不是必须^_^)。据所知,许多供应商已经将读卡器和卡片结合起来,这就是USB令牌;而令牌则嵌入了指纹识别器或OTP(一次性密码)。所以,这个领域非常精彩。

顺便说一下,智能卡是一种接触式卡片(受ISO7816限制)。还有所谓的非接触式卡片(受ISO14443限制),如Mifare S50/S70/DES/UL等。一些供应商还提供了所谓的双模块卡片——这些卡片有两个接口,支持ISO7816和ISO14443。

2008-01-19:第一版。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485