它到底做了什么
凭证就是某个机构签过名的一句话。“此人已满18岁。”“此人可以驾驶汽车。”让这句话有分量的是签名:它表示有权威为这句话背书,而且事后没有人改动过。
在纸上,在PDF里,一个签名覆盖整份文件。要证明其中一行,就得把整份交出去。为了证明年龄而出示驾照,对方连你的住址、证件号码和确切出生日期也一并知道了。这些他从没要过,现在却都有了。
选择性披露取消了这笔交易。签发方对每一项事实分别签名,凭证只携带每一项的指纹。披露哪些由你决定,其余的仍是指纹,不会透露任何内容。签名依然通过校验,因为它从来就不是盖在一个不可分割的整块上。
这一切都装在验证方扫描的那段码里:签名、指纹,以及你选择出示的事实。验证过程中不去取任何东西,所以一道门、一辆公交车、一间乡村诊所,或是巡航中的飞机,完全没有网络也能验证。
还差最后一块,而它恰恰最容易被忽略。能扫描的东西就能被拍照。所以出示的人还必须用一把永不离开设备的私钥,为当场的挑战值签名。少了这一步,别人凭证的截图也能通过。这个库会拒绝任何缺少它的出示。
为没有信号的那一刻而生
我做过 eCNH,巴西的数字驾照,四千多万人在用。教会我最多的不是那个应用,而是路边:警员在一格信号甚至没有信号的公路上扫一张驾照,必须在一秒内给出是或否。
这个答案需要的一切都装得进码里。签名证明签发方,claim 就摆在那里,唯一需要从外面拿的只有签发方的公钥,而它极少变动,随应用一起发出去、每周刷新一次就够了。
这类函式库是有的。它们是企业级 SDK:笨重、绑死在某一国的规范上,而且写得像是默认你已经在身份行业里做事。这一份,是产品工程师在某个周二就能加进项目的版本。
三方,其中没有一方是服务器
每个 claim 只签一次
签发方决定哪些 claim 日后可以隐藏,并为其中每一个签下一个摘要。
只送出被要求的部分
钱包把要留下的 claim 拿掉。对剩下的内容,签发方的签名依然成立。
不问任何人就给出答案
签名、有效期、吊销状态和每一条披露,都拿设备上固定的密钥来核对。
证明你满 18 岁,而不必交出生日
年龄核验的法规来得比工具快。常见做法是让顾客把证件照片上传给第三方,那是一场隐私灾难,也是一起只等着发生的泄露。
选择性披露把这件事做对了。验证方就算想知道出生日期也做不到,因为那个值从未离开钱包。这个性质是密码学上的,不是隐私政策里的一句承诺。
// 凭证里有姓名、住址、出生日期和证件号码。
// 酒吧拿到的是一个布尔值。
const presentation = await present(credential, { disclose: ['over_18'] })
const result = await verify(presentation, { trust })
result.claims // { over_18: true }
result.claims.birth_date // undefined,而且从未被传输
result.withheld // 4,而且它说不出是哪四个
真实数字,包括那个难看的
一份贴近现实的驾照。八个 claim、五年有效期、一个状态列表指针。是量出来的,不是估的:
| 凭证 | 字符数 | 二维码版本 |
|---|---|---|
| 全部可见 | ~740 | 18,扫得挺好 |
| 八个 claim 全部可披露 | ~1590 | 27,太密 |
presenting only over_18 | ~1115 | 22,仍然偏密 |
fits() 告诉你处境。
别信我的话
演练场在你的浏览器里跑起整个函式库,并朝它打出八种真实攻击。每一种都写明它预期的拒绝代码,所以你可以拿这个库跟它自己的说法对质,而不是去信一份 README。
为什么选它,以及什么时候别选
先说五个理由,再说实话的部分。
- 约1,300行,真的读得完小到一名工程师一个下午就能通读一遍,明白它在做什么。读不了的安全,只能靠相信。
- 零依赖运行时不从npm引入任何东西。要审计的供应链只有它本身,也没有什么会在下周悄悄变掉。
- 无法完整校验的,一律拒绝没有部分通过,也没有可以忽略的警告。每一次失败都返回一个有名字的原因,可以记录也可以处理,绝不只丢回一个false。
- 在验证发生的地方运行Node、Deno、Bun、浏览器、React Native 和边缘计算环境。同一份代码,跑在平台自带的标准密码学之上。
- 三种方式测试单元测试、随 RFC 9901 一同发布的官方测试向量,以及用随机畸形输入反复攻击、专门寻找漏网情形的属性测试。
以及什么时候它是错误的工具。
- 你需要 ISO 18013-5 移动驾照那是另一套编码,CBOR 与 COSE,也是另一套标准。这个库讲的是 SD-JWT,欧洲钱包和 OpenID 规范使用的格式。问题相近,活儿不同。
- 你要的是一个完整钱包它负责验证和出示,不保存凭证,不管理设备,也不处理开户流程。它是一个零件,为放进你的产品而做。
- 在受监管的场景上线前需要第三方审计目前还没有。测试、RFC 测试向量以及每一行代码都是公开的,但用于受监管的部署,请为自己的评审留出预算。
安装
npm i qredential
Node 20 及以上、所有现代浏览器、React Native。大约 1,300 行源码,没有任何运行时依赖,正是为了让「信任之前先读一遍」这件事切实可行。
用标准,不是自创
- SD-JWT, RFC 9901选择性披露与密钥绑定,支持嵌套与递归,正是欧洲身份钱包所用的机制
- SD-JWT VC凭证的结构
- Token Status List靠一份缓存副本就能工作的吊销检查
- base45, RFC 9285二维码信封,为读码器兼容性而选
不是 ISO 18013-5 mDL,那边是 CBOR 和 COSE 而非 JWT。它在计划里,而用到未支持特性的凭证会被拒绝,而不是被理解一半。声称做了半个合规标准,比什么都不声称更糟。