Online Authentication Kit:DID 数字身份的颁发、出示与可信链【鸿蒙心迹】
证明自己年满18岁,为什么一定要把整张身份证交出去?

做实名认证的时候,最开始的思路很简单:用户要证明自己成年了,把身份证号、姓名、住址、有效期全传给服务端,服务端查一下不就行了?
结果真做的时候发现不对:我就证明个年龄,为什么要把我整张身份证的信息全给你?服务端拿到了我所有信息,存哪了?会不会泄露?
这时候才意识到:传统的身份验证方式,是"全量出示"。你要证明一个结论,得把所有原始材料都交出去。
那有没有办法:我只证明"我年满18岁"这个结论,不泄露其他任何信息?
一、传统身份验证为什么不行
先想清楚传统方式哪里不对。
| 方式 | 出示内容 | 隐私泄露 |
|---|---|---|
| 身份证验证 | 姓名+身份证号+住址+有效期 | 全泄露 |
| DID 凭证 | 只有"年满18岁"这个结论 | 不泄露 |
传统方式是:你把所有原始数据都交出去,验证者自己判断。问题在于:验证者拿到了所有数据,哪怕他只需要一个结论。
DID 的思路完全反过来:数据在用户自己手里。验证者只问一个问题,用户只回答这个问题的答案,其他什么都不给。
二、DID 到底是什么
DID 是 Decentralized Identifier,去中心化标识符。
它不是普通的账号 ID。普通的账号 ID 是平台给你发的,平台说你是谁你就是谁。DID 是你自己的身份标识,存在你自己的设备里,不是平台发的。
DID Document 是什么?就是 DID 的描述文件,里面存着你的公钥、验证方式这些信息。
| 概念 | 是什么 |
|---|---|
| DID | 你的身份标识 |
| DID Document | 你的身份描述 |
| DID Key | 你的密钥对 |
三、三方关系:Issuer、Holder、Verifier
整个数字身份体系,有三个角色:
- Issuer:颁发方,比如公安局、学校、银行。它给你发凭证,证明你有某个属性。
- Holder:持有方,就是用户自己。凭证存在你的设备里,只有你能拿出来。
- 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 发凭证的过程:
- Holder 把自己的 DID 发给 Issuer;
- Issuer 验证 Holder 身份;
- Issuer 生成一个数字凭证,里面有 Holder 的 DID、Issuer 的签名、要证明的属性;
- Issuer 把凭证发给 Holder;
- Holder 把凭证存在自己设备里。
这个凭证是 Issuer 签名过的。后面验证的时候,验证者只要验证 Issuer 的签名,就知道这个凭证是真的。
五、凭证出示的完整流程
出示凭证的时候,整个过程是这样的:
- Verifier 向 Holder 发起验证请求,带一个 Challenge;
- Holder 拿出对应的凭证,做最小化披露;
- Holder 把披露结果和签名发给 Verifier;
- Verifier 验证签名、验证 Issuer 的签名;
- 验证通过,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 防重放、凭证有效期、签名验证、私钥安全存储。这些才是整个可信链真正起作用的地方。
更多推荐



所有评论(0)