目前来看,密码认证方式早已成为我们一直采用的最直接的身份认证方式。但是我们应该知道的是很多大型企业都有曾出现过明文密码泄漏的事件,而且随着黑产的不断成长,钓鱼,撞库等等一系列的欺骗攻击行为都在不断涌现。由于很大一部分用户习惯于同一密码用于多个网站,这就导致了一次密码泄漏会牵连多个网站,用户密码认证的体系一直都在面临着很大的挑战。
对此而言,一种新的认证方式也已经出现在我们的视线范围内,WebAuthn在2019年3月成为了W3C推荐标准,同年的BlackHat大会上也有一篇议题专门介绍了WebAuthn,直到目前我们常见的浏览器大部分都已经支持了WebAuthn的标准,如Chrome,Safari等。可以看到各方力量都在联合推动着无密码认证的实现与发展,那么接下来我们就来一起认识一下什么是WebAuthn,以及它是如何工作的。

如今的身份认证技术

早期的身份认证技术,大多数人都使用单一的用户名密码访问全部资源。到中期大多数人需要访问数十到百中不同密码保护的资源,如果要设置不同的密码则需要借助密码管理器等工具,也因此出现了单点登录(SSO)的解决方案,而其本质上也是基于静态密码的形式。现如今越来越多的身份认证技术出现,我门已经可以在其中使用静态密码、短信验证码、数字证书、生物识别等方式完成身份验证。接下来会介绍除账号密码(单因素认证)外几种当下常见的身份认证技术。

OAuth(Open Authorization)

OAuth是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。其常见的应用场景就是第三方应用授权登录,运行流程如下。
1190574-3da73ccd48ef95ed

OTP(One-time Password)

一次性密码也就是我们常说的动态口令,是使用密码技术实现的在客户端和服务器之间通过共享秘密的一种认证技术,是一种强认证技术,是增强目前静态口令认证的一种非常方便技术手段。常见的场景比如动态短信,或者硬件形式的动态口令即电子令牌,它可以根据自身的内置密码芯片根据当前时间生产唯一的密码用于验证。
动态口令的同步机制有3种,即时间型、事件型和挑战与应答型,目前应用最多的是时间型动态口令,挑战与应答型动态口令的应用也逐渐增多,并且动态口令逐渐变为多种同步类型复合的机制发展,如时间+挑战与应答型。

2FA/MFA(Two-factor authentication/Multi-factor authentication)

双因素认证与多因素认证,顾名思义就是采用两种或以上不同类型的身份因素来确定当前用户的身份,增强可靠性与安全性。
《风控要略—互联网业务反欺诈之路》中有提到身份认证的层次,如下
album_temp_1603463884
分别为:

  • Know(只有你知道的信息):包括但不限于账户、密码、手机号、身份证信息。
  • Have(只有你拥有的物品):包括但不限于动态密码卡、IC 卡、磁卡。
  • Are(只有你拥有的生物特征):包括但不限于指纹、声纹、虹膜、人脸。
  • Do(你特有的行为证明了“你就是你”):包括但不限于用户的点击、按压、滑动、滚动、击键操作。

双因素认证与多因素认证就是使用上述的多种因素组合进行验证,常见的组合是密码+某种个人物品,比如网上银行的U盾。用户插上U盾,再输入密码。为了更加方便逐渐转换为密码+短信验证码。目前常见的大多数为静态密码+OTP的方式。
当然对于简单的多因素认证方式也依旧存在风险,Proofpoint的高级威胁检测分析人员说“在云安全方面,MFA不是灵丹妙药”。在2019年的一个案例中,一个银行机构网站的代码注入错误允许攻击者输入修改过的多因素字段取代PIN码,从而绕过第二个认证因素。最常见的MFA攻击类型是拦截一次密码,实时攻击使用中间人代理来获取用户输入他们认为是合法的网站的一次性密码。
目前限制MFA绕过攻击的最佳技术是基于最新的快速身份在线(FIDO)标准FIDO2,它使用设备标识来强化针对远程攻击者的身份验证过程以及各种推送技术,这些技术在注册设备上提醒用户任何访问尝试,并要求他们批准该访问。

