Passkey在Android端的应用实践
Passkey,中文名通行密钥,他是WebAuthn的一部分,由FIDO联盟开发,可以做到让用户不使用用户名和密码,来直接验证身份。在2022年的WWDC上,Apple正式宣布了对Passkey的支持,当前10月份,google也宣布了对于passkey的支持。目前已经有一些应用支持了passkey,包括谷歌,微软,github,whatsapp等。最近在我们的Android应用上集成Passkey踩了很多的坑,简单记录一下。 Passkey原理简介 简单来说,Passkey就是通过密钥对验证替代密码验证,原理和SSL/TLS验证类似,密钥对即公钥和私钥。用户的设备上存储的是私钥,创建的时候会将公钥发送到服务器端进行存储。验证的时候,服务端发送一段通过公钥加密的options信息,设备端使用私钥解密后回传给服务器,则能够验证成功。设备上的私钥需要验证用户的指纹、faceid或者yubikey才能使用。 Android系统需要Android 9.0以后,iOS系统需要iOS116以后才能支持,除此之外,Android设备需要登录google 账号,并且手机上有google play service才行,iOS需要开启iCloud 钥匙串。Appple 和 Google 还会通过他们的Cloud来帮助我们在多台设备之间同步同一个用户的身份验证,从而可以让我们实现同一个passkey在同一个用户的多台设备使用。 具体到Android系统,首先需要用户的手机系统在Android 9.0以上,我猜测这是因为在Android 9.0之后要求手机要有安全硬件,放到StrongBox的密钥必须是放到安全硬件中的。web用户可以在手机的chrome浏览器中使用passkey。对于Android应用则需要用户手机上安装的最新的(至少是2023年版本的)Google play service,且手机上的play services不能是中国特色的阉割版本,否则google password manager不能使用,应用也不能够使用Passkey。对于Android 14以后的系统,应用是可以使用第三方密码管理器的,不过我还没有实践,这里不做讨论。 Android应用接入 在Android中接入Passkey其实是比较简单的,具体是有两个场景,分别是创建Passkey和验证Passkey。 准备工作 为了让Android系统能够识别我们的应用支持Passkey,需要在我们的后端服务器中配置我们的Digital Asset Links JSON文件,这个文件如果我们配置过Android的App Links支持,应该是已经有的,这个文件的路径应该为https://example.com/.well-known/assetlinks.json,配置的内容如下: 1 2 3 4 5 6 7 8 9 10 11 12 [ { "relation" : ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds" ], "target" : { "namespace" : "android_app", "package_name" : "com.example.android", "sha256_cert_fingerprints" : [ SHA_HEX_VALUE ] } } ] 其中的relation用来指定声明的关系,handle_all_urls就是指可以让app处理所有的app links, get_login_creds指的是处理登录验证。 target是表示该声明应用到的目标,namespace指定为android应用,package_name为我们应用的包名,sha256_cert_fingerprints为应用的签名SHA256。 这个文件放到我们的服务器,需要保证访问路径,跟我们前面说到的一样。并且请求返回的Content-Type为application/json。如果我们的服务端有robots.txt文件要配置允许google去访问该文件: User-agent : * Allow: /.well-known/ Google为我们提供了Credential Manager 来使用Passkey,需要添加如下依赖: 1 2 3 4 dependencies { implementation("androidx.credentials:credentials:1.3.0-alpha01") implementation("androidx.credentials:credentials-play-services-auth:1.3.0-alpha01") } 上面第二个依赖,如果我们是只支持Android 14以上,且不用谷歌的密码管理器,可以不用。 Proguard文件中需要添加如下内容: -if class androidx.credentials.CredentialManager -keep class androidx.credentials.playservices.** { *; } 创建和验证Passkey都需要CredentialManager,创建方式如下: 1 val credentialManager = CredentialManager.create(context) 创建Passkey 创建Passkey的流程如上图所示,首先需要从服务端的接口拿到一些数据,把这个作为requestJson创建CreatePublicKeyCredentialRequest去调用创建Credential,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest( requestJson = requestJson, preferImmediatelyAvailableCredentials = true/false ) coroutineScope.launch { try { val result = credentialManager.createCredential( context = activityContext, request = createPublicKeyCredentialRequest ) } catch (e: CreateCredentialException) { handleFailure(e) } } 上面的requestJson是我们从服务端拿到的,他应该是符合WebAuthn标准的json内容,prefImmediatelyAvailableCredentials, 如何设置为true,手机上没有可用的passkey注册提供者会直接报错,而不是看有没有混合可用的passkey。requestJson的demo如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 { "challenge": "abc123", //服务端随机生成的字符串,用于后续判断客户端回传,用于避免被攻击 "rp": { //Replay party信赖方试题,用来表示应用信息,id为域名,需要和wellknown用的域名相同 "name": "Credential Manager example", "id": "credential-manager-test.example.com" }, "user": { //用户信息,id和name不能缺少,displayName是可选的 "id": "def456", "name": "helloandroid@gmail.com", "displayName": "helloandroid@gmail.com" }, "pubKeyCredParams": [ //公钥凭据支持的算法类型和密钥类型,这个在webAuthn网站上可以找到相同的文档 { "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257 } ], "timeout": 1800000, //验证超时时间,毫秒 "attestation": "none", "excludeCredentials": [ //可选项,排除的凭据,可通过这个来限制不让同一台设备设置多个passkey {"id": "ghi789", "type": "public-key"}, {"id": "jkl012", "type": "public-key"} ], "authenticatorSelection": { //设置支持的类型 "authenticatorAttachment": "platform", //platform就只支持手机内置的,若为cross-platform就可支持usb的验证,yubikey等 "requireResidentKey": true, //设置为true,则可检测到的凭据会将用户信息存到passkey中,并可以让用户在进行身份验证时选择账号。 "userVerification": "required" //用于设置使用设备屏幕锁定功能进行用户验证,默认是preferred,用户可以跳过,建议设置为required。 } } 以上json更多的解释可以看webauthn的网站: Web Authentication: An API for accessing Public Key Credentials - Level 3 (w3c.github.io) 我们客户端调用createCredential方法后拿到的结果,类似如下: 1 2 3 4 5 6 7 8 9 { "id": "KEDetxZcUfinhVi6Za5nZQ", //创建的passkey的base64网址编码id,需要后端存储 "type": "public-key", //此值始终为public-key,不过ios手机上可能为passkey "rawId": "KEDetxZcUfinhVi6Za5nZQ", "response": { "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9", //ArrayBuffer编码的客户端数据 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID
以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。

登录后,加入这场讨论。
登录参与