证明自己年满18岁,为什么一定要把整张身份证交出去?

文章封面

做实名认证的时候,最开始的思路很简单:用户要证明自己成年了,把身份证号、姓名、住址、有效期全传给服务端,服务端查一下不就行了?

结果真做的时候发现不对:我就证明个年龄,为什么要把我整张身份证的信息全给你?服务端拿到了我所有信息,存哪了?会不会泄露?

这时候才意识到:传统的身份验证方式,是"全量出示"。你要证明一个结论,得把所有原始材料都交出去。

那有没有办法:我只证明"我年满18岁"这个结论,不泄露其他任何信息?

一、传统身份验证为什么不行

先想清楚传统方式哪里不对。

方式出示内容隐私泄露
身份证验证姓名+身份证号+住址+有效期全泄露
DID 凭证只有"年满18岁"这个结论不泄露

传统方式是:你把所有原始数据都交出去,验证者自己判断。问题在于:验证者拿到了所有数据,哪怕他只需要一个结论。

DID 的思路完全反过来:数据在用户自己手里。验证者只问一个问题,用户只回答这个问题的答案,其他什么都不给。

二、DID 到底是什么

DID 是 Decentralized Identifier,去中心化标识符。

它不是普通的账号 ID。普通的账号 ID 是平台给你发的,平台说你是谁你就是谁。DID 是你自己的身份标识,存在你自己的设备里,不是平台发的。

DID Document 是什么?就是 DID 的描述文件,里面存着你的公钥、验证方式这些信息。

概念是什么
DID你的身份标识
DID Document你的身份描述
DID Key你的密钥对

三、三方关系:Issuer、Holder、Verifier

整个数字身份体系,有三个角色:

  1. Issuer:颁发方,比如公安局、学校、银行。它给你发凭证,证明你有某个属性。
  2. Holder:持有方,就是用户自己。凭证存在你的设备里,只有你能拿出来。
  3. Verifier:验证方,比如某个App、某个网站。它要验证你有没有某个属性。

这段代码解决什么问题: 创建 DID 身份。
文件: did/DidManager.ets
用途: DID 身份管理
接入位置: 用户首次开启数字身份

import { onlineAuthenticationKit } from '@kit.OnlineAuthenticationKit';

// 创建 DID 身份
let did = await onlineAuthenticationKit.createDidKey({
  keyType: 'ECC256'
});

let didId = did.did;
let didDocument = did.document;

// 保存到设备安全存储
await this.saveToSecureStorage(didId, didDocument);

这里最关键的是:私钥存在设备安全存储里,导出不出来。业务代码拿不到私钥。

系统架构图

四、凭证颁发的完整流程

凭证颁发是 Issuer 给 Holder 发凭证的过程:

  1. Holder 把自己的 DID 发给 Issuer;
  2. Issuer 验证 Holder 身份;
  3. Issuer 生成一个数字凭证,里面有 Holder 的 DID、Issuer 的签名、要证明的属性;
  4. Issuer 把凭证发给 Holder;
  5. Holder 把凭证存在自己设备里。

这个凭证是 Issuer 签名过的。后面验证的时候,验证者只要验证 Issuer 的签名,就知道这个凭证是真的。

五、凭证出示的完整流程

出示凭证的时候,整个过程是这样的:

  1. Verifier 向 Holder 发起验证请求,带一个 Challenge;
  2. Holder 拿出对应的凭证,做最小化披露;
  3. Holder 把披露结果和签名发给 Verifier;
  4. Verifier 验证签名、验证 Issuer 的签名;
  5. 验证通过,Verifier 只拿到他要的那个结论。

这段代码解决什么问题: 出示凭证。
文件: did/CredentialManager.ets
用途: 向验证方出示凭证
接入位置: 用户授权验证时

async presentCredential(verifierChallenge: string, attribute: string) {
  // 从设备里取出凭证
  let credential = await this.getCredential(attribute);
  
  // 最小化披露:只出示需要的属性
  let disclosed = await credential.disclose({
    attributes: [attribute],
    challenge: verifierChallenge
  });
  
  // 用 DID 私钥签名
  let signedResult = await this.didKey.sign(disclosed);
  
  // 发给验证方
  return signedResult;
}

这里最关键的两点:一个是 Challenge,防止重放攻击。每次验证的 Challenge 都不一样,别人截获了上次的验证结果也没用。另一个是最小化披露,只出示需要的属性,其他都不泄露。

运行效果图

六、为什么身份和凭证要分开

很多人以为 DID 就是数字身份证。不对。

DID 是你的身份标识,是"你是谁"。凭证是"你有什么属性"。

概念回答的问题
DID你是谁
凭证你有什么属性

你有一个 DID,然后有很多凭证:一个证明你年满18岁,一个证明你是某大学毕业,一个证明你有银行账号。每个凭证是独立的。

验证的时候,你可以只拿出"年满18岁"这个凭证,不用把其他的都拿出来。

七、几个容易踩的坑

第一个坑:把 DID 当成普通账号 ID。DID 是你自己的身份,不是平台发的。

第二个坑:DID 和数字凭证混为一谈。DID 是身份,凭证是属性证明。

第三个坑:服务端只验证字段不验证签名。光看字段值没用,必须验证签名,证明这个凭证是 Issuer 发的。

第四个坑:凭证过期后仍允许使用。凭证有有效期,过期了就没用了。

第五个坑:每次验证都要求用户提供全部身份信息。应该最小化披露,要什么给什么。

第六个坑:Challenge 重复使用。Challenge 要每次都新的,不然重放攻击。

第七个坑:把私钥导出到普通业务代码。私钥必须存在 TEE 里,不能导出。

业务流程图

八、TEE 在整个可信链里的位置

TEE 是可信执行环境。DID 的私钥就存在 TEE 里。

为什么要存在 TEE 里?因为私钥如果在普通内存里,应用被攻破了,私钥就泄露了。存在 TEE 里,私钥根本出不来,签名操作也是在 TEE 里完成的。

整个可信链是这样的:Issuer 用他的私钥签名凭证 → Holder 的 TEE 里存着自己的私钥 → Verifier 验证签名。每一环都有安全保障。

这次做数字身份最大的体会是:DID 不是"把身份证数字化",是整个身份验证模式变了。从"全量出示原始数据"变成"只出示结论"。数据在用户自己手里,用户决定出示什么。

真正做的时候,最容易忽略的不是 DID 协议本身,而是周边的东西:Challenge 防重放、凭证有效期、签名验证、私钥安全存储。这些才是整个可信链真正起作用的地方。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