Copyright © 2026 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本规范描述了一种使用可验证凭证 [VC-DATA-MODEL-2.0] 保护光学条形码的机制, 例如驾驶证上的条形码(PDF417)和旅行证件上的机器可读区(MRZ)。 可验证凭证 表示足够紧凑,可以容纳在 150 字节以内, 因此可与使用标准印刷工艺印刷在实体卡上的 传统二维条形码集成。
本节描述的是本文档 在发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 标准和草案 索引中找到。
本文档由 可验证凭证工作组 作为工作草案发布,并使用 推荐标准 轨道。
作为 工作草案发布,并不表示 W3C 及其成员认可。
这是一份草案文档,可能随时被其他文档 更新、取代或废弃。除作为正在进行中的工作外, 不宜引用本文档。
本文档由一个根据 W3C 专利 政策 运作的小组制作。 W3C 维护一个 任何专利披露的公开列表, 这些披露与该小组的交付成果有关;该页面还包括 披露专利的说明。任何实际知晓某项专利, 并认为该专利包含 必要权利要求 的个人,必须依照 W3C 专利政策第 6 节 披露相关信息。
本文档受 2025年8月18日 W3C 流程文档约束。
实体凭证,例如驾驶证、护照和旅行 凭证,通常包含机器可读数据,可用于快速读取 文档中的信息。这些信息以诸如 PDF417 [ISO15438-2015]、 机器可读区(MRZ)[ICAO9303-3], 以及 其他格式化为一维或二维“条”的光学可扫描代码等格式编码; 因此产生了“条形码”这一术语。这些信息通常 未受到防篡改保护,而现成的条形码生成和 扫描库意味着任何人生成这些 条形码都相当容易。
因此,对于这些条形码的签发者而言,保护 条形码中包含的信息以及 生成该条形码的实体是有用的。
可验证凭证数据模型 v2.0 规范为表达凭证信息提供了一个全球标准, 例如驾驶证或旅行证件中的信息。可验证凭证数据完整性 1.0 规范为保护凭证信息提供了一个 全球标准。这两项规范结合后,提供了一种防止凭证被篡改、 表达凭证作者身份,并以隐私保护方式提供凭证当前状态的 手段。然而,这些数据格式通常 过大,无法在光学条形码中表达。
链接数据的紧凑二进制对象 表示 v1.0 规范提供了一种压缩受保护 可验证 凭证的方法,使其达到可以 将信息表达为光学条形码,或嵌入光学 条形码中的程度。
本规范描述了一种保护光学条形码的机制, 例如驾驶证上的条形码(PDF417)和旅行证件上的机器可读区(MRZ), 其方法是使用可验证凭证 [VC-DATA-MODEL-2.0] 来表达 有关条形码本身或条形码主体的信息。 接着,该可验证凭证使用数据完整性 [VC-DATA-INTEGRITY] 进行保护,然后 使用 CBOR-LD [CBOR-LD] 进行压缩。
可验证凭证可通过两种主要机制 用于保护条形码:
这两个用例都为可验证凭证保护的数据实现了真实性、 完整性和防篡改性。此外,对于需要这些能力的用例, 这些可验证凭证可以按每个条形码为单位 被撤销或暂停。
以下各节提供了一些入门示例,说明 本规范可如何用于通过可验证 凭证,用数字签名增强现有实体凭证。
本节提供一个示例,说明如何利用本规范中的技术 来保护使用 PDF417 条形码的驾驶证上的 光学条形码。我们从一个示例驾驶证开始:
驾驶证背面包含一个 PDF417 条形码:
PDF417 数据包含使用本规范中描述的算法 保护的信息。也就是说,PDF417 条形码包含一个 如下形式的可验证 凭证。
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vdl/v2",
"https://w3id.org/vdl/utopia/v1"
],
"type": [
"VerifiableCredential",
"OpticalBarcodeCredential"
],
// 下面的 issuer 值在上方的 'utopia/v1' 上下文中定义为 URL
"issuer": "did:web:dmv.utopia.example",
"credentialStatus": {
"type": "TerseBitstringStatusListEntry",
"terseStatusListBaseUrl": "https://dmv.utopia.gov/statuses/12345/status-lists"
"terseStatusListIndex": 123567890
},
"credentialSubject": {
"type": "AamvaDriversLicenseScannableInformation",
"protectedComponentIndex": "uP_BA"
},
"proof": {
"type": "DataIntegrity",
"cryptosuite": "ecdsa-xi-2023",
// 下面的公钥在上方的 'utopia/v1' 上下文中定义为 URL
"verificationMethod": "did:web:dmv.utopia.example#key-1",
"proofPurpose": "assertionMethod",
"proofValue": "z4peo48uwK2EF4Fta8P...HzQMDYJ34r9gL"
}
}
随后,上述可验证 凭证使用 [CBOR-LD] 压缩为以下输出(采用 CBOR 诊断表示法):
1281{
1 => [ 32768, 32769, 32770], // @context
155 => [ 116, 164 ], // type
192 => 174, // issuer
186 => { 154 => 166, 206 => 178, 208 => 1234567890 }, // credentialStatus
188 => { 154 => 172, 180 => h'753FF040 }, // credentialSubject
194 => { // proof
154 => 108, // type
214 => 4, // cryptosuite
224 => 230 // verificationMethod
228 => 176, // proofPurpose
210 => Uint8Array(65) [ ... ], // proofValue
}
}
本节提供一个示例,说明如何利用本规范中的技术 将出生证明作为可验证 凭证进行保护,然后 将其表达为打印纸质文档上的二维码:
二维码编码以下可验证 凭证。有关 编码的详细信息可在下方单独的标签页中查看:
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vital-records/v1rc1"
],
"type": [
"VerifiableCredential",
"BirthCertificateCredential"
],
"issuer": "https://hospital.example/issuer",
"validFrom": "2023-09-30T11:30:00Z",
"credentialSubject": {
"type": "BirthCertificate",
"certificationDate": "2023-09-30T13:44:52Z",
"newborn": {
"type": "Newborn",
"name": "Tim Doe",
"gender": "Male",
"birthDate": "2023-10-05T14:29:00Z",
"birthPlace": {
"type": "PostalAddress",
"streetAddress": "123 Hospital Rd",
"addressLocality": "Utopia Town",
"addressRegion": "Utopolis",
"postalCode": "12345",
"addressCountry": "Utopia"
},
"parent": [{
"type": "Mother",
"name": "Jane Doe",
"namePriorToMarriage": "Jane Smith"
}, {
"type": "Father",
"name": "John Doe"
}]
}
}
}
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vital-records/v1rc1"
],
"type": [
"VerifiableCredential",
"BirthCertificateCredential"
],
"issuer": "https://hospital.example/issuer",
"validFrom": "2023-09-30T11:30:00Z",
"credentialSubject": {
"type": "BirthCertificate",
"certificationDate": "2023-09-30T13:44:52Z",
"newborn": {
"type": "Newborn",
"name": "Tim Doe",
"gender": "Male",
"birthDate": "2023-10-05T14:29:00Z",
"birthPlace": {
"type": "PostalAddress",
"streetAddress": "123 Hospital Rd",
"addressLocality": "Utopia Town",
"addressRegion": "Utopolis",
"postalCode": "12345",
"addressCountry": "Utopia"
},
"parent": [
{
"type": "Mother",
"name": "Jane Doe",
"namePriorToMarriage": "Jane Smith"
},
{
"type": "Father",
"name": "John Doe"
}
]
}
},
"proof": {
"type": "DataIntegrityProof",
"created": "2026-06-23T17:09:43Z",
"verificationMethod": "did:key:zDnaep3M6F9WVbtR4JaDCpS84JphmppHXBh8NNLWQYJDvpa8i",
"cryptosuite": "ecdsa-rdfc-2019",
"proofPurpose": "assertionMethod",
"proofValue": "z5dT3pjsBWEXyquJXoNX1atkXDisQaLJMcrkJPNYMSMqSMocrqhHkK9nRiztfTDSANzg5iEi6YurddLfD
nnStdeTm"
}
}
51997(
[
1,
{
1: [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vital-records/v1rc1"
],
157: [
118,
164
],
304: {
156: 162,
204: "2023-09-30T13:44:52Z",
260: {
150: "Tim Doe",
156: 174,
188: "2023-10-05T14:29:00Z",
196: {
156: 178,
324: "Utopia",
326: "Utopia Town",
328: "Utopolis",
330: "12345",
332: "123 Hospital Rd"
},
226: "Male",
267: [
{
150: "Jane Doe",
156: 172,
258: "Jane Smith"
},
{
150: "John Doe",
156: 170
}
]
}
},
308: [
2,
"hospital.example/issuer"
],
310: {
156: 108,
336: 1782234583,
338: "ecdsa-rdfc-2019",
348: 354,
350: h'7ae780156acdf39b8a76dd9dfdcc7d9cf32636e91579f894a2f1fc8cafc9fd0fb5a72660ed343f1ca8854
2eafd94e542c509080ab479da5e303fb74dcdd2065674',
352: [
1025,
h'8024035ecd0dd569e4db7f6feefdb0249d9156db32899632ae707466310043e3cb954b'
]
},
320: 1696073400
}
])}
VC1-R0OR*W3H90Q80L8FA9DIWENPEJ/5S4F03F53F7*5$KE$:5CPEXPC C1$CBWEAECCPEI.EL8FA9DIWENPEJ/553FPED7*5$K
E006-EDAECOX5Z C04EKVC006DB6DOC1534KG$-EOXKY60$RK0XJ6MK5%PNF6QF63W5CA79L6/SAJL6:Q66G7KG6B73KQ0*43$2D
YEDP34W3E053I53W531VE8466L6$963W5HX6-963G7PA7646OHBW%O053M53B735T86 A/3EMEDB73R+86 A/3EMED-3454EF-DD
70O8DHWES9EXVDZOEF70UZCQF60R6B73$T9*96%K6379WQE-EDAEC*34JTC-RS9Z9TVDB73LK1$RKT0J6I91/DP34W3E053G53B7
3XD06I91/D+34J$DAWE6MKT0JKI949DP34W3E053E53B73WS61E059DWQE-EDAEC.%5$9FQ$DTVDW:5ZQE%$E4JE+60+:K0XJ/TD
L70CF37J7U8RN70W3EJPCHQEOX57VC9OCNF61A6B73.SB*70B73W-BMC88VI17O7KH*PAHK7DEN BQ$HAPSE/EE451E7EPCRGYJZ
V5TPNQ62SAJO2AUSG2DUDIO 2IOER26ON44.4M:-QVC0%ZBY+M/ACB7319CE73T70/L4*O4E:BVX1IHDVXRY6E93WDS4 GI:VRWH
HEI6X9EG-CM10+ZSE*IB73U485ZCA%0
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vital-records/v1rc1"
],
"type": [
"VerifiableCredential",
"BirthCertificateCredential"
],
"issuer": "https://hospital.example/issuer",
"validFrom": "2023-09-30T11:30:00Z",
"credentialSubject": {
"type": "BirthCertificate",
"certificationDate": "2023-09-30T13:44:52Z",
"newborn": {
"type": "Newborn",
"name": "Tim Doe",
"gender": "Male",
"birthDate": "2023-10-05T14:29:00Z",
"birthPlace": {
"type": "PostalAddress",
"streetAddress": "123 Hospital Rd",
"addressLocality": "Utopia Town",
"addressRegion": "Utopolis",
"postalCode": "12345",
"addressCountry": "Utopia"
},
"parent": [
{
"type": "Mother",
"name": "Jane Doe",
"namePriorToMarriage": "Jane Smith"
},
{
"type": "Father",
"name": "John Doe"
}
]
}
},
"proof": {
"type": "DataIntegrityProof",
"created": "2026-06-23T17:09:43Z",
"verificationMethod": "did:key:zDnaep3M6F9WVbtR4JaDCpS84JphmppHXBh8NNLWQYJDvpa8i",
"cryptosuite": "ecdsa-rdfc-2019",
"proofPurpose": "assertionMethod",
"proofValue": "z3xU4Hqfs6EKrrwiRyNP6Pwxfo8yHfyHA5SFt4Dn9cSfQVcqDsCzSeD8CSDs2XqU7Y4qB6zkWrJqt9t2P
s13mzgoA"
}
}}
以下是本规范中技术的设计目标:
本文档通篇使用的术语定义在 可验证凭证数据模型 v2.0 规范的 术语一节中,以及 可验证凭证数据完整性 1.0 规范中。
A 和 B)
的顺序,
如 [XPATH-FUNCTIONS]
中所定义,它定义了一种按码点比较
字符串的
全序。
请注意,对于 UTF-8 编码的字符串,比较字节序列会得到与码点
顺序相同的结果。
除标记为非规范性的章节外,本规范中的所有编写指南、图表、示例和注释 均为非规范性内容。本规范中的其他所有内容均为规范性内容。
本文档中的关键词 MAY、MUST、RECOMMENDED、REQUIRED 和 SHOULD 应按 BCP 14 [RFC2119] [RFC8174] 中的描述解释,但仅当它们像这里所示那样全部 大写出现时才如此。
一致性 文档是数据模型的任何具体表达, 且该表达符合本规范中的规范性陈述。具体而言, 必须执行本文档 2. 数据模型和 3. 算法 各节中的所有相关规范性陈述。
一致性处理器是以软件和/或硬件实现的任何算法, 它生成或消费 一致性文档。一致性处理器在消费 不符合一致性的文档时MUST产生错误。
本文档包含 JSON 和 JSON-LD 数据示例。其中一些示例
是无效 JSON,因为它们包含诸如内联注释(//)
用于解释某些部分,以及省略号(...)用于表示省略
与示例无关的信息。如果实现者希望将这些示例视为有效 JSON 或 JSON-LD,
则需要移除这些部分。
以下各节概述了本规范使用的数据模型, 用于表达保护光学印刷信息的可验证凭证, 例如旅行证件上的条形码和机器可读区。
OpticalBarcodeCredential 用于以一种提供 1)作者身份信息、2)防篡改性,
以及 3)可选的撤销和暂停状态的方式,保护光学
条形码的内容。换句话说,
该凭证可以告诉你是谁签发了该光学条形码,
光学条形码自首次签发以来是否被篡改过,以及
该光学条形码的签发者是否仍然
保证
该文档仍然有效。这些特性为实体文档提供了重要的
反欺诈保护。
OpticalBarcodeCredential 的 credentialSubject 要么是
AamvaDriversLicenseScannableInformation 类型,要么是 MachineReadableZone。
AamvaDriversLicenseScannableInformation 表示
该可验证凭证
保护实体
文档上的 PDF417 条形码,以及
可验证凭证
中表达的信息。
MachineReadableZone 表示
该可验证凭证
保护实体文档上的机器可读区,
以及
可验证凭证
中表达的信息。
如果 OpticalBarcodeCredential 的类型为
AamvaDriversLicenseScannableInformation,
则有一个REQUIRED的附加字段 protectedComponentIndex,
其中包含 PDF417 中哪些字段
被数字签名的信息。protectedComponentIndex MUST
是一个三字节/24 位值,
以 multibase-base64url 编码后在 JSON-LD 凭证中总计为 5 个字符。符合 AAMVA 的驾驶证 PDF417
[aamva-dl-id-card-design-standard]
中有 22 个
必填字段,protectedComponentIndex 值的前 22 位对应这些字段。每个
AAMVA 必填
字段都以一个三字符元素 ID 开头(例如,DBA 表示文档有效期)。为了在
protectedComponentIndex 值中的位与这些字段之间构造
映射,请按 Unicode 码点顺序对这些
元素 ID 排序。然后,如果
protectedComponentIndex 中位置 i 的位为 1,
则已排序元素 ID 中位置 i 的
AAMVA 必填字段由数字
签名保护。protectedComponentIndex 中最后两位MUST
为 0。更多
信息请参见第 3.2.4.4
创建 opticalDataBytes。
为了实现尽可能高的压缩率,RECOMMENDED
issuer 和 verificationMethod 字段使用来自 JSON-LD Context 的术语,
这样由于 CBOR-LD 的语义
压缩机制,它们可以被压缩到几个字节。
下面提供了一个使用本节中指定属性的 光学条形码凭证示例:
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vdl/v2",
"https://w3id.org/vdl/utopia/v1"
],
"type": [
"VerifiableCredential",
"OpticalBarcodeCredential"
],
"issuer": "did:web:dmv.utopia.example",
"credentialStatus": {
"type": "TerseBitstringStatusListEntry",
"terseStatusListBaseUrl": "dmv.utopia.gov/statuses/12345/status-lists"
"terseStatusListIndex": 123567890
},
"credentialSubject": {
"type": "AamvaDriversLicenseScannableInformation",
"protectedComponentIndex": "uP_BA"
}
}
TerseBitstringStatusListEntry 是
BitstringStatusListEntry 的一种紧凑表示,如 Bitstring Status List v1.0
规范中所定义。
TerseBitstringStatusListEntry 类型的对象MUST具有两个
附加属性:
terseStatusListBaseUrl,它标识与此凭证关联的状态列表所在位置。
terseStatusListBaseUrl MUST 是 URL [URL]。
terseStatusListIndex,它指定上述 URL 处的一个单独状态。
terseStatusListIndex MUST
能表示为 32 位无符号整数。
要处理 TerseBitstringStatusListEntry,请应用第
3.2.3 转换状态列表条目中的算法,将其转换为
BitstringStatusListEntry,
然后按 Bitstring Status List v1.0
中所述进行处理。
实现者需要为单个状态列表的长度设置一个值 listLength。对于 32 位
terseStatusListIndex,
这会得到状态列表数量 listCount = 2^32 / listLength。
从 TerseBitstringStatusListEntry 转换为
BitstringStatusListEntry 时需要 listLength。
注意到 listLength 的某些值会损害这些状态列表的隐私保护属性,
实现MUST使用 listLength = 2^26 和 listCount
= 2^6。
RECOMMENDED 实现者在将 CBOR-LD 编码的
AamvaDriversLicenseScannableInformation
凭证编码进 PDF417 之前,先将其字符编码为 base64url。可以在
base 编码的 VCB 数据前添加 multibase 标头。PDF417 字段标头规范预计会指明 multibase
标头是否存在。
REQUIRED 实现者在将 CBOR-LD 编码的
MachineReadableZone 凭证
编码进二维码之前,将其重新编码为带有前置字符串 'VC1-' 的 base45-multibase。
以下一节描述了用于在实体介质上添加和验证数字 证明的算法,这些证明保护光学信息,例如驾驶证和旅行证件上的 条形码和机器可读区。
本节包含对编码和解码 可验证 凭证通用的算法。
本规范要求在编码和解码 可验证 凭证时, 向 CBOR-LD 处理器提供一个特定于应用的压缩表。针对各种 签发者的所有上下文 URL 的注册表以 逗号分隔值文件形式提供,并且可通过对该文件的 变更请求以仅追加和先到先得的方式更新和修改。 实现SHOULD每月 检索并使用最新文件,以确保压缩和解压缩支持最新值。
以下算法指定了如何将可验证 凭证编码为 可在二维码中表达的文本字符串。必需输入为一个 可验证 凭证(映射 inputDocument),以及一组选项 (映射 options)。输出是一个已编码的可验证 凭证 (字符串)或错误。 每当该算法编码字符串时,它MUST使用 UTF-8 编码。
45 作为
targetBase,以及
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./: 作为 baseAlphabet 传入。
VC1-、
R(base45 的 Multibase
前缀),以及
base45Value。
以下算法指定了如何解码已编码进二维码的可验证 凭证。 必需输入为文本字符串(字符串 inputDocument),以及一组选项(映射 options)。输出是一个 可验证 凭证(映射)或错误。每当该算法 编码 字符串时,它MUST使用 UTF-8 编码。
VC1-R 开头。如果不是,
则MUST引发
错误。
VC1-R)
后的 inputDocument。
45 作为
sourceBase,以及
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./: 作为 baseAlphabet 传入。
本节包含特定于编码和解码
type 为 OpticalBarcodeCredential 的
可验证凭证
的算法。
OpticalBarcodeCredential 中的
BitstringStatusListCredential
(如 Bitstring Status List
v1.0 规范中所定义)。
TerseBitstringStatusListEntry,
并将 statusListEntryTerse.index 设置为
statusListEntryVerbose.statusListIndex 的整数表示。
OpticalBarcodeCredential,
其中 unsignedStatus.issuer 设置为 issuerUrl,
并且
unsignedStatus.credentialStatus
设置为 statusListEntryTerse。
OpticalBarcodeCredential。
本节中的算法用于将
TerseBitstringStatusListEntry 转换为 BitstringStatusListEntry;
在已对可验证
凭证
执行验证之后,
该对象会在验证过程中使用。
在可验证
凭证验证完成之后,
该算法接受一个 OpticalBarcodeCredential 可验证
凭证
(结构 vc)、
一个整数 listLength(包含与 vc 关联的
BitstringStatusListCredential 中的条目数量),以及一个字符串
statusPurpose(例如,'revocation'、'suspension'...)作为输入,并返回
一个 'BitstringStatusListEntry' 对象。
floor() 操作)。
result 可用作 Bitstring Status List v1.0 规范中 验证算法的输入。
ecdsa-xi-2023 密码套件实际上是 ecdsa-rdfc-2019
算法 [VC-DI-ECDSA],并添加了一个步骤,
该步骤接受一些“额外信息”
(xi)作为输入,例如原始光学条形码数据,并将该数据
纳入由数字签名保护的信息中。本节中的算法
详细说明了如何创建和验证此类签名。
要生成证明,MUST执行数据完整性 [VC-DATA-INTEGRITY] 规范中 第 4.1 节:添加证明中的算法。 对于该算法,特定于密码套件的 转换算法定义在 数据 完整性 ECDSA 密码套件 v1.0 的 转换 (ecdsa-rdfc-2019)一节中, 哈希算法定义在第 3.2.4.3 哈希 (ecdsa-xi-2023)中,并且 证明序列化算法定义在 数据 完整性 ECDSA 密码套件 v1.0 的 证明序列化 (ecdsa-rdfc-2019)一节中。
要验证证明,MUST执行数据完整性 [VC-DATA-INTEGRITY] 规范中 第 4.2 节:验证证明中的算法。对于该算法,特定于密码 套件的 转换算法定义在 数据 完整性 ECDSA 密码套件 v1.0 的 转换 (ecdsa-rdfc-2019)一节中, 哈希算法定义在 第 3.2.4.3 哈希 (ecdsa-xi-2023)中,并且 证明验证算法定义在 数据 完整性 ECDSA 密码套件 v1.0 的 证明验证 (ecdsa-rdfc-2019)一节中。
哈希算法是在 数据完整性 ECDSA 密码套件 v1.0 规范的 哈希 (ecdsa-rdfc-2019)一节中定义的算法, 并添加了如下所述的光学数据哈希。假定 实现会向该哈希算法提供机器可读光学数据(PDF417 或 MRZ 数据)。
该算法所需的输入是一个已转换数据文档 (transformedDocument)、一个规范证明配置 (canonicalProofConfig),以及光学数据 (opticalDataBytes)。输出会产生一个表示为 一系列字节的哈希数据值。
哈希算法是在 数据完整性 ECDSA 密码套件 v1.0 的 哈希 (ecdsa-rdfc-2019)一节中定义的算法,并将步骤 3 替换为以下两个步骤:
credentialSubject.protectedComponentIndex 从 multibase-base64url 解码为
二进制后所得长度为 24 的位串的前 22 位。
1 的位:
\n,U+000A)连接到末尾,
并将结果追加到 dataToCanonicalize。
证明配置算法是在 数据 完整性 ECDSA 密码套件 v1.0 的 证明配置 (ecdsa-rdfc-2019)一节中定义的算法,并将 步骤 4 替换为以下步骤:
DataIntegrityProof,
并且
proofConfig.cryptosuite 未设置为 ecdsa-xi-2023,
则MUST引发
INVALID_PROOF_CONFIGURATION 错误。
本节为非规范性内容。
在阅读本节之前,强烈建议读者熟悉 数据完整性规范的 安全考量一节中提供的一般安全建议,以及 ECDSA 密码套件规范的 安全考量一节中提供的具体安全建议。
在以下各节中,我们会回顾这些要点,并引导 读者了解更多信息。
针对 OpticalBarcodeCredentials 的一种攻击向量涉及复制
包含数字签名的光学条形码,以用于欺诈性文档。
虽然复制的条形码会像原始条形码一样通过签名验证,但此攻击
可通过文档验证者检查以下三项来缓解:签名数据
与文档上可见的数据相匹配,签名数据与
用户的实体属性相匹配,并且可见数据与用户的实体属性相匹配。当这
三项全部等同时,OpticalBarcodeCredential 可能是
副本的唯一方式,是欺诈性文档创建者能够访问一个真实的
OpticalBarcodeCredential,且其中已签名的实体属性与
欺诈性文档使用者的属性完全重叠。存在一个未被发现的
被盗 OpticalBarcodeCredential,并且其完全匹配
任意人员外观的可能性很低,因此此攻击不太可能成功。
在某些情况下,数字签名可能无法覆盖
现有光学数据的全部内容。例如,考虑这样一种情况:
某个序列号由实体凭证制造商注入,
因而签发者在签名时并不知道该序列号。在这种情况下,验证者
会假定任何未被数字签名的数据都可能已在
光学条形码中被更改,而不会影响 OpticalBarcodeCredential
成功验证的能力。
在检查光学条形码中的数据是否与 文档上可见的数据以及文档持有者的特征相匹配时,实现者 被建议只使用经过数字签名的字段。验证者被 建议只使用受数字签名保护的字段,无论 其他字段在未签名文档上的欺诈检测中多么常用。例如, 如果眼睛颜色和头发颜色受签名保护,但 持有者的 肖像未受保护,则建议验证者在尝试通过肖像检测欺诈时, 更强调眼睛 颜色和头发颜色。
建议由验证者使用的软件的实现者, 在验证过程中只显示 已通过数字签名保护的卡片数据。显示 未签名数据(这些数据可能已被篡改)可能会干扰欺诈检测。
建议验证者
始终使用可信程序和接口来检查
OpticalBarcodeCredential 的有效性。使用不可信软件验证文档
可能导致欺诈性凭证被接受,或真实凭证被盗。
在阅读本节之前,强烈建议读者熟悉 数据完整性规范的 安全考量一节中提供的一般安全建议,以及 ECDSA 密码套件规范的 安全考量一节中提供的具体安全建议。
以下一节描述了实现本规范的开发者 应当了解的隐私考量,以避免违反 隐私假设。
本节为非规范性内容。
本节包含可验证凭证条形码的示例,以及 它们如何生成和验证的逐步过程。
在本节中,我们将分析两个贯穿示例:一个用于保护 乌托邦就业许可文件 MRZ 的 VCB,以及一个用于保护 乌托邦驾驶证 PDF417 的 VCB。
我们从将由 VCB 签名的数据开始(即 PDF417 中的 AAMVA 必填字段):
DACJOHN DADNONE DAG123 MAIN ST DAIANYVILLE DAJUTO DAKF87P20000 DAQF987654321 DAU069 IN DAYBRO DBA04192030 DBB04191988 DBC1 DBD01012024 DCAC DCBNONE DCDNONE DCFUTODOCDISCRIM DCGUTO DCSSMITH DDEN DDFN DDGN
为简单起见,假设你想要签名的 PDF417 中唯一数据是
名字(DAC)、姓氏(DCS)和驾驶证号(DAQ)。
用于
protectedComponentIndex 的位串值随后为 100000100000000000100000,
并且
protectedComponentIndex 的值为 "uggAg"。应用
3.2.4.4 创建
opticalDataBytes,我们得到
canonicalizedData = 'DACJOHN\nDAQF987654321\nDCSSMITH\n' opticalDataBytes: [188, 38, 200, 146, 227, 213, 90, 250, 50, 18, 126, 254, 47, 177, 91, 23, 64, 129, 104, 223, 136, 81, 116, 67, 136, 125, 137, 165, 117, 63, 152, 207]
现在,我们可以将此哈希值与
3.2.4.3 哈希
(ecdsa-xi-2023) 一起使用,以签名该 VC。
使用 BitstringStatusListCredential 执行
3.2.1
编码 OpticalBarcodeCredential,我们得到以下 JSON-LD VC:
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vc-barcodes/v1",
"https://w3id.org/utopia/v2"
],
"type": [
"VerifiableCredential",
"OpticalBarcodeCredential"
],
"credentialSubject": {
"type": "AamvaDriversLicenseScannableInformation",
"protectedComponentIndex": "uggAg"
},
"issuer": "did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj",
"credentialStatus": {
"type": "TerseBitstringStatusListEntry",
"terseStatusListBaseUrl": "https://sandbox.platform.veres.dev/statuses/z19rJ4oGrbFCqf3cNTVDHSbNd/status-lists",
"terseStatusListIndex": 3851559041
},
"proof": {
"type": "DataIntegrityProof",
"verificationMethod": "did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj#zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj",
"cryptosuite": "ecdsa-xi-2023",
"proofPurpose": "assertionMethod",
"proofValue": "z4g6G3dAZhhtPxPWgFvkiRv7krtCaeJxjokvL46fchAFCXEY3FeX2vn46MDgBaw779g1E1jswZJxxreZDCrtHg2qH"
}
}
现在,我们可以对该 VC 应用 CBOR-LD 压缩。这里,我们使用 最新版本的 CBOR-LD;不过,在本节末尾,我们提供了 使用旧版本 CBOR-LD 编码的 VCB,用于与 未保持最新的 CBOR-LD 实现进行互操作性测试。
对于本规范,我们已保留值为 100 的 CBOR-LD 注册表条目
(即,这些载荷将以标签 0x0664 开头)。随后,
使用 CBOR-LD 编码的参数可在 CBOR-LD 规范的注册表中找到,
如下所示:
registryEntryId: 100
typeTable:
{
"context":
{
"https://www.w3.org/ns/credentials/v2": 32768,
"https://w3id.org/vc-barcodes/v1": 32769,
"https://w3id.org/utopia/v2": 32770
},
"https://w3id.org/security#cryptosuiteString":
{
"ecdsa-rdfc-2019": 1,
"ecdsa-sd-2023": 2,
"eddsa-rdfc-2022": 3,
"ecdsa-xi-2023": 4
}
}
处理上下文并为上下文术语分配整数值后, 应得到的术语到 ID 映射如下:
Map(97) {
'@context' => 0,
'@type' => 2,
'@id' => 4,
'@value' => 6,
'@direction' => 8,
'@graph' => 10,
'@included' => 12,
'@index' => 14,
'@json' => 16,
'@language' => 18,
'@list' => 20,
'@nest' => 22,
'@reverse' => 24,
'@base' => 26,
'@container' => 28,
'@default' => 30,
'@embed' => 32,
'@explicit' => 34,
'@none' => 36,
'@omitDefault' => 38,
'@prefix' => 40,
'@preserve' => 42,
'@protected' => 44,
'@requireAll' => 46,
'@set' => 48,
'@version' => 50,
'@vocab' => 52,
'...' => 100,
'BitstringStatusList' => 102,
'BitstringStatusListCredential' => 104,
'BitstringStatusListEntry' => 106,
'DataIntegrityProof' => 108,
'EnvelopedVerifiableCredential' => 110,
'EnvelopedVerifiablePresentation' => 112,
'JsonSchema' => 114,
'JsonSchemaCredential' => 116,
'VerifiableCredential' => 118,
'VerifiablePresentation' => 120,
'_sd' => 122,
'_sd_alg' => 124,
'aud' => 126,
'cnf' => 128,
'description' => 130,
'digestMultibase' => 132,
'digestSRI' => 134,
'exp' => 136,
'iat' => 138,
'id' => 140,
'iss' => 142,
'jku' => 144,
'kid' => 146,
'mediaType' => 148,
'name' => 150,
'nbf' => 152,
'sub' => 154,
'type' => 156,
'x5u' => 158,
'AamvaDriversLicenseScannableInformation' => 160,
'MachineReadableZone' => 162,
'OpticalBarcodeCredential' => 164,
'TerseBitstringStatusListEntry' => 166,
'protectedComponentIndex' => 168,
'did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj' => 170,
'did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj#zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj' => 172,
'did:key:zDnaeZSD9XcuULaS8qmgDUa6TMg2QjF9xABnZK42awDH3BEzj' => 174,
'did:key:zDnaeZSD9XcuULaS8qmgDUa6TMg2QjF9xABnZK42awDH3BEzj#zDnaeZSD9XcuULaS8qmgDUa6TMg2QjF9xABnZK42awDH3BEzj' => 176,
'https://sandbox.platform.veres.dev/statuses/z19rJ4oGrbFCqf3cNTVDHSbNd/status-lists' => 178,
'confidenceMethod' => 180,
'credentialSchema' => 182,
'credentialStatus' => 184,
'credentialSubject' => 186,
'evidence' => 188,
'issuer' => 190,
'proof' => 192,
'refreshService' => 194,
'relatedResource' => 196,
'renderMethod' => 198,
'termsOfUse' => 200,
'validFrom' => 202,
'validUntil' => 204,
'terseStatusListBaseUrl' => 206,
'terseStatusListIndex' => 208,
'challenge' => 210,
'created' => 212,
'cryptosuite' => 214,
'domain' => 216,
'expires' => 218,
'nonce' => 220,
'previousProof' => 222,
'proofPurpose' => 224,
'proofValue' => 226,
'verificationMethod' => 228,
'assertionMethod' => 230,
'authentication' => 232,
'capabilityDelegation' => 234,
'capabilityInvocation' => 236,
'keyAgreement' => 238
}
有关上述内容的更多信息,请参见6.3 实现说明。
这会得到以下已编码凭证:
D9CB1D821864A60183198000198001198002189D82187618A418B8A3189C18A618CE18B218D01AE592208118BAA2189C18A018A8447582002018BE18AA18C0A5189C186C18D60418E018E618E258417AB7C2E56B49E2CCE62184CE26818E15A8B173164401B5D3BB93FFD6D2B5EB8F6AC0971502AE3DD49D17EC66528164034C912685B8111BC04CDC9EC13DBADD91CC18E418AC
diagnostic:
51997([
100,
{
1: [32768, 32769, 32770],
157: [118, 164],
184: {156: 166, 206: 178, 208: 3851559041},
186: {156: 160, 168: h'75820020'},
190: 170,
192: {
156: 108,
214: 4,
224: 230,
226: h'7AB7C2E56B49E2CCE62184CE26818E15A8B173164401B5D3BB93FFD6D2B5EB8F6AC0971502AE3DD49D17EC66528164034C912685B8111BC04CDC9EC13DBADD91CC',
228: 172
}
}
])
将驾驶证 CBOR-LD 编码为 base64url,并将结果插入 PDF417 字节中 'ZZ' 子文件的 'ZZA' 字段:
bytes(@\n\x1e\rANSI 000000090002DL00410234ZZ02750202DLDAQF987654321\nDCSSMITH\nDDEN\nDACJOHN\nDDFN\nDADNONE\nDDGN\nDCAC\nDCBNONE\nDCDNONE\nDBD01012024\nDBB04191988\nDBA04192030\nDBC1\nDAU069 IN\nDAYBRO\nDAG123 MAIN ST\nDAIANYVILLE\nDAJUTO\nDAKF87P20000 \nDCFUTODOCDISCRIM\nDCGUTO\nDAW158\nDCK1234567890\nDDAN\rZZZZA2csdghhkpgGDGYAAGYABGYACGJ2CGHYYpBi4oxicGKYYzhiyGNAa5ZIggRi6ohicGKAYqER1ggAgGL4YqhjApRicGGwY1gQY4BjmGOJYQXq3wuVrSeLM5iGEziaBjhWosXMWRAG107uT/9bSteuPasCXFQKuPdSdF+xmUoFkA0yRJoW4ERvATNyewT263ZHMGOQYrA==\r)
上述内容现在可以转换为条形码:
现在我们应用反向过程进行验证。
我们首先从 PDF417 读取数据:
bytes(@\n\x1e\rANSI 000000090002DL00410234ZZ02750202DLDAQF987654321\nDCSSMITH\nDDEN\nDACJOHN\nDDFN\nDADNONE\nDDGN\nDCAC\nDCBNONE\nDCDNONE\nDBD01012024\nDBB04191988\nDBA04192030\nDBC1\nDAU069 IN\nDAYBRO\nDAG123 MAIN ST\nDAIANYVILLE\nDAJUTO\nDAKF87P20000 \nDCFUTODOCDISCRIM\nDCGUTO\nDAW158\nDCK1234567890\nDDAN\rZZZZA2csdghhkpgGDGYAAGYABGYACGJ2CGHYYpBi4oxicGKYYzhiyGNAa5ZIggRi6ohicGKAYqER1ggAgGL4YqhjApRicGGwY1gQY4BjmGOJYQXq3wuVrSeLM5iGEziaBjhWosXMWRAG107uT_9bSteuPasCXFQKuPdSdF-xmUoFkA0yRJoW4ERvATNyewT263ZHMGOQYrA==\r)
我们提取 'ZZ' 子文件中 'ZZA' 字段的数据,并撤销 base 编码:
D9CB1D821864A60183198000198001198002189D82187618A418B8A3189C18A618CE18B218D01AE592208118BAA2189C18A018A8447582002018BE18AA18C0A5189C186C18D60418E018E618E258417AB7C2E56B49E2CCE62184CE26818E15A8B173164401B5D3BB93FFD6D2B5EB8F6AC0971502AE3DD49D17EC66528164034C912685B8111BC04CDC9EC13DBADD91CC18E418AC
现在,我们使用 CBOR-LD 解压缩,以得到将要验证的
原始 JSON-LD VC。同样,这些参数与 CBOR-LD
注册表条目 100 关联。
typeTable:
{
"context":
{
"https://www.w3.org/ns/credentials/v2": 32768,
"https://w3id.org/vc-barcodes/v1": 32769,
"https://w3id.org/utopia/v2": 32770
},
"https://w3id.org/security#cryptosuiteString":
{
"ecdsa-rdfc-2019": 1,
"ecdsa-sd-2023": 2,
"eddsa-rdfc-2022": 3,
"ecdsa-xi-2023": 4
}
}
处理上下文并为上下文术语分配整数值后, 应得到的 ID 到术语映射如下。请注意,这是 压缩期间构造的映射的反向映射。
Map(97) {
0 => '@context',
2 => '@type',
4 => '@id',
6 => '@value',
8 => '@direction',
10 => '@graph',
12 => '@included',
14 => '@index',
16 => '@json',
18 => '@language',
20 => '@list',
22 => '@nest',
24 => '@reverse',
26 => '@base',
28 => '@container',
30 => '@default',
32 => '@embed',
34 => '@explicit',
36 => '@none',
38 => '@omitDefault',
40 => '@prefix',
42 => '@preserve',
44 => '@protected',
46 => '@requireAll',
48 => '@set',
50 => '@version',
52 => '@vocab',
100 => '...',
102 => 'BitstringStatusList',
104 => 'BitstringStatusListCredential',
106 => 'BitstringStatusListEntry',
108 => 'DataIntegrityProof',
110 => 'EnvelopedVerifiableCredential',
112 => 'EnvelopedVerifiablePresentation',
114 => 'JsonSchema',
116 => 'JsonSchemaCredential',
118 => 'VerifiableCredential',
120 => 'VerifiablePresentation',
122 => '_sd',
124 => '_sd_alg',
126 => 'aud',
128 => 'cnf',
130 => 'description',
132 => 'digestMultibase',
134 => 'digestSRI',
136 => 'exp',
138 => 'iat',
140 => 'id',
142 => 'iss',
144 => 'jku',
146 => 'kid',
148 => 'mediaType',
150 => 'name',
152 => 'nbf',
154 => 'sub',
156 => 'type',
158 => 'x5u',
160 => 'AamvaDriversLicenseScannableInformation',
162 => 'MachineReadableZone',
164 => 'OpticalBarcodeCredential',
166 => 'TerseBitstringStatusListEntry',
168 => 'protectedComponentIndex',
170 => 'did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj',
172 => 'did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj#zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj',
174 => 'did:key:zDnaeZSD9XcuULaS8qmgDUa6TMg2QjF9xABnZK42awDH3BEzj',
176 => 'did:key:zDnaeZSD9XcuULaS8qmgDUa6TMg2QjF9xABnZK42awDH3BEzj#zDnaeZSD9XcuULaS8qmgDUa6TMg2QjF9xABnZK42awDH3BEzj',
178 => 'https://sandbox.platform.veres.dev/statuses/z19rJ4oGrbFCqf3cNTVDHSbNd/status-lists',
180 => 'confidenceMethod',
182 => 'credentialSchema',
184 => 'credentialStatus',
186 => 'credentialSubject',
188 => 'evidence',
190 => 'issuer',
192 => 'proof',
194 => 'refreshService',
196 => 'relatedResource',
198 => 'renderMethod',
200 => 'termsOfUse',
202 => 'validFrom',
204 => 'validUntil',
206 => 'terseStatusListBaseUrl',
208 => 'terseStatusListIndex',
210 => 'challenge',
212 => 'created',
214 => 'cryptosuite',
216 => 'domain',
218 => 'expires',
220 => 'nonce',
222 => 'previousProof',
224 => 'proofPurpose',
226 => 'proofValue',
228 => 'verificationMethod',
230 => 'assertionMethod',
232 => 'authentication',
234 => 'capabilityDelegation',
236 => 'capabilityInvocation',
238 => 'keyAgreement'
}
有关上述内容的更多信息,请参见6.3 实现说明。
随后,解压缩会得到以下凭证:
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vc-barcodes/v1",
"https://w3id.org/utopia/v2"
],
"type": [
"VerifiableCredential",
"OpticalBarcodeCredential"
],
"credentialSubject": {
"type": "AamvaDriversLicenseScannableInformation",
"protectedComponentIndex": "uggAg"
},
"issuer": "did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj",
"credentialStatus": {
"type": "TerseBitstringStatusListEntry",
"terseStatusListBaseUrl": "https://sandbox.platform.veres.dev/statuses/z19rJ4oGrbFCqf3cNTVDHSbNd/status-lists",
"terseStatusListIndex": 3851559041
},
"proof": {
"type": "DataIntegrityProof",
"verificationMethod": "did:key:zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj#zDnaeWjKfs1ob9QcgasjYSPEMkwq31hmvSAWPVAgnrt1e9GKj",
"cryptosuite": "ecdsa-xi-2023",
"proofPurpose": "assertionMethod",
"proofValue": "z4g6G3dAZhhtPxPWgFvkiRv7krtCaeJxjokvL46fchAFCXEY3FeX2vn46MDgBaw779g1E1jswZJxxreZDCrtHg2qH"
}
}
我们应用 3.2.4.4
创建 opticalDataBytes
来创建 ecdsa-xi-2023 所需的 opticalDataBytes,
使用扫描得到的 PDF417 和 protectedComponentIndex 作为输入。
canonicalizedData = 'DACJOHN\nDAQ987654321\nDCSSMITH\n' opticalDataBytes: [188, 38, 200, 146, 227, 213, 90, 250, 50, 18, 126, 254, 47, 177, 91, 23, 64, 129, 104, 223, 136, 81, 116, 67, 136, 125, 137, 165, 117, 63, 152, 207]
然后,我们应用 3.2.4.3 哈希 (ecdsa-xi-2023) 和 3.2.4.2 验证 证明 (ecdsa-xi-2023) 来验证 该凭证。
最后一步是检查驾驶证
凭证上的状态信息。我们应用 3.2.3
转换状态列表条目
将 TerseBitstringStatusListEntry 转换为
BitstringStatusListEntry。
这里我们检查两种状态类型:'revocation' 和 'suspension',并将这些
字符串作为 statusPurpose 的值传入。
{
type: 'BitstringStatusListEntry',
statusListCredential: 'https://sandbox.platform.veres.dev/statuses/z19rJ4oGrbFCqf3cNTVDHSbNd/status-lists/revocation/29385',
statusListIndex: 8321,
statusPurpose: 'revocation'
}
{
type: 'BitstringStatusListEntry',
statusListCredential: 'https://sandbox.platform.veres.dev/statuses/z19rJ4oGrbFCqf3cNTVDHSbNd/status-lists/suspension/29385',
statusListIndex: 8321,
statusPurpose: 'suspension'
}
然后,可以按 Bitstring Status List v1.0:验证算法中的方式验证这些条目。
在从上下文术语构建到 CBOR-LD 整数的映射时,请注意, 一些上下文在其内部包含其他上下文,并嵌套在特定 类型的对象下。这些嵌套上下文称为“类型作用域上下文”,并且它们只有 在数据中使用关联类型时才会变为活动状态。这对于术语 ID 分配非常重要,因为上下文中的术语只有在该上下文变为 活动状态后才会被分配 ID。在这些测试向量中,这就是为什么 为驾驶证和就业许可文件创建的映射不同,即便这两个凭证使用 相同上下文。
此外,请注意,在 CBOR-LD 中,当关联值为复数时, 使用奇数来表达术语。例如,在上面的 CBOR-LD 术语到 ID 和 ID 到术语映射中,"type" 映射到 156,但在 VC 中表达多个类型的位置,则使用 157。
appContextMap: [['https://www.w3.org/ns/credentials/v2', 32768], ['https://w3id.org/vc-barcodes/v1', 32769], ['https://w3id.org/utopia/v2', 32770]]
本节为非规范性内容。
本节包含随时间推移对本 规范所作的实质性更改。
本规范的内容将在 标准轨道流程启动后填充。