作者简介 : Henryye,叶轩,来自腾讯微信事业群,主要负责腾讯开源项目TENCENT SOTER(GitHub地址:https://github.com/Tencent/soter, 生物认证平台的开发、维护与运营。
假如有一天,产品经理安排你做指纹支付,并且要下版本就上,你会怎么做?
如果是产品大哥,就从工位下面抽出一把指甲刀架在他脖子上,让他跪在墙角唱征服;
如果是产品妹子,就让她请你喝咖啡,然后谈天说地,趁此机会告诉她“还是选择世界和平吧,比做指纹支付简单多了。”
当然,想象还是太温柔了。真正做过指纹支付项目的在下,经常会在半夜三更回忆起当年做指纹支付需求时候的噩梦,在梦里,我就给自己加戏,手撕产品经理。
也许产品大大们会发出抗议:“指纹支付而已,客户端现成的接口,有何难?”
从2013年iPhone 5s第一款带有指纹识别功能的iPhone上市以来,“指纹支付”这个词就开始频繁出现在各个产品的PM列表排期中。但是,Android 6.0以前的设备,并没有一个统一的指纹认证接口。这也就意味着如果你是一个苦逼的程序猿,那么你就要一家家适配各自的指纹方案,并且还要维护厂商的接口升级。如果结合Android市场的碎片化来看,想要全机型覆盖,简直就是Impossible Mission。实际上,在项目初期,微信便尝试了一家家接入,结果仅仅接入华为Mate 7和荣耀7,便用了整整三个月!这种投入显然是得不偿失的。
好在,从Android 6.0开始,系统提供了标准的FingerprintManager。这一重大利好,让做着类似需求的程序猿们仿佛在黑暗中看到一丝光明,因为这个接口看上去是那么简单易用。无论你是什么品牌的手机,只要是Android 6.0或更新的系统,按照下面的写法,就可以实现指纹认证功能:
FingeprintManager mFingeprintManager = ...
mFingeprintManager.authenticate(null, mCancellationSignal, 0 , new AuthenticationCallback(){...}, null);
设计本身也很简单:
系统认证接口
看上去很完美,仿佛实现指纹支付根本不用开发1个版本,只用1小时,对不对!
但是仔细看下这个接口,感觉哪里不太对:接口仅仅返回认证成功/失败,如果直接信任这个结果,手机被root了,岂不是随时可以将认证结果从false改为true?
那我们换一种思路:root的手机不让用指纹支付行不行?
傻孩子,那你怎么判断手机是不是root呢?不也是通过Android接口获取的值么?这个值一样可以被改掉。
支付安全不可儿戏,一旦出问题,就是重大事故。所幸的是,Google也意识到了这个问题,所以在发布指纹认证接口的同时,增强了原本的KeyStore接口,和Fingeprprint接口联动(代码实现可参考Google官方Sample,链接:https://github.com/googlesamples/android-FingerprintDialog):
Fingerprint安全架构
这张图看上去不明觉厉,原理其实并不难:Google在Android 6.0之后,允许用户在应用中生成一对非对称密钥,将私钥存储在TEE中(什么是TEE?稍后会讲),任何人,包括应用自己甚至Android系统都无法获取私钥,除非用户使用指纹授权才能使用,签名或者加密传入的数据,然后输出密文。这样的话,就可以利用密钥的签名-验签机制(小白不懂什么是签名验签?Google下咯~或者看这篇文章解释签名的部分),只有用户使用指纹签名之后,才能产生正确的签名,后台验签即可,这样就能保证链路安全。
这个设计非常巧妙,但是Google百密一疏:如果黑客在密钥生成的时候就拦截了请求,替换为自己的密钥,那么后面签名和加密,用的也是黑客的密钥,那么整套系统的设计也就崩塌了。
Hack示意
另外还有一个问题,如果仅仅返回true/false,那么只要是录入到设备内的指纹,就可以假冒你的身份支付。这对于家里有熊孩子的家长来说,简直就是银行卡噩梦。雪上加霜的是,对于Android设备而言(其实iOS也是一样),只要知道了锁屏密码就可以录入新的指纹。如果支付后台直接信任指纹认证结果,就相当于将原本非常秘密的支付密码,退化到了锁屏密码的级别。这样,无论支付后台做了多么严密的风控策略,按照木桶原理,从根本上整个系统就是不符合支付安全的。
当然,当时也有类似于FIDO(链接:https://fidoalliance.org/) 之类的认证联盟,但是整个流程过于复杂,甚至还要求在应用后台植入sdk。而且,类似方案的中心服务权限过高,会导致如支付笔数、开通用户数等关键指标为人所知,因此也就无法使用。并且支持设备数实在太少,也并无接入动力。
研究过这些之后,发现并不可直接使用任何一个方案,场面一度尴尬。没有合适的轮子,怎么办?
让我们回头看看Android系统的指纹接口设计:
那Google没有做到什么呢?
同时,我们意识到,在生物认证领域这个千亿级市场中,缺乏一个统一、安全、易接入的认证标准,微信有这样的需求,其他应用也必然如此。微信有能力解决这些问题,实现自己的业务需求,也希望将成功经验复制。既然这样,借此机会制定一个生物认证标准,提供一个生物认证平台,微信责无旁贷,这就是SOTER的起源。
如果以做标准的要求来实现SOTER,那么除了刚刚所述的系统接口缺陷之外,系统设计时还需要考虑:
信任根的重要性之前已经说明。如果一个系统依赖密钥签名,有一个可以信任的根密钥,才有可能构建安全的信任模型。但是,如果一台手机出厂之后才产生根密钥(ATTK),那么中间有足够时间窗口给到黑产从业者替换掉它。因此,常规方式产生根密钥一定是不行的。所以,我们有了一个大胆的想法:直接与厂商合作,在设备出厂之前,产线上生成设备根密钥,公钥通过厂商服务传输给微信密钥服务——TAM。这个想法虽然对厂商改造比较大,但是由于我们直接通产业链上游(高通、MTK等)合作,研发出了一套统一的解决方案,以及产线工具,成功说服厂商对产线做了最小化的改造。It’s tough, but we did it!
设备根密钥流程
有了设备根密钥之后,认证链的构造逻辑就清晰了很多:采用密钥链的形式,用已认证的密钥来认证未认证的密钥就可以了!
方法论有了,实施就变得简单。
但是,依然有一个问题需要思考:到底需要多少层密钥呢?密钥层数少,那么每一次都需要前往中心服务(微信TAM)验签,对第三方应用而言,会更担心泄露自己的商业逻辑;密钥层数越多,会增加了传输复杂度和失败率。经过多方讨论,SOTER决定使用三级密钥,除了产线预制的设备根密钥之外,增加定义应用密钥(每一个应用生命周期内只需要存在一对)和业务密钥(每一个业务需要一对)。事实证明,这是一个明智的选择:这样既保证了流程的流畅度,又保证应用的关键商业隐私不暴露。为什么?往下看。
SOTER架构图
我们十分欣赏Google的指纹和密钥模块接口设计,因此,我们与厂商合作,在此基础上添加patch包,即可迅速实现整个上层架构。
准备应用密钥流程示意图
传输给后台的原串示例:
注1:自设备出厂即在TEE中存储。每一次SOTER相关操作都会使该值自增,后台存储该放重放因子。如果后台发现本次请求防重放因子比已记录的值小,则可认为是非法请求。
注2:本意为Linux系统中用户ID,在Android系统中,一般而言每一个应用都有一个uid,可用于区分应用以及权限控制。注意,uid不同,对应的应用密钥与业务密钥均不同,后台应将uid与cpu_id一起区分密钥。
准备业务密钥流程示意图
传输数据与含义与应用密钥相同,不再赘述。
认证流程示意图
应用获取原串与签名串后,传输至应用后台。应用后台使用对应的业务密钥公钥验签,如果成功,则此次认证或者开通请求合法。
传输给后台原串示例
轮子造好了,我们在自我欣赏的路上越走越远。回过头来看,SOTER是否满足了我们的要求呢?
当然了,我们的方案得到了各大厂商、芯片上的认可,在短时间内,已经拥有了数亿设备的支持,覆盖几乎所有的主流手机品牌,因此应用接入完全无须考虑是否需要多设备适配,或者质疑适配不足。顺便,我们支持了vivo和OPPO的5.x指纹机型,即使系统本身不具有统一的指纹接口。
然而,还有最后两点没有做到:
解决这两个问题的方法只有:开源!
为了满足不同应用的不同场景,我们开源了:
这一切,尽在TENCENT SOTER(GitHub地址:https://github.com/Tencent/soter)。
使用SOTER最快能多块?如果你只需要做锁屏之类对安全性要求不高的需求,只需要:
dependencies { ... compile 'com.tencent.soter:soter-wrapper:1.3.8' ... }
<uses-permission
android:name="android.permission.USE_FINGERPRINT"/>
InitializeParam param = new InitializeParam.InitializeParamBuilder() .setScenes(0) // 场景值常量,后续使用该常量进行密钥生成或指纹认证 .build(); SoterWrapperApi.init(context, new SoterProcessCallback<SoterProcessNoExtResult>() {...}, param);
SoterWrapperApi.prepareAuthKey(new SoterProcessCallback<SoterProcessKeyPreparationResult>() {...},false, true, 0, null, null);
AuthenticationParam param = new AuthenticationParam.AuthenticationParamBuilder()
.setScene(0)
.setContext(MainActivity.this)
.setFingerprintCanceller(mSoterFingerprintCanceller)
.setPrefilledChallenge("test challenge")
.setSoterFingerprintStateCallback(new SoterFingerprintStateCallback() {...}).build();
SoterWrapperApi.requestAuthorizeAndSign(new SoterProcessCallback<SoterProcessAuthenticationResult>() {...}, param);
当然了,如果你想要实现指纹支付、指纹登录等高安全性场景,还有一些其他工作要做,具体可以参考我们的示例代码(链接:https://github.com/Tencent/soter/tree/master/soter-client-demo)和安全接入文档(链接:https://github.com/Tencent/soter/wiki/%E5%AE%89%E5%85%A8%E6%8E%A5%E5%85%A5)。
SOTER(GitHub地址:https://github.com/Tencent/soter) 开源之后,已经有包括微众银行在内的多个应用已经接入,这些应用接入的时间均不超过一个版本。使用的场景也从指纹支付,到指纹登录、指纹解锁。用过的,都说好。
那么,让我们再回顾下开头的场景:“我们要做指纹支付,下个版本上…”,想必你已经知道怎么做了,括弧逃~
原文来自:腾讯开源
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。