java - 如何以编程方式在android中使用智能卡读卡器读取智能卡/微处理器卡

标签 java android smartcard smartcard-reader

所以最近我一直在处理包含一些信息的智能卡,我在这里想要实现的是通过任何 Android 智能手机使用智能卡读卡器从这些智能卡中获取这些数据。 我一直在使用 HID OMNIKEY 3021 USB可以读取这些卡的智能卡读卡器(我知道这个读卡器可以通过 Windows 应用程序使用这些卡,因为我已经亲自测试过了)

现在安卓提供USB Host这使得读取任何 USB 主机成为可能,前提是 Android 智能手机支持它。

我正在尝试使用 USB 主机提供的这些类来访问此卡中的数据。

我检测任何 USB 主机的代码:

private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);

IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);

IntentFilter attachedFilter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED);
registerReceiver(mUsbAttachedReceiver, attachedFilter);

private final BroadcastReceiver mUsbAttachedReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Utils.writeStringToTextFile("\n1 .Get an action : " + action, FileName);
        if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
            synchronized (this) {
                device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (device != null) {
                    showToast("Plugged In");
                    mUsbManager.requestPermission(device, mPermissionIntent);
                }
            }
        } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null) {
                showToast("Plugged Out");
                // call your method that cleans up and closes communication with the device
            }
        }
    }
};

private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {

    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (ACTION_USB_PERMISSION.equals(action)) {
            synchronized (this) {
                device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if (device != null) {
                        //call method to set up device communication
                        Utils.writeStringToTextFile("2 .Get an action : " + action + "\nDevice is : " + device, FileName);
                        showToast("Permission Granted for device");

                        Handler h = new Handler();
                        h.postDelayed(run, 1000);

                    }
                } else {
                    showToast("Permission denied for device" + device);
                }
            }
        }
    }
};

一切都按预期工作,因为我得到了 UsbDevice 设备,它给出了设备信息,例如:

Device is : UsbDevice[mName=/dev/bus/usb/001/002,mVendorId=1899,mProductId=12322,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=OMNIKEY AG,mProductName=Smart Card Reader USB,mVersion=2.0,mSerialNumber=null,mConfigurations=[
UsbConfiguration[mId=1,mName=CCID,mAttributes=160,mMaxPower=50,mInterfaces=[
UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=11,mSubclass=0,mProtocol=0,mEndpoints=[
UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=24]
UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]
UsbEndpoint[mAddress=5,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]

现在我正尝试使用此 UsbDevice 设备 从卡中获取数据和详细信息,但我没有成功,而且我找不到与此相关的任何有用的帖子。

我知道我必须使用 UsbInterfaceUsbEndpointUsbDeviceConnection 来从卡中获取我想要的东西,但我做不到所以。

此外,我找不到任何样本或类似的东西。 谁能指出我正确的方向?

很抱歉发了这么长的帖子也提前感谢:)

编辑: 感谢Mr. Michael Roland ,我能够阅读有关 CCID 的信息,因为阅读器设备通过 USB 接口(interface)读取 CCID。

所以我使用了下面的代码:

        UsbDeviceConnection connection = mUsbManager.openDevice(device);
        UsbEndpoint epOut = null, epIn = null;

        for (int i = 0; i < device.getInterfaceCount(); i++) {
            UsbInterface usbInterface = device.getInterface(i);
            connection.claimInterface(usbInterface, true);

            for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
                UsbEndpoint ep = usbInterface.getEndpoint(j);
                showToast("Endpoint is : " + ep.toString() + " endpoint's type : " + ep.getType() + " endpoint's direction : " + ep.getDirection());
                Log.d(" ", "EP " + i + ": " + ep.getType());
                if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                    if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                        epOut = ep;

                    } else if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
                        epIn = ep;
                    }

                }
            }

            int dataTransferred = 0;
            byte[] PC_to_RDR_IccPowerOn = hexStringToByteArray("62" + "00000000" + "00" + "00" + "00" + "0000");

            if (epOut != null) {
                //Firstly send Power in on Bulk OUT endpoint
                dataTransferred = connection.bulkTransfer(epOut, PC_to_RDR_IccPowerOn, PC_to_RDR_IccPowerOn.length, TIMEOUT);
            }

            StringBuilder result = new StringBuilder();

            if (epIn != null) {
                final byte[] RDR_to_PC_DataBlock = new byte[epIn.getMaxPacketSize()];
                result = new StringBuilder();
                //Secondly send Power out on Bulk OUT endpoint
                dataTransferred = connection.bulkTransfer(epIn, RDR_to_PC_DataBlock, RDR_to_PC_DataBlock.length, TIMEOUT);
                for (byte bb : RDR_to_PC_DataBlock) {
                    result.append(String.format(" %02X ", bb));
                }

                if (dataTransferred > 0) {
                    Utils.writeStringToTextFile("\n2nd buffer received was : " + result.toString(), "Card_communication_data.txt");
                    String s1 = Arrays.toString(RDR_to_PC_DataBlock);
                    String s2 = new String(RDR_to_PC_DataBlock);
                    showToast("received - " + s1 + " - " + s2);
                } else {
                    showToast("received length at 2nd buffer transfer was " + dataTransferred);
                }
            }
        }