什么是WebAuthn(Web Authentication)

FIDO联盟是一个安全、开放、防钓鱼、无密码认证标准的联盟。FIDO联盟成立于2013年,目前在全球拥有超过250名成员。FIDO有三个协议:UAF, U2F和FIDO2,它们是同一个协议家族。
WebAuthn是FIDO联盟指导下的FIDO2项目的核心组成部分。WebAuthn的目标就是提供一系列标准化的协议,让用户告别过去繁琐且不安全的账号密码登录方式,以实现安全的无密登录体验为目的。它彻底抛弃了传统的账号密码登录方式,允许用户直接使用设备的指纹识别、面部识别、虹膜识别、声音识别、实体密钥(USB连接、蓝牙连接、NFC连接)等方式来进行登录验证。
本质上WebAuthn只是一个浏览器JS API,描述用于创建和管理公钥证书的接口。是用来规范浏览器Web API的标准。FIDO2才是整个项目的名字。

我们来看一下FIDO2的规范,其中包括了WebAuthn与Client-to-Authenticator Protocol (CTAP)如下。

当使用者像应用程序要求注册或登入时,使用 FIDO2 的服务端 (RP App Server) 会告知浏览器说,我们现在要用 FIDO Authentication,而浏览器则会使用 WebAuthn API,透过 CTAP protocol来与Authenticator沟通,存取到需要的资讯并产生public key传回给服务端。
这边出现两个新名词:RP app server 与 Authenticator。
所谓的RP(Relying Party),描写的就是负责注册与认证使用者的组织或是服务。
而Authenticator就是负责产生credential的软硬体,例如:TouchID,YubiKey等等。TouchID这种存在于设备内部的,属于”platform” type,而Yubikey这类外部硬体设施则属于”cross-platform”type。

Web Authentication工作流程

WebAuthn基于挑战-应答模型工作,上面虽然已经介绍过几个基本概念,但我们还是再来根据下图重新介绍一遍其中四个重要的角色以及整体流程。

  • USER(用户):指正准备注册/登录的用户
  • USER AGENT(用户代理):用户使用的浏览器或系统,负责与认证器交互
  • AUTHENTICATOR(认证器):认证器通常指USB Key或是设备内置的指纹扫描器、虹膜扫描器、面部识别装置等,正是它们在使用流程中代替了密码甚至是用户名
  • RELYING PARTY(依赖方):指服务提供方,即网站

在其中认证通常会分为两种分别是Registration和Authentication,上图中不同实体角色之间的所有通信都由用户代理(USER AGENT)处理。

Registration

Registration使用认证器(AUTHENTICATOR)去创建一组新的公钥-私钥凭据,可以用于对依赖方(RELYING PARTY)生成的challenge进行签名。然后会把公钥以及签名的challenge发送回依赖方进行处理与存储。依赖方以后可以在需要时使用这些凭证来验证用户的身份。

根据上图,首先用户进入注册页面输入用户名并点击注册,客户端会发送一个请求到服务端,服务端也就是依赖方接下来会自动生成一个随机的字符串challenge以及其他信息返回给用户代理。
用户代理收到后再把这些信息交给认证器,接下来用户代理也就是浏览器会提示用户需要进行验证,并提供多种方式给用户选择,待用户选择相应的验证方式后认证器通过请求用户进行验证(按下按钮或者使用设备上的pin、生物特征等),验证完成后认证器会生成一个公钥-私钥对,并存储私钥,用户信息,以及域名。之后再使用私钥对challenge进行签名,并将数据返回给用户代理端(包括crediID,publicKey,签名等)。
用户代理端会将认证器传来的数据编码再发送给依赖方,依赖方接收到后会去检查该信息,并确保收到的信息(包括challenge,crediID,签名等)与之前发送的信息是一致的(使用公钥解密签名与cookie中或者session中的challenge是否一致),并完成注册。

Authentication

Authentication中前一部分和Registration一样都是接收依赖方生成的challenge,接下来认证器使用之前存储的公钥-私钥凭据对challenge进行签名,并将其发送给依赖方。通过这种方式依赖方可以验证用户是否拥有所需的凭证,从而证明其身份。

根据上图,首先用户进入注册页面输入用户名并点击注册,客户端会发送一个请求到服务端,接下来会自动生成一个随机的字符串challenge以及其他信息返回给用户代理。
用户代理收到后再把这些信息(包括challenge、依赖方信息和用户代理端信息等)交给认证器,认证器接下来会对用户进行验证,成功后会获取到之前对应已经存储的信息,然后再使用其中的私钥对challenge进行签名,并将数据(包括challenge,crediID,签名等)返回给用户代理端。
用户代理端会将认证器传来的数据编码再发送给依赖方,依赖方根据credID获取存储的此用户注册时保存的公钥来验证challenge是否与发送的一致,如果一致则登录成功。

使用Web Authentication API

我们使用https://webauthn.cn/ 作为演示案例,我们从用户代理端也就是浏览器的角度,对应代码来看看Web Authentication API的实际效果以及认证流程。
使用Chrome进入页面并打开Console以及监控Network

Registration

输入用户名admin@rui0.cn点击注册,这里触发了webauthn.js的makeCredential()方法

可以看到浏览器发起请求

接收到服务端提供的challenge以及其他信息

其中我们主要需要关注以下字段

  • challenge: 挑战是在服务器上生成的加密随机字节的缓冲区,这是防止“重播攻击”所必需的。
  • rp: 代表“依赖方”,它可以视为描述了负责注册和验证用户的组织。其中的id为必须为当前域名或为当前域名的子集的域名(不是子域名),不指定则默认使用当前域名。
  • user: 这是有关当前正在注册的用户的信息。身份验证者使用将id凭证与用户关联。建议不要将个人识别信息用作id,因为它可能存储在身份验证器中。
  • pubKeyCredParams: 这是一组对象,描述了服务器可以接受哪些公钥类型。
  • authenticatorSelection: 此可选对象帮助依赖方进一步限制允许注册的身份验证器的类型。其中authenticatorAttachment参数可以为null(表示接受所有类型的认证器)或是platform,cross-platform两值之一,分别表示仅接受平台内置的、无法移除的认证器如TouchID和接受外部认证器如USB Key。
  • attestation: 从身份验证器返回的证明数据具有可用于跟踪用户的信息。此选项允许服务器指示证明数据对于此注册事件的重要性。值”none”表示服务器不关心证明。”indirect”是服务器将允许匿名证明数据。”direct”表示服务器希望从身份验证器接收证明数据。

根据代码可以看到首先将这些信息存储到makeCredentialOptions中,然后通过触发navigator.credentials.create()来生成newCredential

触发该方法后认证器会请求验证身份,认证器通常会以某种形式要求用户确认,如输入PIN,使用指纹,进行虹膜扫描等,以证明用户在场并同意注册。之后,认证器将创建一个新的非对称密钥对,并安全地存储私钥以供将来验证使用。

选择使用TouchID来验证

验证成功可以看到Console输出了对应生成的数据,也就是生成的newCredential,其中新的公钥、全局唯一的凭证ID和其他的证明数据会被返回到浏览器,成为attestationObject

并发送给服务端

  • id: 新生成的凭证的ID,在对用户进行身份验证时,它将用于识别使用的credential。
  • rawId: 再次提供ID,但采用二进制形式。
  • clientDataJSON: 这表示从浏览器传递到身份验证器的数据,以便将新凭据与服务器和浏览器关联。身份验证器将其提供为UTF-8字节数组。
  • attestationObject: 该对象包含凭证公钥,credential ID以及用于验证注册事件的其他元数据。它是用CBOR编码后的二进制数据。

