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

如何简单有效地访问智能卡

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (15投票s)

2008年1月18日

CPOL

4分钟阅读

viewsIcon

242577

downloadIcon

29127

使用一组类来访问智能卡读卡器,通过脚本测试智能卡 - 甚至可以逐个发送 APDU。

引言

本文提供了两个演示项目,向您展示如何在 Windows 中简单有效地访问智能卡。如果您不熟悉该领域,这是一个学习的好机会。

基本演示非常简单,它可以向您展示如何列出系统中的读卡器、连接/断开连接、获取卡的 ATR,以及与卡进行一次 APDU 传输。

注意:基本演示只接受没有非数字字符的单个 APDU,例如:“0084000008”(从卡中获取 8 字节的挑战)。

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

  • 有一个 APDU 处理器:它可以处理用户输入。
    • 带空格分隔的单个 APDU:“0084 0000 08”
    • 多个 APDU:“0084 0000 08;00A4 0000 02 3F;”
    • 甚至是 APDU 的脚本文件:“c:\myapdu.txt
  • ATR 分析,我们演示了协议:T=0 或 T=1。
  • 多 APDU 控制台:这个控制台对于这类测试很有用。
    • 如果出现问题,您可以提示用户“按任意键继续”。
    • 您可以通过按住左键并拖动矩形来选择控制台文本,释放左键 - 文本就会复制到您的剪贴板中 - 就像 Windows 的 cmd.exe 一样,但这也适用于 Win9x ;-)

如果您想知道它们是如何运行的,您应该准备一个智能卡读卡器或安装一个虚拟智能卡读卡器,以及至少一张智能卡。

但是,如果没有,还有多 APDU 控制台供您在项目中特定场合使用脚本,我想。

背景

自 2004 年以来,我已经在许多项目中使用了智能卡,但真的找不到有用的类来访问 CodeProject 或互联网上的其他地方的智能卡。因此,我决定编写几个类来向您展示我们通常的做法。

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

要使用这些 API,我们应该调用

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

其中,4 项是可选的。

使用代码

正如我们所见,基本演示具有核心类 CSCardmgr。而且,它的接口非常简单。

// Get the readers' list
BOOL SCardGetPcscList();

// open the reader using the index
BOOL SCardOpen(int nInx);

// Get ATR from the card.
BOOL SCardReset(LPCTSTR strResp);

// Transmit APDU
BOOL SCardTransmit(LPCTSTR strApdu, LPCTSTR strResp, UINT *nSW);

// Close the connected reader
BOOL SCardClose();

这里有两个非常有用的函数,用于字符串和十六进制之间的转换,来自我的好领导之一 Yu 先生。

///////////////////////////////////////////////////////////////////////////////
// hex to asc: 0x22 -> "22"
int Hex2Asc(char *Dest,char *Src,int SrcLen)
{
    int i;
    for ( i = 0; i < SrcLen; i ++ )
    {
        sprintf(Dest + i * 2,"%02X",(unsigned char)Src[i]);
    }
    Dest[i * 2] = 0;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// asc to hex: "22" -> 0x22
int Asc2Hex(char *Dest,char *Src,int SrcLen)
{
    int i;
    for ( i = 0; i < SrcLen / 2; i ++ )
    {
        sscanf(Src + i * 2,"%02X",(unsigned char *)&Dest[i]);
    }
    return TRUE;
}

对于高级演示,我们改进了 ATR 处理和 APDU 处理 - 这些在智能卡应用中很有用。首先,ATR 处理:我们仅提取智能卡支持的协议,并在 ATR 字符串的末尾显示其协议。

///////////////////////////////////////////////////////////////////////////////
// Get the ATR information of the open Reader
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;                // Prot.
    BYTE        protocol = ATR_PROTOCOL_TYPE_T0;
    
    ZeroMemory(szATR, sizeof(szATR));

    if(m_SCard.SCardReset(szATR))
    // Do Reset it
    {
        int nLenAtr = strlen(szATR);

        // Convert ATR to Hex format
        Asc2Hex((char*)byATR, szATR, nLenAtr);                

        CAtr            objATR(byATR, nLenAtr/2);

        // get prots supported.
        objATR.ATR_GetNumberOfProtocols(&np);        
        INT nType = objATR.ATR_GetProtocolType(np, &protocol);

        sprintf(szFullAtr, "%s (T=%d).", szATR, protocol);

        // Display the ATR Information
        SetDlgItemText(IDS_MSG, szFullAtr);            
    }
}

其次,我们提供了 CApduProcesserCConsoleWindow 来改善 APDU 传输体验:用户输入更加灵活,如前所述,我们为此使用 CApduProcesser。我们使用此代码块来去除非数字字符。

// Eat the non-numeric chars.
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(;;)
    {
        /* get an input event */
        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:初始版本。
  • 2008-04-12:添加了虚拟 PC/SC 驱动程序(virtual-pcsc_drv.zip),用于使用/测试这些演示。

有关驱动程序的许可,请参阅 readme.txt。:: 建议在虚拟机中使用此驱动程序。

© . All rights reserved.