我收到了 80 13 00 00 00 00 00 00 00 00 3B 9A 96 C0 10 31 FE 5D 00 64 05 7B 01 02 31 80 90 00 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 但我仍然不确定如何处理数据字段:ATR 或如何形成 命令 APDU PC_to_RDR_XfrBlock 命令..

我觉得我应该

Send command APDU wrapped into PC_to_RDR_XfrBlock command

现在;谁能帮我解决这个问题?

编辑 2: 我弄清楚了 ATR 的含义以及如何形成命令 APDU。

但现在我应该切换协议(protocol)

The default Protocol is T=0. To set the T=1 protocol, a PTS (also known as PPS) must be sent to the card by the device As both T=0 and T=1 protocols are mandatory for the card, the basic PTS for protocol switching is mandatory for the card. The PTS can be used, as indicated in ISO/IEC 7816-3, to switch to higher baud rates than the default one proposed by the card in the ATR if any (TA(1) byte).

我不确定这意味着什么以及如何实现!!

最佳答案

由于没有适当的指南或任何示例列出人们可以遵循的基本步骤,所以这里是我设法交流的方式(这更像是一个菜鸟指南,如果我错了请纠正我): 首先使用USB Host API 我能够通过智能卡读卡器连接到智能卡。

为了连接,这里有一个 fragment 可以帮助您理解:

    //Allows you to enumerate and communicate with connected USB devices.
    UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
    //Explicitly asking for permission
    final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
    PendingIntent mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
    HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();

    UsbDevice device = deviceList.get("//the device you want to work with");
    if (device != null) {
        mUsbManager.requestPermission(device, mPermissionIntent);
    }

现在您必须了解在 Java 中,通信是使用 package javax.smarcard 进行的这不适用于 Android,因此请使用 look here了解如何通信或发送/接收 APDU(智能卡命令)。

现在如上面提到的答案所述

You cannot simply send an APDU (smartcard command) over the bulk-out endpoint and expect to receive a response APDU over the bulk-in endpoint.

要获取端点,请参阅下面的代码 fragment :

UsbEndpoint epOut = null, epIn = null;
UsbInterface usbInterface;

UsbDeviceConnection connection = mUsbManager.openDevice(device);

        for (int i = 0; i < device.getInterfaceCount(); i++) {
            usbInterface = device.getInterface(i);
            connection.claimInterface(usbInterface, true);

            for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
                UsbEndpoint ep = usbInterface.getEndpoint(j);

                if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                    if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                        // from host to device
                        epOut = ep;

                    } else if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
                        // from device to host
                        epIn = ep;
                    }
                }
            }
        }

现在您有批量输入和批量输出端点来发送和接收 APDU 命令和 APDU 响应 block :

要发送命令,请参见下面的代码 fragment :

public void write(UsbDeviceConnection connection, UsbEndpoint epOut, byte[] command) {
    result = new StringBuilder();
    connection.bulkTransfer(epOut, command, command.length, TIMEOUT);
    //For Printing logs you can use result variable
    for (byte bb : command) {
        result.append(String.format(" %02X ", bb));
    }
}

对于接收/读取响应,请参见下面的代码 fragment :

public int read(UsbDeviceConnection connection, UsbEndpoint epIn) {
result = new StringBuilder();
final byte[] buffer = new byte[epIn.getMaxPacketSize()];
int byteCount = 0;
byteCount = connection.bulkTransfer(epIn, buffer, buffer.length, TIMEOUT);

    //For Printing logs you can use result variable
    if (byteCount >= 0) {
        for (byte bb : buffer) {
            result.append(String.format(" %02X ", bb));
        }

        //Buffer received was : result.toString()
    } else {
        //Something went wrong as count was : " + byteCount
    }

    return byteCount;
}

现在如果你看到 this answer here要发送的第一个命令是:

PC_to_RDR_IccPowerOn命令激活卡。

您可以通过阅读 USB Device Class Specifications doc here 的第 6.1.1 节来创建它.

现在让我们以这个命令为例:62000000000000000000 你如何发送这个:

write(connection, epOut, "62000000000000000000");

现在,在您成功发送 APDU 命令后,您可以使用以下命令读取响应:

read(connection, epIn);

然后收到类似的东西

80 18000000 00 00 00 00 00 3BBF11008131FE45455041000000000000000000000000F1

现在这里的代码中收到的响应将在 read() 方法的 result 变量中,您可以使用上面的代码获取 Data字段:来自同一 RDR_to_PC_DataBlock

的 ATR

你必须知道/阅读如何阅读这个ATR,它可以用来检测卡的类型和其他东西,比如它是使用 T0 还是 T1 协议(protocol)那么 TA(1) 是什么,它可以告诉 FI 索引到时钟转换因子表DI 索引到波特率调整因子表 等等

Check this website用于解析 ATR

现在如果你想切换协议(protocol)说 T0T1 即发送 PPS/PTS 或者你想设置任何参数,然后您可以使用 PC_to_RDR_SetParameters 命令(document 的第 6.1.7 节)。

在我的例子中,用于从 T0 切换到 T1PPS/PTS 示例是:"61 00000007 00 00 01 0000 9610005D00FE00" .

这将给出一些结果,例如:"82 07000000 00 00 00 00 01 96 10 00 5D 00 FE 00"。您使用 RDR_to_PC_Parameters(document 的第 6.2.3 节)

检查

有关此命令的详细信息,请参阅 CCID protocol Document 的第 6.1.7 节. 我能够使用从 ATR block 接收到的详细信息来形成此命令,然后使用 write() 方法发送命令,然后使用 read() 读取响应 方法。

此外,对于选择任何文件或发送任何命令,您可以使用 PC_to_RDR_XfrBlock 使用 write() 方法发送,然后接收响应使用代码中的 read() 方法。您也可以在 read() 方法中更改要读取的 btyes 的编号。

记得使用线程进行通信,还有read here获取更多提示。

关于java - 如何以编程方式在android中使用智能卡读卡器读取智能卡/微处理器卡,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45099849/

相关文章:

android - ./Gradlew Build 需要 2 小时 40 分钟 - 是否有改进的余地?

java - 快速将文件读入字符串数组

javascript - 如何防止机器人程序和垃圾 API 请求?

java - 如何使用 apache POI 在 docx 中插入当前日期字段

android - 比较android sqlite数据库中的日期(存储为字符串)?

java - Java 3.0 智能卡 Servlet 可以与 Web 服务器交互吗?

ios - iOS是否具有用于访问智能卡的API

java - 在没有 pkcs11 的情况下通过智能卡在 java 中签署文档

java - "+="运算符在 Java 中的作用是什么?

java - Android - 构造函数变量中的空指针异常