簡體   English   中英

如何以編程方式在android中使用智能卡讀卡器讀取智能卡/微處理器卡

[英]How to read a smart card/ microprocessor card using a smart card reader in android programmatically

所以最近我一直在使用包含一些信息的智能卡,我在這里想要實現的是通過任何 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);
                }
            }
        }
    }
};

一切都按預期工作,因為我得到了給出設備信息的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從卡中獲取我想要的東西,但我無法這樣做。

另外,我找不到任何樣品或類似的東西。 誰能指出我正確的方向?

很抱歉發了這么長的帖子也提前感謝:)

編輯:感謝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);
                }
            }
        }

我收到了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 ..

我想我應該

發送封裝到 PC_to_RDR_XfrBlock 命令中的命令 APDU

現在; 誰能幫我這個?

編輯 2:我弄清楚了 ATR 的含義以及如何形成命令 APDU。

但現在我應該切換協議

默認協議為 T=0。 要設置 T=1 協議,設備必須向卡發送 PTS(也稱為 PPS) 由於 T=0 和 T=1 協議對於卡都是強制性的,因此對於協議切換的基本 PTS 是強制性的卡片。 如 ISO/IEC 7816-3 所示,PTS 可用於切換到比卡在 ATR 中建議的默認波特率更高的波特率(如果有的話)(TA(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 中,通信是使用javax.smarcard進行的,該包不適用於 Android,因此請查看此處以了解如何通信或發送/接收 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;
                    }
                }
            }
        }

現在您有批量輸入和批量輸出端點來發送和接收 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;
}

現在,如果您在這里看到此答案,則要發送的第一個命令是:

PC_to_RDR_IccPowerOn命令激活卡。

您可以在此處閱讀 USB 設備類規范文檔的第 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

查看此網站以解析ATR

現在如果你想切換協議說T0T1即發送PPS/PTS或者你想設置任何參數,那么你可以使用PC_to_RDR_SetParameters命令( 文檔的 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檢查( 文檔的第 6.2.3 節)

有關此命令的詳細信息,請參閱CCID 協議文檔的第 6.1.7 節。 我能夠使用從ATR塊接收到的詳細信息來形成此命令,然后使用write()方法發送命令,然后使用read()方法讀取響應。

此外,對於選擇任何文件或發送任何命令,您可以使用PC_to_RDR_XfrBlock使用write()方法發送,然后使用代碼中的read()方法接收響應。 您也可以在 read() 方法中更改要讀取的 btyes 的編號。

請記住使用線程進行通信,也請閱讀此處以獲取更多提示。

典型的 USB 智能卡讀卡器執行USB CCID 設備類規范 因此,您需要在您的應用程序中實施該協議,以便與讀卡器(和卡)進行通信。 請參閱通過 Android USB 主機與智能卡讀卡器通信,了解如何實現它的(部分工作)起點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM