通行密钥比密码更易于使用且更安全。采用通行密钥为用户提供一种简单又安全的方式,让用户无需输入密码就能在各种平台上登录你的App 和网站。——Apple

传统密码
远古时代:明文传密码
优点:
流程直观易懂
风险:
- 用户后台采用明文通信,易遇到中间人攻击
- 数据库一旦被拖库,所有用户的密码会立刻泄漏
- 后台能看到用户密码(你以为后台不会打日志吗)
文艺复兴:哈希存密码
优点:
- 用户后台用HTTPS协议传输,一定程度缓解了中间人攻击的问题
- 数据库不明文存用户密码,被拖库后获取用户明文密码存在破解成本,可有效保护用户隐私
风险:
- 数据库被拖库后,可用彩虹表推测部分用户密码
- 因为哈希值唯一,如果知道用户在其他系统的明文密码,就可以知道用户在该系统中有没有使用相同的密码
流行:加盐存密码

优点:
提高用户密码破解成本
缺点:
盐一旦泄漏,就可用彩虹表推测用户密码
总结:密码永远不安全
2FA:双因素认证
一般而言,三种因素可以证明用户的身份:
- 秘密信息:例如用户名、密码、口令
- 个人物品:用户手机、身份证、银行卡
- 生理特征:指纹、脸、掌纹、虹膜
双因素认证是指需要两个因素证据才能通过认证
TOTP(Time-based one-time password)
基于时间的一次性密码,属于“个人物品”因素
应用
-
QQ令牌

-
银行密码器

-
身份认证器APP(微软的Authenticator、谷歌的Authenticator、苹果的Password)
认证原理
算法(前端、后台都会用到):
TC = floor((unixtime(now) − unixtime(T0)) / TS)
TOTP = HASH(SecretKey, TC)
unixtime(now)当前时间(服务器时间)unixtime(T0)约定的起始时间TS验证码有效长度(比如30秒)TC时间计数器HASH约定哈希函数,一般是SHA-1
对于第5、6步,由于客户端时间与服务器时间存在误差,会允许用户使用上n个时间段至下n个时间段的TOTP认证(n由后台决定)
邮箱/短信验证码
太经典了,基本天天都能用到
优点:
- 可以以安全的名义获取用户隐私
缺点:
- 要钱
- 链路受控(验证码邮件可能会被收件方屏蔽、短信验证码需通过运营商链路发送)
- 接入麻烦,国内接入还需审核短信签名与短信内容
- 并非每个人都有可以接收验证码的手机号(比如Github的SMS认证仅限美国用户使用)
Passkey理论
非对称加密
加密和解密使用不同的密钥

应用:
- 端对端加密(iMessage)
- Xlog加密
- 数字签名
数字签名
数字签名是一种用于验证数字信息完整性、真实性和抗否认性的加密技术,防止信息在传输过程中被篡改。分为签名、验证两步
举例:Alice要告诉Bob,今晚8点开会
不带数字签名:
Attacker的行为称为“中间人攻击”
带数字签名:
签名算法(以RSA为例):
RSA详解

前置知识
同余

欧拉定理与欧拉函数


