hce nfc
TLDR: because you can spoil NFC so badly that the user will have to reboot their device to be able to work with the NFC-related functionality of your app.
TLDR:因为您可能严重破坏NFC,以至于用户必须重新启动设备才能使用应用程序与NFC相关的功能。
But first — a little bit of history on what HCE is and why it’s being used.
但是首先-关于什么是HCE以及为什么使用它的历史。
When you tap any NFC card reader with your smartphone, the data exchange starts. The thing called NFC controller picks up the data frames coming from the reader (essentially, these data frames are just byte arrays constructed following some protocol), it reroutes them to something and then answers back to the reader. This communication can span across a series of steps.
当您用智能手机点击任何NFC读卡器时,数据交换开始。 NFC控制器负责拾取来自读取器的数据帧(本质上,这些数据帧只是按照某种协议构造的字节数组),它将它们重新路由到某些内容 ,然后返回读取器。 这种沟通可以跨越一系列步骤。
Now, the question is — what this aforementioned something is? There’re two options.
现在的问题是-这是什么东西 ? 有两种选择。
SE和HCE (SE and HCE)
First, your smartphone can be equipped with a so-called Secure Element (or SE, for short). That’s a chip that’s capable of communicating with the card reader on itself — at no point in time does it need to communicate with Android OS to perform its duties. It contains the tiny apps (sometimes called applets, but I also saw some people calling them cardlets), which can respond to the reader’s queries.
首先,您的智能手机可以配备所谓的安全元件(简称SE)。 这是一种能够自行与读卡器通信的芯片-它在任何时候都无需与Android OS通信即可执行其职责。 它包含一些微小的应用程序(有时称为小程序,但我也看到有人称它们为小卡),它们可以响应读者的查询。
These SEs are tamper-resistant, so it’s tough to get any data from them. Also, since they do not communicate with OS (remember — NFC controller reroutes the data frames directly to SE, these frames do not go through the OS), it means that even on the device with the compromised OS the data exchange between the reader and the smartphone will be secure.
这些SE具有防篡改功能,因此很难从中获取任何数据。 此外,由于它们不与OS通信(请记住-NFC控制器将数据帧直接重新路由到SE,这些帧不会通过OS),这意味着即使在操作系统受损的设备上,读取器和显示器之间的数据交换也是如此。智能手机将是安全的。
There is another way to handle the connection, though. And that is where HCE comes into play. HCE stands for Host Card Emulation — in a nutshell, this is a process of emulating the card by the code running on the host operating system (in our case, this is Android).
但是,还有另一种处理连接的方法。 这就是HCE发挥作用的地方。 HCE代表主机卡仿真—简而言之,这是通过主机操作系统(在我们的情况下为Android)上运行的代码来仿真卡的过程。
What you do to implement HCE in your code is you extend the class called HostApduService and override its method called processCommandApdu(byte[] commandApdu, Bundle extras). The commandApdu
parameter contains the data frame I mentioned before — you pick it up, you create a response and send it back to the reader. The Google Pay app on your smartphone works just like that.
在代码中实现HCE的方法是扩展名为HostApduService的类,并覆盖其名为processCommandApdu(byte [] commandApdu, Bundle extras)的方法 。 commandApdu
参数包含我之前提到的数据框-拾取它,创建响应并将其发送回阅读器。 智能手机上的Google Pay应用程序就是这样工作的。
SE看起来更安全,为什么我们还要使用HCE? (SE looks like a more secure thingy, why do we even use HCE then?)
Good question! Since the code responsible for host card emulation runs in the host OS, this means that in theory the data exchange can be compromised (in practice, though, this solution is sufficiently secure, at least on non-rooted devices; otherwise I doubt that the Google Pay would even exist in a first place). A couple of years ago, I read an article that explained it in excellent detail (I can’t find it now, unfortunately), but mostly it all boils down to the conflict of mobile carriers with Google back in 2010–2012.
好问题! 由于负责主机卡仿真的代码在主机OS中运行,因此这意味着从理论上讲,数据交换可能会受到损害(尽管实际上,该解决方案至少在非root用户的设备上是足够安全的;否则,我怀疑Google Pay甚至会排在第一位。 几年前,我读了一篇文章,对它进行了详尽的解释(不幸的是,现在找不到),但大多数情况都归结于2010-2012年移动运营商与Google的冲突。
Before you can run your applets on the Secure Elements, you need to distribute them onto the SEs somehow and make sure these applets are not compromised. You also need to be able to update these applets from time to time, and the only possible way to do that securely, according to that article, was to deliver the applets via the mobile carriers.
在可以在Secure Elements上运行小程序之前,您需要以某种方式将它们分发到SE上,并确保这些小程序不被破坏。 您还需要能够不时更新这些applet,并且根据该文章所述,安全地执行此操作的唯一可能方法是通过移动运营商交付applet。
And here’s when the conflict started — mobile carriers wanted to have their piece of the cake too, and they either refused to roll out the applets needed for a Google Pay app (which was called Google Wallet back in the day) onto the devices equipped with SEs or they set a price which was too high. The result was that Google decided that they needed their own solution for card emulation, which they could distribute along with the Android apps. So Google ended up adopting the Host Card Emulation mode, which was first implemented in the CyanogenMod OS.
这是冲突开始的时候-移动运营商也想分一杯piece,他们要么拒绝将Google Pay应用程序(在当时称为Google Wallet)所需的applets投放到配备了此功能的设备上SE或他们设定的价格过高。 结果是Google决定他们需要自己的卡仿真解决方案,可以与Android应用程序一起分发。 因此,谷歌最终采用了主机卡仿真模式,该模式首先在CyanogenMod OS中实现。
That’s how we ended up having a solution that is not as secure as it could be (but is still better than not having any solution at all).
这就是我们最终得到的解决方案不够安全的原因(但总比没有任何解决方案要好)。
很好,但是为什么我需要小心? (That’s all nice, but why do I need to be careful?)
Oh, wow, I almost forgot that I wanted to discuss a small but important finding I had back in October. Sorry for that, I love history and couldn’t resist a temptation to give a short history lesson!
哦,哇,我差点忘了我想讨论一下我十月份的一个小而重要的发现。 抱歉,我热爱历史,无法抗拒上简短的历史课!
Ok, so back in October, I’ve been working on the implementation of the HostApduService, and I noticed something weird. At some point, I realized that there was no NFC communication at all; the system just wasn’t starting my implementation of the HostApduService. The attempts to turn NFC on and off weren’t helping; the only thing that helped was rebooting the device. The only good thing was that only my own app seemed to be affected — I validated it by paying for purchase via Google Pay and NFC-initiated payment has worked just fine.
好的,所以回到十月份,我一直在研究HostApduService的实现,但我发现有些奇怪。 在某个时候,我意识到根本没有NFC通信。 系统只是没有开始我对HostApduService的实现。 开启和关闭NFC的尝试没有帮助; 唯一有用的是重新启动设备。 唯一的好处是,似乎只有我自己的应用程序受到了影响-我通过Google Pay付款进行了购买并通过NFC发起的付款很好地验证了它。
Later that evening, I realized that while I was working on that part of the code, I’ve made a small mistake, which resulted in the unhandled exception being thrown in the processCommandApdu method. I decided to check my suspicion, and it turns out I was right — if your code throws an exception from that method, the OS can not restart your implementation of the HostApduService service anymore. If you look at the system logs, here’s what you are going to see in case the card emulation does not crash:
那天傍晚,我意识到当我在代码的那一部分上工作时,我犯了一个小错误,这导致在processCommandApdu方法中引发了未处理的异常。 我决定检查一下我的怀疑,事实证明我是对的-如果您的代码从该方法引发异常, 则操作系统将无法重新启动HostApduService服务的实现。 如果您查看系统日志,以下是在卡仿真未崩溃的情况下将要看到的内容:
2019-10-25 19:19:31.587 3046-3506/? D/HostEmulationManager: notifyHostEmulationActivated
2019-10-25 19:19:31.588 3046-3506/? D/HostEmulationManager: notifyHostEmulationData
2019-10-25 19:19:31.588 3046-3506/? D/HostEmulationManager: Binding to service ComponentInfo{service name omitted}
2019-10-25 19:19:31.592 3046-3506/? D/HostEmulationManager: Waiting for new service.
2019-10-25 19:19:31.605 3046-3046/? D/HostEmulationManager: Service bound
2019-10-25 19:19:31.610 3046-3046/? D/HostEmulationManager: Sending data
2019-10-25 19:19:31.654 3046-3506/? D/HostEmulationManager: notifyHostEmulationData
2019-10-25 19:19:31.658 3046-3046/? D/HostEmulationManager: Sending data
2019-10-25 19:19:31.685 3046-3506/? D/HostEmulationManager: notifyHostEmulationData
2019-10-25 19:19:31.697 3046-3046/? D/HostEmulationManager: Sending data
2019-10-25 19:19:31.759 3046-3506/? D/HostEmulationManager: notifyHostEmulationData
2019-10-25 19:19:31.767 3046-3046/? D/HostEmulationManager: Sending data
2019-10-25 19:19:31.829 3046-3506/? D/HostEmulationManager: notifyHostEmulationData
2019-10-25 19:19:31.837 3046-3046/? D/HostEmulationManager: Sending data
2019-10-25 19:19:33.275 3046-3506/? D/HostEmulationManager: notifyHostEmulationDeactivated
2019-10-25 19:19:33.275 3046-3506/? D/HostEmulationManager: Unbinding from service ComponentInfo{service name omitted}
What we can see here is that the OS activates the host emulation, then starts a new instance of our HostApduService implementation, waits for it, binds to it when it’s ready, and then starts the data exchange. When the data exchange is done, the system deactivates the host emulation and unbinds from the service (I assume it also destroys it, but it’s not shown in the logs). If you tap the card reader with your smartphone again, you’ll see more or less the same logs.
我们在这里可以看到,操作系统激活了主机仿真,然后启动了HostApduService实现的新实例,等待它,在准备就绪时绑定到它,然后开始数据交换。 数据交换完成后,系统将停用主机仿真并取消与服务的绑定(我认为它也会破坏服务,但未在日志中显示)。 如果再次用智能手机点击读卡器,您将看到或多或少相同的日志。
But after the crash has already happened, the system logs start looking very different:
但是在崩溃已经发生之后,系统日志开始看起来非常不同:
2019-10-25 18:46:08.398 3037-18057/? D/HostEmulationManager: notifyHostEmulationActivated
2019-10-25 18:46:08.401 3037-18057/? D/HostEmulationManager: notifyHostEmulationData
2019-10-25 18:46:08.402 3037-18057/? D/HostEmulationManager: Service already bound as regular service.
2019-10-25 18:46:08.402 3037-18057/? D/HostEmulationManager: Waiting for new service.
2019-10-25 18:46:09.184 3037-18057/? D/HostEmulationManager: notifyHostEmulationDeactivated
The system activates host emulation, it tries to bind to the service, but the service is still bound. The system keeps on waiting, and then it deactivates the emulation. And there is no coming back from that limbo state unless you reboot your device; what’s even worse — there is little to nothing you can do to mitigate it after the crash has happened, you can’t notify your user — hey, it looks like NFC does not work for you, please reboot the device. Also, I managed to reproduce it on all devices that I own, OS versions 6.0, 9.0, and 10.0, so the issue seems to affect a wide range of devices.
系统激活主机仿真,尝试绑定到服务,但该服务仍被绑定。 系统保持等待状态,然后取消激活仿真。 除非您重新启动设备,否则不会从这种混乱状态中恢复过来。 更糟糕的是,崩溃发生后,您几乎无能为力地缓解它,您无法通知用户-嘿,看来NFC对您不起作用,请重新启动设备。 另外,我设法在自己拥有的所有设备(操作系统版本6.0、9.0和10.0)上重现了该文件,因此该问题似乎影响了许多设备。
As far as I remember, I dug into the Android source codes trying to figure out if that’s something that I can tackle, and I ended up neck-deep in some native C++ code. Pretty soon, I realized that Google folks should have a much better knowledge of NFC intrinsics than I do, so I ended up taking another route:
据我所记得,我研究了Android源代码,试图找出是否可以解决这个问题,最终我陷入了一些本机C ++代码的困境。 很快,我意识到Google员工应该比我更了解NFC内在知识,因此我最终走了另一条路:
First, I filed an issue in the Google Issue Tracker. As far as I can see, this issue is blocked by another one, which is not visible to non-Google employees, so there is at least some work going on in that direction (you can upvote it, though, to speed things up possibly).
首先,我在Google问题跟踪器中提交了问题 。 据我所知,这个问题被另一个问题阻止了,这对于非Google员工来说是不可见的,因此,至少有一些工作是朝着这个方向进行的(不过您可以投票,以加快工作速度) )。
And I also rechecked my own code very carefully to make sure that at no point in time does it throw the unhandled exceptions. It’s one thing to find such an issue when you’re a developer that sits comfortably in his chair looking at the logs. It’s entirely another thing when you want to pay for a ride or open the door or whatever is it that you do with your smartphone, and you find that nothing works, and you don’t know what to do.
而且我还非常仔细地重新检查了自己的代码,以确保它不会在任何时候抛出未处理的异常。 当您是一个坐在椅子上看着日志的开发人员时,发现这样的问题是一回事。 当您想花钱去兜风或开门,或者您用智能手机做什么时,这完全是另一回事,而您发现什么都行不通,也不知道该怎么办。
That’s more or less all that I wanted to share with you today. The moral of that story is simple — no one is perfect, but sometimes two imperfections meet each other and produce a very troublesome result. Check your HostApduServices if you have them, and it’ll surely contribute to the overall user experience that your clients have daily.
这就是我今天想与您分享的内容。 这个故事的寓意很简单-没有人是完美的,但有时两个缺陷会相遇并产生非常麻烦的结果。 检查您的HostApduServices(如果有),它一定会对客户每天的整体用户体验有所帮助。
hce nfc