流程描述
1.app账户登录 --- 服务端返回token
2.app打开指纹登录选项,生成非对称密钥,弹出验证界面--服务端返回随机数相关数据
3.指纹验证成功后,对客户端生成的唯一标识+随机数签名 ,发送公钥和签名数据--- 服务端公钥验签,把账户与唯一标识、公钥绑定
4.退出账户---服务端返回token失效
5.点击指纹登录功能,弹出验证界面---服务端返回随机数
6.指纹验证成功后对唯一标识和随机数签名,发送签名数据---服务端验签成功后返回token
时序图
关键点说明
- 步骤2的随机数和步骤5的需要返回随机数给第3步和第6步使用,目的是防止重放攻击(不加随机数的话,攻击者获取到即使加密的登录请求网络包也能获取token),增加安全性,避免本地相同请求读取缓存。
- 唯一标识符可以参考Google Android开发者官方文档:唯一标识符最佳做法 | Android 开发者 | Android Developers (google.cn),我使用其中建议的一条UUID.randomUUID().toString()
- 指纹和密钥:这里是Android 端重点
1.首先使用Android 的keystore生成非对称密钥对,在构建秘密库初始化参数时候设置需要指纹验证才能使用私钥相关的操作
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null, null) //keystore的来源可空,打开keystore的密钥可空
val keypairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
val purpose = KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
keypairGenerator.initialize(
KeyGenParameterSpec.Builder("myKey", purpose)
.setDigests(KeyProperties.DIGEST_SHA256) //设置摘要算法
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))//设置基于椭圆曲线计算的非对称密钥的算法
.setUserAuthenticationRequired(true) // 设置是否需要生物认证,此处与指纹关联上
.build()
)
keypairGenerator.genKeyPair()
2.根据密钥库对象初始化签名功能对象,构建指纹验证对象,开启认证操作传入签名功能对象,认证成功时候,方可使用我们传入的密码对象
//通过密钥库构建签名功能对象
val signature1 = Signature.getInstance("SHA256withECDSA")
val privatekey = keyStore.getKey("myKey", null) as PrivateKey
signature1.initSign(privatekey)
//构建指纹认证对话框对象
mBiometricPrompt = BiometricPrompt.Builder(activity)
.setTitle(activity.resources.getString(R.string.fingerprint_verify))
.setDescription(activity.resources.getString(R.string.description))
.setNegativeButton(
activity.resources.getString(R.string.cancel), activity.mainExecutor
) { _, i ->
doCancel.invoke()
}.build()
//开启认证,传入具有签名功能的对象
mBiometricPrompt.authenticate(
BiometricPrompt.CryptoObject(signature1),
mCancellationSignal,
activity.mainExecutor,
mAuthenticationCallback
)
// 认证成功回调
mAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
LogUtils.d("finger onAuthenticationError$errString")
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
LogUtils.d("finger onAuthenticationSucceeded${result}")
val signature = result.cryptoObject.signature
signature.update("Abcd12345".toByteArray())
mSignData = signature.sign() //签名值
doAuth.invoke()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
LogUtils.d("finger onAuthenticationFailed")
}
}
3.将签名数据和公钥和原数据发送给服务端
val publickKey = keyStore.getCertificate("myKey").publicKey
服务端验签大概代码逻辑如下(非Android端代码):
Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initVerify(publickKey);
signature.update("Abcd12345".toByteArray());
boolean = signature1.verify(mSignData);
另一种使用对称密钥的指纹登录方式
有很多文章描述的是在:打开指纹登陆选项时生成对称密钥,用对称密钥加密用来登录的信息(比如用户密码或其他什么口令),然后指纹登陆时,本地指纹验证成功后用对称密钥解密这些登录信息,然后拿账号密码或口令登录服务器,有点类似安全存储token,但是安全性要比非对称要低!。
fido指纹登陆相关流程
根据fido对接官方中午文档描指纹登陆相关的UAF规范(旨在提供免密认证的规范协议。用户可通过指纹,虹膜,声音,脸部等生物识别技术来完成验证。UAF 也支持 PIN 码输入的验证方法),从两个方面简述指纹fido流程:
-
根据 FIDO UAF 文档指引,FIDO UAF 在移动设备上的实现将分5层: app, client application,UAF Client,UAF ASM,UAF Authenticator。各层的简要功能如下:
app :提供 UI 用于演示功能,显示 UAF Server 和 UAF Client 的信息交互,提供简单的 Server 设置。
client application :封装与 UAF Server 交互的 API,以及与 UAF Client 交互的 API。app 调用 client application 的 API 与 UAF Server 及 UAF Client 交互。
UAF Client :负责处理 Server 的 protocol message,并根据内容向 UAF ASM 发出请求。
UAF ASM :用于处理 UAF Client 层的信息,并管理 UAF Authenticator。实际情况中 UAF ASM 通常与 UAF Authenticator 看做一个整体,对外打包为1个 apk。
UAF Authenticator :用于认证用户,一个 UAF Authenticator 可能存在多个内部的 authenticator,它们拥有不同的特性,提供不同的认证方式。
层级示意图:
-
FIDO 指纹认证过程主要四个步骤: 提供了用户注册,登陆认证,交易认证,注销等功能。
1.注册:会为注册的 username 生成一对非对称密钥对,FIDO UAF 要求密钥对存储在 UAF Authenticator 或 UAF ASM 的安全环境中。
2.认证:在登陆认证和交易认证的时候使用生成的私钥进行签名,服务器再通过 authenticator 提供的公钥对签名报文进行验签,验签成功则认可这个用户,从而实现免密登陆的特性。
3.交易:交易认证比登陆认多了一个交易信息,交易信息需要显示给用户(如果支持)并加入签名报文,其他和登陆认证是一样的。所以交易认证的操作类型也是 auth ,(认证登录不涉及这一步)。
4.注销:已注册的用户可以通过注销删除用户记录。特殊的是注销操作不需要把 UAF Client 的结果发送给 UAF Server。UAF Server 在接收到注销操作后,就直接删除用户了。