欧拉函数举例:
,因为1, 2, 3, 4, 5里只有1, 5与6互质
,因为7是质数,1-6与7互质
,因为1-8里只有1, 3, 5, 7与8互质
欧拉定理推广
- 若a与b互质,则有
- 若n为质数,则有
总结:我们现在有的武器(基础理论)
- 欧拉定理:
若 和 为正整数,且 和 互质,则
- 欧拉定理推广1:
若 和 互质,则
- 欧拉定理推广2:
若n为质数,则
- 费马小定理(实际为欧拉定理推导,可结合 看 )
若 和 为正整数,且 与 互质, 为质数,则
密钥生成
第一步 - 输出 、 、 、
取一质数 、,结合 ,有
举例: p=61, q=53 则n=61*53=3233 \phi(n)=60*52=3120
第二步 - 输出
选一整数 ,满足 ,且 与 互质(计算机里一般取65535)
举例: e=17
第三步 - 输出
计算出整数 ,使得
怎么计算?
举例: d=2753
第四部 - 汇总
公钥:
私钥:
的长度:密钥长度(比如RSA1024,1024指的是 有1024位)
举例: 上面的公钥:(17, 3233),私钥:(2753, 3233)
加解密
假设原始数据为 ,密文为 ,则
公钥加密:
私钥解密:
注:,否则需分段加密
安全论证
已知公钥,能否知道私钥?
等价于:已知 ,,能否知道 ?
已知:,所以需要知道
已知:,所以需要知道 ,
已知:,所以需要对 做质因数分解
如果 , 取得比较大(比如1000位),那么 至少有 位,以人类目前的科学水平不可能对如此大的数做质因数分解。
正确性证明
大质数的获取
- 随机生成一个大数
- 判断 是否为质数,一般选用 算法判断
Passkey怎么验证用户身份
首先,用户创建一对密钥,私钥存本地,公钥发给服务器并存服务器里。
在登录时,后台会给用户发送挑战(随机字符串)。用户用自己的私钥对挑战做数字签名,并将签名发送给后台。后台再拿存储的用户公钥做验证,判断签名是否有效。
Passkey实践
检测你的设备是否支持Passkey:
流程:简单版
- 已注册用户添加Passkey
- 用Passkey登录
WebAuthN
WebAuthN是W3C标准。客户端很多Passkey的实现也是基于WebAuthN。
数据结构与流程分析
注册Passkey
客户端请求注册Passkey(用户已登录):
客户端 -> 后台:随意
后台->客户端:
{ "challenge": "gVQ2n5FCAcksuEefCEgQRKJB_xfMF4rJMinTXSP72E8", "rp": { "name": "Passkey Example", "id": "example.com" }, "user": { "id": "GOVsRuhMQWNoScmh_cK02QyQwTolHSUSlX5ciH242Y4", "name": "Michael", "displayName": "Michael" }, "pubKeyCredParams": [ { "alg": -7, "type": "public-key" } ], "timeout": 60000, "attestation": "none", "excludeCredentials": [ ], "authenticatorSelection": { "authenticatorAttachment": "platform", "requireResidentKey": true, "residentKey": "required" }, "extensions": { "credProps": true }}- rp.name,rp.id:域信息
- user.id:用户特征
- user.name,user.displayName:Passkey展示在用户密码器上的信息
- pubKeyCredParams:支持算法类型
- timeout:challenge有效期时长
- excludeCredentials:不允许生成的凭据id,通常为用户已注册的凭据id
- authenticatorSelection:认证类型,可选物理设备/平台
拿到这些数据可以直接透传给系统/浏览器。系统/浏览器会拉起将这些信息透传给密码管理器,让密码管理器创建Passkey。
密码管理器创建Passkey后,会返回公钥及基础信息给系统,系统再透传给客户端/浏览器。
客户端->后台:
{ "id": "base64url-encoded-credential-id", "type": "public-key", "response": { "clientDataJSON": "base64url-encoded-client-data-json", "attestationObject": "base64url-encoded-attestation-object" }}- id:凭据id
- clientDataJSON:base64编码的json数据,json为:
{"type": "webauthn.create", // 或 "webauthn.get""challenge": "base64url-encoded-challenge","origin": "https://example.com"}- attestationObject:CBOR格式结构体,包含:
- authData:凭据信息,CBOR格式的Authenticator data,包含公钥、域信息等
- fmt
- attStmt
后台 -> 客户端:随意
登录
客户端请求登录:
客户端 -> 后台:(可选)用户特征
后台->客户端:
{ "challenge": "x1wRuShyI4k7BqYJi60kVk-clJWsPnBGgh_7z-W9QYk", "allowCredentials": [], "timeout": 60000, "rpId": "example.com"}- challenge:后台发的挑战
- allowCredentials:允许使用的凭证id,为空表示由用户自己选择。(如果客户端->后台有发送用户特征,那么这里的allowCredentials应为用户注册过的Passkey凭证id)
- timeout:挑战过期时间
- rpId:域信息
客户端拿到这些数据,应直接透传给系统。系统会透传给密码管理器,让密码管理器通过rpId选择一个Passkey。如果没有Passkey,会弹窗告诉用户没有已注册的Passkey。
客户端拿到Passkey认证数据后:
客户端 -> 后台:
{ "id": "t2hF9lEjZ-8K5oFIZw1wQA", "rawId": "dDJoRjlMamp6LTdLNWhGSVp3MXdRQQ", "type": "public-key", "response": { "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJjaGFsbGVuZ2VleGFtcGxlIiwib3JpZ2luIjoiaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20iLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0", "authenticatorData": "YWdhdXRoZW50aWNhdG9yRGF0YS5leGFtcGxl", "signature": "c2lnbmF0dXJlZXhhbXBsZQ", "userHandle": "dXNlcklkZXh0cmFhcmVv" }}- id:凭据id
- response.clientDataJSON,response.authenticatorData和注册Passkey时类似
- signature:用Passkey私钥加密的hash(clientDataJSONauthenticatorData)
后台收到数据后,会先对signature解密,得到B,再判断B==hash(clientDataJSONauthenticatorData)
后台 -> 客户端:随意
iOS实现
前置条件:
配置associated domains,https://{域名}/.well-known/apple-app-site-association的内容为:
{ "webcredentials": { "apps": [ "{团队ID}.{bundleId}" ] }}怎么获取团队id?
- 打开
放上去后:
iPhone启动或安装APP时会去拉一遍数据
需注意的API:
- ASAuthorizationPlatformPublicKeyCredentialProvider - Passkey注册、认证都要调他
- ASAuthorizationPlatformPublicKeyCredentialRegistration - Passkey注册结果
- ASAuthorizationPlatformPublicKeyCredentialAssertion - Passkey认证结果
解析数据:
ASAuthorizationPlatformPublicKeyCredentialRegistration

ASAuthorizationPlatformPublicKeyCredentialAssertion

Android实现
Android与Passkey的操作都需用到Credential Manager
在Android14以前,安卓的密码管理器只能用谷歌密码管理,而谷歌密码管理是包含在GMS里的。如果系统不带GMS,系统就不会有密码管理器,因此国行系统需要安装谷歌服务才能使用Passkey。
Android14及以后,用户可以安装并使用其它密码管理器了。用户在设置里选择密码器后,如果密码器支持Passkey操作,就可以使用Passkey。
Credential Manager
前置条件:
配置Digital Asset Links,https://{域名}/.well-known/assetlinks.json的内容为:
[ { "relation": [ "delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds" ], "target": { "namespace": "android_app", "package_name": "{包名}", "sha256_cert_fingerprints": [ "{指纹签名}" ] } }]很简单,无论是登录还是注册,将后台传的数据透传给系统即可
OPPO设备
OPPO自己搞了个SDK:
前置条件:
配置fido2-trusted-facets,https://{域名}/.well-known/fido2-trusted-facets.json的内容为:
[ { "relation": [ "delegate_permission/common.get_login_creds" ], "target": { "namespace": "android_app", "package_name": "{包名}", "sha256_cert_fingerprints": [ "{指纹签名}" ] } }]OPPO Passkey API和Credential Manager差不多。
咨询OPPO工程师,该API可能很快会被废除:

鸿蒙
不支持,别想了
后台实现
代码略
遇到的坑点:
- iOS clientDataJSON.origin为
https://{associated domains} - 安卓 clientDataJSON.origin为
android:apk-key-hash:{包签名base64} - OPPO clientDataJSON.origin为
android:apk-key-hash:{包签名base64并做urlEncode}
所以,如果要支持iOS、安卓和OPPO设备的Passkey登录注册,后台需要设置3个origin
