首页 文章

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

提问于
浏览
4

最近我一直在使用智能卡来保存一些信息,我想在这里实现的是通过任何Android智能手机使用智能卡读卡器从这些智能卡中获取这些数据 . 我一直在使用HID OMNIKEY 3021 USB智能卡读卡器来读取这些卡(而且我知道这个读卡器可以通过Windows应用程序使用这些卡,因为我亲自测试了这个)

现在,Android提供了USB Host,可以在Android智能手机支持的情况下读取任何USB主机 .

我正在尝试使用USB Host提供的这些类来访问此卡内的数据 .

我的代码检测任何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 ,它给出了设备的信息,例如:

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 device 从卡中获取数据和详细信息,但我没有成功这样做,我找不到任何有用的帖子 .

我知道我必须使用 UsbInterfaceUsbEndpointUsbDeviceConnection 从卡中获取我想要的东西,但我无法这样做 .

此外,我无法找到任何样品或类似的东西 . 有人能指出我正确的方向吗?

对不起,长篇文章也谢谢提前:)

EDIT : 感谢Mr. Michael Roland,当读卡器设备通过USB接口说出CCID时,我能够阅读有关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 00 但我仍然不确定如何处理数据字段:ATR或如何为 PC_to_RDR_XfrBlock 命令形成 Command APDU ..

我想我应该这样做

发送命令APDU包装到PC_to_RDR_XfrBlock命令中

现在;谁能帮我这个?

EDIT 2: 我弄清楚ATR的含义以及如何形成命令APDU .

但现在我应该切换协议

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

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

2 回答

  • 1

    典型的USB智能卡读卡器实现USB CCID device class specification . 因此,您需要在应用程序中实现该协议,以便与读取器(和卡)进行通信 . 有关如何实现它的(部分工作)起点,请参阅Communicate with smartcard reader through Android USB host .

  • 1

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

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

    //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(智能卡命令) .

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

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

    要获取 endpoints ,请参阅下面的代码段:

    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;
                        }
                    }
                }
            }
    

    现在您有了批量输入和批量输出 endpoints 来发送和接收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));
        }
    }
    

    要获得/阅读回复,请参阅下面的代码段:

    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 变量中,从上面的代码可以用来从同一个 RDR_to_PC_DataBlock 获取 Data field: ATR

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

    Check this website用于解析 ATR .

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

    在我的情况下 PPS/PTS 用于从 T0 切换到 T1 的示例是: "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 块接收的详细信息形成此命令,然后使用 write() 方法发送命令,然后使用 read() 方法读取响应 .

    另外,对于选择任何文件或发送任何命令,您可以使用 PC_to_RDR_XfrBlock 使用 write() 方法发送,然后使用 read() 方法从代码接收响应 . 您还可以在read()方法中更改要读取的bty的数量 .

    请记住使用线程进行通信,并使用read here获取更多提示 .

相关问题