我们可以先来看一下clientDataJSON的Base64解码数据

可以看到challenge和之前接收到的一样,origin表示为创建凭证的页面的源。
attestationObject使用CBOR解码数据


其中authData包含rpIdHash、flagsBuf、flags、counter、credID、COSEPublicKey等数据。

最后,服务器收到数据后需要执行一系列检查以确保注册完成且数据未被篡改。步骤包括:

  1. 验证接收到的challenge与发送的challenge相同
  2. 确保origin与预期的一致
  3. 使用对应认证器型号的证书链验证clientDataHash的签名和证明

在大部分情况下(同时也是默认情况),注册时认证器并不会对挑战进行签名,attestationObject 并不会包含签名后的挑战。只有依赖方明确要求证明且用户同意(部分浏览器要求)后认证器才会对挑战进行签名(具体实现据情况会有所不同)。对此,MDN 解释道“大部分情况下,用户注册公钥时我们会使用「初次使用时信任模型」(TOFU) ,此时验证公钥是没有必要的。”

Authentication

点击登录,根据js代码来看,通过请求assertion来获得对应信息,然后调用navigator.credentials.get()让客户端来拿到凭证,这时候同样会触发认证器验证同上。


服务器收到请求会根据用户名查询到对应的credID,也就是allowCredentials中的id。

接下来身份验证器找到与依赖方返回信息相匹配的此服务的凭据,并提示用户进行身份验证。

TouchID验证成功后身份验证器将使用在注册期间为此帐户生成的私钥对clientDataHash和authenticatorData进行签名以及其他信息将其放在返回的credential中然后通过浏览器发送需要的内容

  • authenticatorData: 认证器信息,包含认证状态、签名计数等
  • signature: 被认证器签名的 authenticatorData + clientDataHash(clientDataJSON 的 SHA-256 hash)
  • userHandle: 创建凭证时的用户ID,也就是最开始注册时服务端返回的user.id

服务器收到信息后根据credID获取存储的此用户注册时保存的公钥来验证challenge是否与发送的一致,如果一致则登录成功。

总结

首先我们再来回顾一下注册环节,主要是使用了navigator.credentials.create()请求认证器生成公钥凭证,其余流程如下

对于登录认证环节,主要是使用了navigator.credentials.get()请求获取公钥凭证,其余流程如下。

WebAuthn被称为“Web身份认证的未来”,它本身需要依靠浏览器作为媒介和认证器进行交互,其本质上利用了非对称加密的方式来保护用户账号认证的安全性。其实我们可以想到当使用SSH进行连接时通常也有两种方式,一种是密码而另一种就是利用公钥,实际上与WebAuthn原理相同。WebAuthn的目标就是提供一系列标准化的协议,让用户告别过去繁琐且不安全的账号密码登录方式,以实现安全的无密登录体验。本文只简单介绍了其基本概念与流程,更多的细节以及标准建议阅读W3C文档。
虽然WebAuthn提供了一种更加安全的认证模式,但与此同时必然会引发新的安全研究角度,对此浏览器安全会变得更加重要,依赖方的认证处理标准需要进一步规范统一。当然安全并不是一项单一的技术,相信不久的将来WebAuthn会成为Web身份验证中的重要组成部分之一。

参考

https://www.jianshu.com/p/84a4b4a1e833
https://blog.csdn.net/wxy540843763/article/details/84497359
https://buaq.net/go-31212.html
https://fidoalliance.org/fido2
https://blog.techbridge.cc/2019/08/17/webauthn-intro
https://webauthn.io
https://webauthn.guide
https://flyhigher.top/develop/2160.html
https://webauthn.me/introduction
https://w3c.github.io/webauthn
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Authentication_API