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



所以最近我一直在使用保存一些信息的智能卡,我在这里试图实现的是使用智能卡读卡器通过任何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

我想我应该

发送命令 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) 字节)。

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

由于没有适当的指南或任何示例来列出可以遵循的基本步骤,因此这是我设法沟通的方式(这更像是菜鸟指南,如果我错了,请纠正我): 首先,使用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);
}

现在你必须明白,在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 主机与智能卡读卡器通信,了解如何实现这一点(部分工作)。

最新更新