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

sha*_*ane 5 java android smartcard smartcard-reader

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

现在,Android 提供了USB 主机,只要 Android 智能手机支持它,就可以读取任何 USB 主机。

我正在尝试使用 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);
                }
            }
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

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

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]]]]
Run Code Online (Sandbox Code Playgroud)

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

我知道我必须使用UsbInterfaceUsbEndpointUsbDeviceConnection让我从卡想要的东西,但我不能这样做。

另外,我找不到任何样本或类似的东西。任何人都可以指出我正确的方向吗?

很抱歉这篇很长的帖子也提前谢谢:)

编辑: 感谢Michael Roland 先生,我能够阅读有关 CCID 的信息,因为阅读器设备通过 USB 接口说出 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);
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

我收到的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 00,但仍然我不知道如何处理数据字段做:ATR或如何形成Command APDUPC_to_RDR_XfrBlock命令..

我想我应该

发送命令 APDU 包装成 PC_to_RDR_XfrBlock 命令

现在; 谁能帮我这个?

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

但现在我应该切换协议

默认协议是 T=0。要设置 T=1 协议,设备必须向卡发送 PTS(也称为 PPS) 由于 T=0 和 T=1 协议对卡都是强制性的,因此协议切换的基本 PTS 是强制性的卡片。如 ISO/IEC 7816-3 所示,PTS 可用于切换到比卡在 ATR 中建议的默认波特率更高的波特率(如果有的话)(TA(1) 字节)。

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

sha*_*ane 7

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

对于连接,这里有一个片段可以帮助您理解:

    //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);
    }
Run Code Online (Sandbox Code Playgroud)

现在您必须了解,在 Java 中,通信是使用Android 不可用的包进行的javax.smarcard,因此请查看此处以了解如何进行通信或发送/接收 APDU(智能卡命令)。

现在正如上面提到的答案所说

您不能简单地通过批量输出端点发送 APDU(智能卡命令)并期望通过批量输入端点接收响应 APDU。

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

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;
                    }
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

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

要发送命令,请参阅下面的代码片段:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

对于接收/读取响应,请参阅下面的代码片段:

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您在此处看到此答案,则要发送的第一个命令是:

PC_to_RDR_IccPowerOn 命令激活卡。

您可以通过在此处阅读USB 设备类规范文档的第 6.1.1 节来创建。

现在让我们举一个这个命令的例子,就像这里的一样:62000000000000000000 你如何发送这个是:

write(connection, epOut, "62000000000000000000");
Run Code Online (Sandbox Code Playgroud)

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

read(connection, epIn);
Run Code Online (Sandbox Code Playgroud)

并收到类似的东西

80 18000000 00 00 00 00 00 3BBF11008131FE45455041000000000000000000000000F1

现在这里的代码中收到的响应将在上面代码中resultread()方法变量中,您可以使用它来Data field: ATR 从相同的RDR_to_PC_DataBlock

你必须知道/了解如何阅读本ATR可以用来检测卡的类型和其他类似的东西无论是使用T0和T1协议,然后什么TA(1),它可以告诉了FI Index into clock conversion factor tableDI Index into Baud rate adjustment factor table

检查此网站以解析ATR.

现在,如果你想协议发言权切换T0T1即发送PPS/PTS或者你想设置任何参数,那么你可以使用PC_to_RDR_SetParameters命令(第6.1.7文件)。

示例PPS/PTS从切换T0T1在我的情况是:"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文档的第 6.2.3 节)

有关此命令的详细信息,请参阅CCID 协议文档的第 6.1.7 节。我能够使用从ATR块接收的详细信息形成此命令,然后使用该write()方法发送该命令,然后使用该方法读取响应read()

同样对于选择任何文件或发送任何命令,您可以使用PC_to_RDR_XfrBlockto send usingwrite()方法,然后read()从代码中使用方法接收响应。您也可以在 read() 方法中更改要读取的字节数。

请记住使用线程进行通信,并阅读此处以获取更多提示。


Mic*_*and 1

典型的 USB 智能卡读卡器实现USB CCID 设备类规范。因此,您需要在应用程序中实现该协议,以便与读卡器(和卡)进行通信。请参阅通过 Android USB 主机与智能卡读卡器通信,了解如何实现该功能的(部分有效)起点。

  • 无论如何,这将是一个很长的答案,我会尝试将其发布在这里。 (2认同)