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):
这张图看上去不明觉厉,原理其实并不难:Google在Android 6.0之后,允许用户在应用中生成一对非对称密钥,将私钥存储在
TEE
中(什么是TEE?稍后会讲),任何人,包括应用自己甚至Android系统都无法获取私钥,除非用户使用指纹授权才能使用,签名或者加密传入的数据,然后输出密文。这样的话,就可以
利用密钥的
签名-验签机制
(小白不懂什么是签名验签?Google下咯~或者看
这篇文章
解释签名的部分),
只有用户使用指纹签名之后,才能产生正确的签名,后台验签即可,这样就能保证链路安全。
这个设计非常巧妙,但是Google百密一疏:
如果黑客在密钥生成的时候就拦截了请求,替换为自己的密钥,那么后面签名和加密,用的也是黑客的密钥,那么整套系统的设计也就崩塌了。
另外还有一个问题,如果仅仅返回true/false,那么只要是录入到设备内的指纹,就可以假冒你的身份支付。这对于家里有熊孩子的家长来说,简直就是银行卡噩梦。雪上加霜的是,对于Android设备而言(其实iOS也是一样),只要知道了锁屏密码就可以录入新的指纹。如果支付后台直接信任指纹认证结果,就相当于将原本非常秘密的支付密码,退化到了锁屏密码的级别。这样,无论支付后台做了多么严密的风控策略,按照木桶原理,从根本上整个系统就是
不符合支付安全
的。
当然,当时也有类似于
FIDO
(链接:https://fidoalliance.org/)之类的认证联盟,但是整个
流程过于复杂
,甚至还要求在
应用后台植入sdk
。而且,类似方案的中心服务权限过高,会导致如支付笔数、开通用户数等关键指标为人所知,因此也就无法使用。并且支持设备数实在太少,也并无接入动力。
研究过这些之后,发现并不可直接使用任何一个方案,场面一度尴尬。没有合适的轮子,怎么办?
让我们回头看看Android系统的指纹接口设计:
那Google没有做到什么呢?
同时,我们意识到,在生物认证领域这个千亿级市场中,
缺乏一个统一、安全、易接入的认证标准
,微信有这样的需求,其他应用也必然如此。微信有能力解决这些问题,实现自己的业务需求,也希望将成功经验复制。既然这样,借此机会制定一个生物认证标准,
提供一个生物认证平台,微信责无旁贷,这就是SOTER的起源。
如果以做标准的要求来实现SOTER,那么除了刚刚所述的系统接口缺陷之外,系统设计时还需要考虑:
-
后台不存储任何敏感信息,包括对称密钥、非对称密钥私钥,
更不能将用户生态无特征(如指纹图案)以任何形式传输或存储
,防止应用后台被脱库;
-
如果有后台交互,不暴露应用方核心商业隐私,如认证次数、业务开通次数;
-
应用接入门槛低,客户端无须集成重量级sdk,后台无须集成sdk;
-
简单易用,第三方应用只需要操作上层接口,无须进行复杂的底层开发。
信任根的重要性之前已经说明。如果一个系统依赖密钥签名,有一个可以信任的根密钥,才有可能构建安全的信任模型。但是,如果一台手机出厂之后才产生根密钥
(ATTK)
,那么中间有足够时间窗口给到黑产从业者替换掉它。因此,常规方式产生根密钥一定是不行的。所以,我们有了一个大胆的想法:直接与厂商合作,在设备出厂之前,产线上生成设备根密钥,公钥通过厂商服务传输给微信密钥服务——TAM。这个想法虽然对厂商改造比较大,但是由于我们直接通产业链上游(高通、MTK等)合作,研发出了一套统一的解决方案,以及产线工具,成功说服厂商对产线做了最小化的改造。It’s tough, but we did it!
设备根密钥流程
-
厂商在产线上对设备下发生成设备根密钥命令;
-
TEE
中生成一对设备唯一的RSA-2048非对称密钥,私钥存储在设备RPMB区域,没有任何厂商或者应用方可以读取(包括微信与设备厂商),公钥以及设备ID导出;
-
公钥和设备ID上传到厂商服务器,之后通过公众平台安全接口,传输到微信公众平台;
-
微信公众平台将公钥与设备ID传输到微信TAM服务器。
这里,我们又遇到了我们的老朋友:TEE(Trusted Execution Environment),后面我们也会多次与这个名词打交道。如果想要看详细的介绍,可以参考这里(链接:https://en.wikipedia.org/wiki/Trusted_execution_environment)。当然了,我相信大部分同学都跟我一样,只想要一个形象的解释。简单地说,你的手机中,除了类似Android这样的操作系统之外,还有一个独立的环境。这个环境目前并无行之有效的破解方法,也就是说即使Root了Android系统,都无法破解TEE中的数据。如果将整部手机比作房子的话,Android操作环境就是客厅,TEE就是你的保险箱。可想而知,如果将所有的数据都存储在TEE,关键操作也在TEE内进行,岂不美哉!当然了,这样的话,所有从TEE中出来的敏感数据,就一定要添加上使用可信密钥对其的签名了。
有了设备根密钥之后,认证链的构造逻辑就清晰了很多:采用密钥链的形式,用已认证的密钥来认证未认证的密钥就可以了!
方法论有了,实施就变得简单。
但是,依然有一个问题需要思考:到底需要多少层密钥呢?密钥层数少,那么每一次都需要前往中心服务(微信TAM)验签,对第三方应用而言,会更担心泄露自己的商业逻辑;密钥层数越多,会增加了传输复杂度和失败率。经过多方讨论,SOTER决定使用三级密钥,除了产线预制的设备根密钥之外,增加定义应用密钥(每一个应用生命周期内只需要存在一对)和业务密钥(每一个业务需要一对)。事实证明,这是一个明智的选择:这样既保证了流程的流畅度,又保证应用的关键商业隐私不暴露。为什么?往下看。
我们十分欣赏Google的指纹和密钥模块接口设计,因此,我们与厂商合作,在此基础上添加patch包,即可迅速实现整个上层架构。