20、与服务器端结合:生成挑战值(challenge)、签名验证、安全传输

好,咱们终于聊到虹膜识别真正上战场的地方了——跟服务器打交道。

说实话,手机本地做虹膜识别其实不难,难的是怎么把识别结果安全地告诉服务器,并且让服务器相信这个结果是真的。我见过不少项目,本地识别做得漂漂亮亮,一到网络传输就翻车了。

为什么需要挑战值(Challenge)?

先问一个问题:如果客户端直接发「虹膜匹配成功」给服务器,服务器敢信吗?

当然不敢。万一有人抓包重放呢?万一客户端被篡改了呢?

所以我们需要一个机制——挑战值。说白了,就是服务器给客户端出一道随机的「考题」,客户端必须用虹膜识别结果加上这道考题一起签名,再发回去。

核心逻辑:挑战值是一次性的、随机的、不可预测的。每次请求都不一样。

我在项目中遇到过一种情况:有人直接把挑战值写死在客户端代码里。嗯,那跟没做没啥区别。挑战值必须由服务器实时生成,而且用完就废。

挑战值的生成流程

我习惯这样设计流程:

  1. 客户端发起请求:告诉服务器「我要做虹膜识别」
  2. 服务器生成挑战值:一个 32 字节的随机数,比如用 SecureRandom
  3. 服务器返回挑战值:同时把挑战值存到 Redis 里,设置 30 秒过期
  4. 客户端拿到挑战值:传给虹膜识别模块
  5. 虹膜识别成功后:用私钥对「挑战值 + 识别结果」签名
  6. 客户端把签名发回服务器:服务器验签通过,才算真正认证成功
客户端 服务器 ① 请求虹膜认证 ② 返回挑战值(32字节随机数) ③ 虹膜识别 + 签名 ④ 发送签名结果 ⑤ 验签 + 比对 ⑥ 返回认证结果

签名验证:到底签什么?

很多人搞不清楚签名到底要签哪些数据。我直接说结论:签名的内容 = 挑战值 + 虹膜特征哈希 + 时间戳

为什么加时间戳?防止签名被截获后在一定窗口期内重放。为什么加特征哈希?确保签名跟本次识别结果绑定。

我的习惯:签名前把所有字段按字典序拼接,避免字段顺序不一致导致签名失败。这个坑我踩过,当时排查了一下午才发现是字段顺序问题。

代码示例:Android 端签名

// 生成签名
fun generateSignature(challenge: ByteArray, irisHash: ByteArray): ByteArray {
    val timestamp = System.currentTimeMillis().toString().toByteArray()
    
    // 按字典序拼接
    val dataToSign = challenge + irisHash + timestamp
    
    // 使用 Android Keystore 中的私钥签名
    val signature = Signature.getInstance("SHA256withECDSA")
    signature.initSign(privateKey)
    signature.update(dataToSign)
    return signature.sign()
}

服务器端验签

服务器收到签名后,需要做这几件事:

  • 检查挑战值是否有效:去 Redis 查一下,有没有这个挑战值,过期了没
  • 检查时间戳:跟服务器当前时间比,偏差超过 30 秒的直接拒绝
  • 验签:用客户端公钥验证签名
  • 比对虹膜特征:把签名里的特征哈希跟数据库里存的比对

注意:千万不要在服务器端存储完整的虹膜图像!只存特征哈希。我曾经见过一个项目把虹膜图片直接存数据库,这要是泄露了,用户连换密码的机会都没有。

安全传输:HTTPS 还不够吗?

够,但不够彻底。

HTTPS 能防中间人攻击,但防不了客户端被篡改。你想想看,如果攻击者逆向你的 App,把签名逻辑改了,HTTPS 也拦不住。

所以真正的安全传输是端到端的:

传输层 防护目标 实现方式
网络层 防窃听、防篡改 HTTPS + 证书锁定
应用层 防重放、防伪造 挑战值 + 签名
数据层 防泄露 敏感数据加密存储

我个人建议三层都做,别偷懒。尤其是证书锁定,很多开发者嫌麻烦就不做,结果被中间人攻击搞得很惨。

避坑指南

我这些年踩过的坑,列几个典型的:

  • 挑战值过期时间设太长:我曾经设了 5 分钟,结果被人利用窗口期重放攻击。现在一律 30 秒。
  • 签名算法选错:ECDSA 比 RSA 快,但有些老旧服务器不支持。提前确认好。
  • 公钥分发不安全:第一次注册时就把公钥传上去,但怎么保证公钥没被篡改?建议用证书链。
  • 日志里打印了签名数据:嗯,这个我都不好意思说,线上环境千万别打敏感日志。

总结一句话:挑战值解决「是不是本人操作」,签名解决「数据有没有被改」,HTTPS 解决「路上有没有人偷看」。三者缺一不可。

好了,这一章的内容就到这儿。记住,安全没有银弹,每一层防护都是在增加攻击者的成本。做到让攻击者觉得「划不来」,你就赢了。


公众号:蓝海资料掘金营,微信deep3321