Как читать смарт-карту / микропроцессорную карту с помощью устройства чтения смарт-карт в Android программно

Поэтому в последнее время я работаю со смарт-картами, которые содержат некоторую информацию, и здесь я пытаюсь получить эти данные с этих смарт-карт с помощью устройства чтения смарт-карт через любой Android-смартфон. Я использовал устройство чтения смарт-карт HID OMNIKEY 3021 USB, которое считывало бы эти карты (и я знаю, что это устройство работает с этими картами через приложения Windows, потому что я лично проверил это)

Теперь Android предоставляет USB-хост, который позволяет читать любой 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 который выдает информацию об устройстве, например:

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 получить данные и данные с карты, но мне не удалось это сделать, и я не смог найти какой-либо полезный пост по этому поводу.

Я знаю, что должен использовать UsbInterface, UsbEndpoint, UsbDeviceConnection получить то, что я хочу от карты, но я не могу этого сделать.

Кроме того, я не могу найти какие-либо образцы или тому подобное для того же. Кто-нибудь может указать мне правильное направление?

Извините за длинный пост, также спасибо заранее!

РЕДАКТИРОВАТЬ: Благодаря Michael Roland, я смог прочитать о CCID, поскольку устройство чтения говорит CCID через интерфейс USB.

Поэтому я использовал следующий код:

        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 или как сформировать Command APDU за PC_to_RDR_XfrBlock команда..

Я думаю, что я должен

Отправить команду APDU, завернутую в команду PC_to_RDR_XfrBlock

сейчас; Кто-нибудь может мне с этим помочь?

РЕДАКТИРОВАТЬ 2: я понял, что означает ATR и как сформировать команду APDU.

Но теперь я должен переключить протокол

Протокол по умолчанию - T=0. Чтобы установить протокол T=1, устройство должно отправить на карту PTS (также известную как PPS). Поскольку протоколы T = 0 и T=1 являются обязательными для карты, базовый PTS для переключения протокола является обязательным для карта. PTS может использоваться, как указано в ИСО / МЭК 7816-3, для переключения на более высокие скорости в бодах, чем скорость по умолчанию, предложенная картой в ATR, если таковая имеется (байт TA(1)).

И я не уверен, что это значит и как этого добиться!!

2 ответа

Решение

Типичные устройства чтения смарт-карт USB реализуют спецификацию класса устройства USB CCID. Следовательно, вам нужно внедрить этот протокол в ваше приложение, чтобы общаться с читателем (и картой). См. Раздел " Связь со считывателем смарт-карт через USB-хост Android" для (частично работающей) отправной точки о том, как реализовать это.

Поскольку не существует надлежащего руководства или образца, в котором изложены основные шаги, которым можно следовать, вот как мне удалось общаться (это больше похоже на руководство новичков, и, пожалуйста, исправьте меня, если я ошибаюсь): во-первых, используя 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 Команда для активации карты.

которую вы можете создать, прочитав раздел 6.1.1 документа Спецификации класса USB-устройства здесь.

Теперь давайте возьмем пример этой команды, такой как здесь: 62000000000000000000 Как вы можете отправить это:

write(connection, epOut, "62000000000000000000");

Теперь, после того как вы успешно отправили команду APDU, вы можете прочитать ответ, используя:

read(connection, epIn);

И получить что-то вроде

80 18000000 00 00 00 00 00 3BBF11008131FE45455041000000000000000000000000F1

Теперь ответ, полученный в коде здесь, будет в result переменная read() метод из кода выше, который вы можете использовать для получения Data field: ATR из того же RDR_to_PC_DataBlock

Вы должны знать / читать о том, как читать это ATR который может быть использован для определения типа карты и других вещей, например, использует ли он протокол T0 или T1, тогда что такое TA(1), который может рассказать о FI Index into clock conversion factor table а также DI Index into Baud rate adjustment factor table и так далее

Проверьте этот сайт для разбора ATR,

Теперь, если вы хотите переключиться на протокол, скажите T0 в T1 т.е. отправить PPS/PTS или вы хотите установить любой параметр, то вы можете использовать PC_to_RDR_SetParameters команда (раздел 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 (раздел 6.2.3 документа)

Подробнее об этой команде см. Раздел 6.1.7 документа с протоколом CCID. Я смог сформировать эту команду, используя данные, полученные от ATR заблокировать, а затем отправить команду, используя write() метод, а затем прочитать ответ, используя read() метод.

Также для выбора любого файла или отправки любой команды вы можете использовать PC_to_RDR_XfrBlock отправить с помощью write() метод, а затем получить ответ, используя read() метод из кода. Также вы можете изменить количество файлов, которые вы хотите прочитать в методе read().

Не забудьте использовать темы для общения, а также прочитать здесь для получения дополнительных советов.

Другие вопросы по тегам