当前位置: 首页 > 知识库问答 >
问题:

如何发送命令到智能卡读卡器(而不是智能卡),而没有卡存在?

路阳华
2023-03-14

前言:

我有一个双接口智能卡读卡器,具有一些扩展功能(除了向卡发送APDU命令和接收APDU响应)。

例如,在其文档中提到,您可以使用以下命令获取读卡器的固件版本:

GET_FIRMWARE_VERSION: FF69 44 42 05 68 92 00 05 00

在它的工具,有一个按钮为这个功能,它的工作原理很好:

我甚至嗅了嗅USB端口,看看我的电脑和我的读卡器之间的连接中到底交换了什么:

问题:

我想使用其他工具或通过代码获取我的读卡器版本(可能发送其他扩展命令),但我必须在读卡器中插入一张卡才能发送命令,否则我会收到无卡存在异常,而我不想向卡发送命令!(读卡器工具成功应答以获取\u固件\u版本,而读卡器插槽中没有任何可用卡)

到目前为止我所做的:

1.我尝试了一些工具,包括OpenSCTool、PyAPDUTool和另一个读者工具。2.我写了下面的python脚本发送扩展命令。

#--- Importing required modules.
import sys
import time
sys.path.append("D:\\PythonX\\Lib\\site-packages")
from smartcard.scard import *
import smartcard.util
from smartcard.System import readers


#---This is the list of commands that we want to send device
cmds =[[,0xFF,0x69,0x44,0x42,0x05,0x68,0x92,0x00,0x04,0x00],]


#--- Let's to make a connection to the card reader
r=readers()
print "Available Readers :",r
print
target_reader = input("--- Select Reader (0, 1 , ...): ")
print

while(True):
    try:
        print "Using :",r[target_reader]
        reader = r[target_reader]
        connection=reader.createConnection()
        connection.connect()
        break
    except:
        print "--- Exception occured! (Wrong reader or No card present)"
        ans = raw_input("--- Try again? (0:Exit/1:Again/2:Change Reader)")
        if int(ans)==0:
            exit()
        elif int(ans)==2:
            target_reader = input("Select Reader (0, 1 , ...): ")

#--- An struct for APDU responses consist of Data, SW1 and SW2
class stru:
    def __init__(self):
        self.data = list()
        self.sw1 = 0
        self.sw2 = 0

resp = stru()

def send(cmds):
    for cmd in cmds:

        #--- Following 5 line added to have a good format of command in the output.
        temp = stru() ;
        temp.data[:]=cmd[:]
        temp.sw1=12
        temp.sw2=32
        modifyFormat(temp)
        print "req: ", temp.data

        resp.data,resp.sw1,resp.sw2 = connection.transmit(cmd)
        modifyFormat(resp)
        printResponse(resp)

def modifyFormat(resp):
    resp.sw1=hex(resp.sw1)
    resp.sw2=hex(resp.sw2)   
    if (len(resp.sw2)<4):
        resp.sw2=resp.sw2[0:2]+'0'+resp.sw2[2]
    for i in range(0,len(resp.data)):
        resp.data[i]=hex(resp.data[i])
        if (len(resp.data[i])<4):
            resp.data[i]=resp.data[i][0:2]+'0'+resp.data[i][2]

def printResponse(resp):
    print "res: ", resp.data,resp.sw1,resp.sw2


send(cmds)
connection.disconnect()

输出:

>>> ================================ RESTART ================================
Available Readers : ['CREATOR CRT-603 (CZ1) CCR RF 0', 'CREATOR CRT-603 (CZ1) CCR SAM 0']

--- Select Reader (0, 1 , ...): 0

Using : CREATOR CRT-603 (CZ1) CCR RF 0
--- Exception occured! (Wrong reader or No card present)
--- Try again? (0:Exit/1:Again/2:Change Reader)

>>> ================================ RESTART ================================
Available Readers : ['CREATOR CRT-603 (CZ1) CCR RF 0', 'CREATOR CRT-603 (CZ1) CCR SAM 0']

--- Select Reader (0, 1 , ...): 1

Using : CREATOR CRT-603 (CZ1) CCR SAM 0
--- Exception occured! (Wrong reader or No card present)
--- Try again? (0:Exit/1:Again/2:Change Reader)

但是两者都有上述问题!

问题:

1-在没有可用卡的情况下,如何向读卡器发送扩展命令?

2-为什么在嗅探数据中看不到命令头?(注意,由于头是所有扩展命令的预先指定的固定值,我认为读卡器工具不使用GET_FIRMWARE_VERSION命令发送头,它只发送数据!但它是如何工作的?)

更新:

通过反复试验,我发现了一些有用的东西。

假设:

  • 伪APDUs固定头=FF 69 44 42
  • GET_READER_FIRMWARE_VERSION的伪APDU数据栏=68 92 00 04 00
  • CHANGE_SAM_SLOT的伪APDU数据栏=68 92 01 00 03XX 00(我的阅读器有两个SAM插槽,所以XX可以是0102
  • 选择APDU命令=00 A4 04 00 00

好的,我编写了以下Java程序:

import java.util.List;
import java.util.Scanner;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;
import javax.xml.bind.DatatypeConverter;

public class TestPCSC {

    public static void main(String[] args) throws CardException {

        TerminalFactory tf = TerminalFactory.getDefault();
        List< CardTerminal> terminals = tf.terminals().list();
        System.out.println("Available Readers:");
        System.out.println(terminals + "\n");

        Scanner scanner = new Scanner(System.in);
        System.out.print("Which reader do you want to send your commands to? (0 or 1 or ...): ");
        String input = scanner.nextLine();
        int readerNum = Integer.parseInt(input);
        CardTerminal cardTerminal = (CardTerminal) terminals.get(readerNum);
        Card connection = cardTerminal.connect("DIRECT");
        CardChannel cardChannel = connection.getBasicChannel();

        System.out.println("Write your commands in Hex form, without '0x' or Space charaters.");
        System.out.println("\n---------------------------------------------------");
        System.out.println("Pseudo-APDU Mode:");
        System.out.println("---------------------------------------------------");
        while (true) {
            System.out.println("Pseudo-APDU command: (Enter 0 to send APDU command)");
            String cmd = scanner.nextLine();
            if (cmd.equals("0")) {
                break;
            }
            System.out.println("Command  : " + cmd);
            byte[] cmdArray = hexStringToByteArray(cmd);
            byte[] resp = connection.transmitControlCommand(CONTROL_CODE(), cmdArray);
            String hex = DatatypeConverter.printHexBinary(resp);
            System.out.println("Response : " + hex + "\n");
        }

        System.out.println("\n---------------------------------------------------");
        System.out.println("APDU Mode:");
        System.out.println("---------------------------------------------------");

        while (true) {
            System.out.println("APDU command: (Enter 0 to exit)");
            String cmd = scanner.nextLine();
            if (cmd.equals("0")) {
                break;
            }
            System.out.println("Command  : " + cmd);
            byte[] cmdArray = hexStringToByteArray(cmd);
            ResponseAPDU resp = cardChannel.transmit(new CommandAPDU(cmdArray));
            byte[] respB = resp.getBytes();
            String hex = DatatypeConverter.printHexBinary(respB);
            System.out.println("Response : " + hex + "\n");
        }

        connection.disconnect(true);

    }

    public static int CONTROL_CODE() {
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.indexOf("windows") > -1) {
            /* Value used by both MS' CCID driver and SpringCard's CCID driver */
            return (0x31 << 16 | 3500 << 2);
        } else {
            /* Value used by PCSC-Lite */
            return 0x42000000 + 1;
        }

    }

    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

}

在上面的程序中,我可以使用连接向我的阅读器发送命令。传输控制命令cardChannel。transmit()方法。关键是,所有使用第一种方法发送给读卡器的命令都假定为伪APDU命令,我不应该为它们使用Psedo APDU头!使用第二种方法发送给读卡器的所有命令都假定为常规APDU命令,因此如果需要通过第二种方法发送伪APDU命令,则必须将伪APDU头添加到其中。

让我们看看无接触阅读器的输出:

run:
Available Readers:
[PC/SC terminal ACS ACR122 0, 
PC/SC terminal CREATOR CRT-603 (CZ1) CCR RF 0,
PC/SC terminal CREATOR CRT-603 (CZ1) CCR SAM 0]

