智能卡技术因其安全性和便携性,在许多领域得到广泛应用。本文旨在通过两个示例项目,展示如何在Windows系统中简单而有效地访问智能卡。如果对这一领域不太熟悉,本文将是一个很好的学习机会。
基本示例非常简单,它展示了如何在系统中列出智能卡读卡器、连接/断开读卡器、获取卡片的ATR(Answer To Reset),以及传输一个APDU(Application Protocol Data Unit)命令给卡片。
需要注意的是,基本示例只接受没有非数字字符的单个APDU,例如:"0084000008"(从卡片获取8字节的挑战)。
高级示例在基本层之上展示了如何更有效地使用智能卡:
如果想运行这些示例,需要准备一个智能卡读卡器,或者安装一个虚拟智能卡读卡器,并且至少需要一张智能卡。如果没有这些设备,也可以使用多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);
}
}
其次,提供了CApduProcesser
和CConsoleWindow
来改进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:第一版。