Which reader do you want to send your commands to? (0 or 1 or ...): 1
Write your commands in Hex form, without '0x' or Space charaters.

---------------------------------------------------
Pseudo-APDU Mode:
---------------------------------------------------
Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800
//Based on reader's documents, 0x6800 means "Class byte is not correct"
//As I have a regular java card in the RF field of my  reader, I conclude that 
//this response is Reader's response (and not card response)

Pseudo-APDU command: (Enter 0 to send APDU command)
6892000400
Command  : 6892000400
Response : 433630335F435A375F425F31353038323100039000

Pseudo-APDU command: (Enter 0 to send APDU command)
FF694442056892000400
Command  : FF694442056892000400
Response : 6800
//Pseudo-APDU commands doesn't work in Pseudo-APDU mode if I add the Pseudo-APDU header to them. 

Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
0

---------------------------------------------------
APDU Mode:
---------------------------------------------------
APDU command: (Enter 0 to exit)
00A4040000
Command  : 00A4040000
Response : 6F198408A000000018434D00A50D9F6E061291921101009F6501FF9000

APDU command: (Enter 0 to exit)
6892000400
Command  : 6892000400
Response : 6E00
//This is the response of my card. I can't receive Firmware version in APDU mode using this command without Pseudo-APDU header. 

APDU command: (Enter 0 to exit)
FF694442056892000400
Command  : FF694442056892000400
Response : 433630335F435A375F425F31353038323100099000
//I successfully received Firmware version in APDU mode using the fixed Pseudo-APDU header.

APDU command: (Enter 0 to exit)
00A4040000
Command  : 00A4040000
Response : 6F198408A000000018434D00A50D9F6E061291921101009F6501FF9000

APDU command: (Enter 0 to exit)
0
BUILD SUCCESSFUL (total time: 1 minute 36 seconds)

还有什么问题吗?

是的,有两个问题!:

1-上述程序仅在第一次运行时运行良好。我的意思是,如果我停止运行并重新运行它,第二个方法会抛出一个异常:

run:
Available Readers:
[PC/SC terminal ACS ACR122 0, PC/SC terminal CREATOR CRT-603 (CZ1) CCR RF 0, PC/SC terminal CREATOR CRT-603 (CZ1) CCR SAM 0]

Which reader do you want to send your commands to? (0 or 1 or ...): 1
Write your commands in Hex form, without '0x' or Space charaters.

---------------------------------------------------
Pseudo-APDU Mode:
---------------------------------------------------

Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
FF694442056892000400
Command  : FF694442056892000400
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
6892000400
Command  : 6892000400
Response : 433630335F435A375F425F31353038323100049000

Pseudo-APDU command: (Enter 0 to send APDU command)
00A4040000
Command  : 00A4040000
Response : 6800

Pseudo-APDU command: (Enter 0 to send APDU command)
0

---------------------------------------------------
APDU Mode:
---------------------------------------------------
APDU command: (Enter 0 to exit)
00A4040000
Command  : 00A4040000
Exception in thread "main" javax.smartcardio.CardException: sun.security.smartcardio.PCSCException: Unknown error 0x16
    at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:219)
    at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)
    at TestPCSC.main(TestPCSC.java:58)
Caused by: sun.security.smartcardio.PCSCException: Unknown error 0x16
    at sun.security.smartcardio.PCSC.SCardTransmit(Native Method)
    at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:188)
    ... 2 more
Java Result: 1
BUILD SUCCESSFUL (total time: 39 seconds)

正如你在上面看到的,我不能再使用第二种方法了,我需要关闭阅读器的电源,然后再次打开它,使它再次正常工作。

2-联系人界面(我指的是SAM阅读器)总是抛出以前的异常!我的意思是,第二种方法根本不起作用(既不是第一次,也不是第二次和第三次……)

请注意,我尝试了不同的读者,似乎这并不限于这个读者。一些ACS阅读器在重新运行时也有类似或完全相同的问题

有人知道吗?

作为一个附带问题,Python是否有与Java相同的发送伪APDU的方法?

最后,从读者的角度来看,连接有什么区别。传输控制命令cardChannel。传输()方法?

共有2个答案

谭宏盛
2023-03-14

Reader()是可用阅读器的数组索引

reader = r[target_reader]

转换

reader = r[int(target_reader)]

输出

Available Readers : ['JAVACOS Virtual Contact Reader 0', 'JAVACOS Virtual Contactless Reader 1', 'OMNIKEY CardMan 3x21 0']
--- Select Reader (0, 1 , ...): 2
Using : OMNIKEY CardMan 3x21 0
ATR :  3B 9E 94 80 1F 47 80 31 A0 73 BE 21 13 66 86 88 02 14 4B 10 19
安奇
2023-03-14

当您停止“智能卡”服务时,该工具是否仍返回固件版本?如果是,则工具可能使用原始IOCTL命令(DeviceIoControl)与驱动程序通信。

再看看这个问题。作者说您必须将SCARD\u PROTOCOL\u UNDEFINED设置为协议参数:

SCardConnect(hSC,
             readerState.szReader,
             SCARD_SHARE_DIRECT,
             SCARD_PROTOCOL_UNDEFINED,
             &hCH,
             &dwAP
            );

我刚刚试过,它似乎至少对Windows10有效。没有插卡就可以进行通信。不过,我没有测试其他Windows版本。

 类似资料:
  • 我已经实现了一个Andoid应用程序-服务器端应用程序。服务器与智能卡读卡器通信。当用户在Android应用程序中触摸按钮时,将构建一个连接到服务器以验证用户。应用和服务器之间交换的消息具有以下格式: 如果消息的类型值,则表明智能卡读卡器中存在错误 如果消息的类型值,则表明智能卡中存在错误 我使用如下代码与智能卡读卡器进行通信: 智能卡IO API具有异常的类。我的问题是,我不知道何时发送类型为或

  • 问题内容: 我有一个IC接触式读卡器和SLE5528智能卡。想知道如何真正开始使用这些物品。 正在读取读取器,插入智能卡后看不到任何影响。 我还从http://www.openscdp.org/安装了opensmart的智能卡外壳 但是我不能用它来读任何读卡器。我想知道它是否有兼容性问题。 请我知道我可能不恰当地提出了这个问题,但是请那里的任何人帮助我。 任何相关的链接或有用的信息都可以帮助我入门

  • 问题内容: 因此,最近我一直在使用具有一些信息的智能卡,而我想要在此处实现的目标是使用智能卡读取器通过任何Android智能手机从这些智能卡中获取此数据。我一直在使用HID OMNIKEY 3021 USB 智能卡读取器来读取这些卡(而且我知道此读取器可通过Windows应用程序与这些卡配合使用,因为我已经对此进行了亲自测试) 现在,Android提供了USB主机,只要Android智能手机支持它

  • 我使用WinSCard列出所有阅读器,这会给我一个如下列表: 这很好,但我不知道哪个名字属于哪个读卡器。 这些名字来自哪里?它们是如何建造的? 到目前为止,我发现:在注册表中有一个键。在此项下,您可以找到与读卡器的“服务”(我通过查询WMI注册表找到)匹配的子项。例如:读卡器的服务是。 此子项()具有另一个子项,该子项具有多个有趣的值: :DWORD,连接的读卡器数量 我猜这些是附加在“友好名称”

  • 市场上最准确的卡路里计数器基于您的个人数据计算消耗的卡路里数: 体重、身高、年龄、性别 个人最大心率 (HRmax) 您的训练或活动的强度 个人最大摄氧量(VO2max) 卡路里计算基于加速度与心率数据的智能组合。卡路里计算用于准确测量您在训练中消耗的卡路里。 您可以在训练期间查看累计消耗的能量(单位:千卡 (kcal)),也可以在训练后查看消耗的总卡路里数。您还可追踪每日消耗的总卡路里数。

  • 市场上最准确的卡路里计数器基于您的个人数据计算消耗的卡路里数: 体重、身高、年龄、性别 个人最大心率 (HRmax) 您的训练或活动的强度 个人最大摄氧量(VO2max) 卡路里计算基于加速度与心率数据的智能组合。卡路里计算用于准确测量您在训练中消耗的卡路里。 您可以在训练期间查看累计消耗的能量(单位:千卡 (kcal)),也可以在训练后查看消耗的总卡路里数。您还可追踪每日消耗的总卡路里数。