在线接受数字凭据

应用内流程和网站流程均接受数字身份证件。如需接受 Google 钱包中的凭据,您需要满足以下条件:

  1. 按照提供的说明通过应用或网站进行集成,并
  2. 填写此表单,申请接受 Google 钱包中的凭据,并同意相关服务条款。

前提条件

如需测试以数字方式出示身份证件,您必须先使用预期的测试账号(必须是 Gmail 账号)注册公开 Beta 版计划。然后,向您的专属 Google 联系人提供后续详细信息。

  • 服务条款链接
  • 徽标
  • 网站
  • 应用软件包 ID(适用于 Android 应用集成)
    • 包含开发者 / 调试 build
  • 应用签名
    • $ $ANDROID_SDK/build-tools/$BUILD_TOOLS_VERSION/apksigner verify --print-certs -v $APK
  • 用于加入公开 Beta 版的 Gmail ID

支持的凭据格式

目前有几个拟议标准定义了数字身份证件的数据格式,其中两个标准在业界获得了广泛的关注:

  1. mdocs - 由 ISO 定义。
  2. w3c 可验证凭据 - 由 w3c 定义。

虽然 Android Credential Manager 支持这两种格式,但 Google 钱包目前仅支持基于 mdoc 的数字身份证件。

支持的凭据

Google 钱包支持 2 种凭据类型:

  1. 数字驾照 (mDL)
  2. 身份凭证

只需更改一个参数,即可在流程中请求任一凭据。

用户体验

本部分介绍了建议的在线演示流程。 此流程展示了向酒精饮料配送应用展示年龄的方式,但 Web 和其他类型的演示的用户体验类似。

在应用或网站中提示用户验证年龄 用户看到可用的符合条件的凭据 用户在 Google 钱包中看到确认页面 用户进行身份验证以确认共享 发送到应用或网站的数据
在应用或网站中提示用户验证年龄 用户看到可用的符合条件的凭据 用户在 Google 钱包中看到确认页面 用户进行身份验证以确认共享 发送到应用或网站的数据

要点

  1. 应用或网站可以灵活地创建 API 的入口点。如第 1 步所示,我们建议显示通用按钮,例如“使用数字身份证件进行验证”,因为我们预计随着时间的推移,API 将支持 Google 钱包以外的选项。
  2. 第 2 步中的选择器界面由 Android 呈现。符合条件的凭据取决于每个钱包提供的注册逻辑与依赖方发送的请求之间的匹配情况
  3. 第 3 步由 Google 钱包呈现。Google 钱包会在此屏幕上显示开发者提供的名称、徽标和隐私权政策。

添加数字身份证件流程

如果用户没有凭据,我们建议在“使用数字身份证件进行验证”按钮旁边提供一个链接,该链接会深层链接到 Google 钱包,以便用户添加数字身份证件。

在应用或网站中提示用户验证年龄 系统将用户定向至 Google 钱包以获取数字身份证件
在应用或网站中提示用户验证年龄 系统将用户定向至 Google 钱包以获取数字身份证件

无可用的数字身份证件

如果用户选择“使用数字身份证件进行验证”选项,但没有数字身份证件,系统会显示此错误消息。

在应用或网站中提示用户验证年龄 系统向用户显示错误消息,提示用户没有数字身份证件
在应用或网站中提示用户验证年龄 系统向用户显示错误消息,提示用户没有数字身份证件

为保护用户隐私,此 API 不支持用于静默了解用户是否有任何可用的数字身份证件。因此,我们建议您添加新手入门链接选项,如下所示。

从钱包请求身份证件凭据的请求格式

以下是用于从 Android 设备或网站上的任何钱包获取身份凭据的 mdoc requestJson 请求示例。

{
      "requests" : [
        {
          "protocol": "openid4vp",
          "data": {<credential_request>} // This is an object, shouldn't be a string.
        }
      ]
}

请求加密

client_metadata 包含每个请求的加密公钥。您必须存储每个请求的私钥,并使用该私钥对您从钱包应用收到的令牌进行身份验证和授权。

requestJson 中的 credential_request 参数将包含以下字段。

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt",
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "cred1",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"  // this is for mDL. Use com.google.wallet.idcard.1 for ID pass
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ]
          }
        ]
      }
    ]
  },
  "client_metadata": {
    "jwks": {
      "keys": [ // sample request encryption key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
          "use": "enc",
          "kid" : "1",
          "alg" : "ECDH-ES",
        }
      ]
    },
    "authorization_encrypted_response_alg": "ECDH-ES",
    "authorization_encrypted_response_enc": "A128GCM"
  }
}

您可以请求获取存储在 Google 钱包中的任何身份凭据的任意数量的受支持属性

应用内

如需从 Android 应用请求身份凭据,请按以下步骤操作:

更新依赖项

在项目的 build.gradle 中,更新您的依赖项以使用 Credential Manager(Beta 版):

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-beta01")
    // optional - needed for credentials support from play services, for devices running Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}

配置 Credential Manager

如需配置和初始化 CredentialManager 对象,请添加类似于以下内容的逻辑:

// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)

请求身份属性

应用不会为身份请求指定单独的参数,而是会在 CredentialOption 中将所有参数作为 JSON 字符串一起提供。Credential Manager 会将此 JSON 字符串传递给可用的数字钱包,而不会检查其内容。然后,每个钱包负责: - 解析 JSON 字符串以了解身份请求。 - 确定其存储的凭据(如果有)中哪些凭据满足请求。

我们建议合作伙伴在服务器上创建请求,即使是 Android 应用集成也是如此。

您将使用请求格式中的 requestJson,其中包含 GetDigitalCredentialOption() 函数调用中的 request

// The request in the JSON format to conform with
// the JSON-ified Digital Credentials API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
    GetDigitalCredentialOption(requestJson = requestJson)

// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
    listOf(digitalCredentialOption)
)

coroutineScope.launch {
    try {
        val result = credentialManager.getCredential(
            context = activityContext,
            request = getCredRequest
        )
        verifyResult(result)
    } catch (e : GetCredentialException) {
        handleFailure(e)
    }
}

验证和确认响应

从钱包收到响应后,您需要验证响应是否成功,以及是否包含 credentialJson 响应。

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is DigitalCredential -> {
            val responseJson = credential.credentialJson
            validateResponseOnServer(responseJson) // make a server call to validate the response
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential ${credential.type}")
        }
    }
}

// Handle failure.
fun handleFailure(e: GetCredentialException) {
  when (e) {
        is GetCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to share the credential.
        }
        is GetCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is NoCredentialException -> {
            // No credential was available.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}

credentialJson 响应包含由 W3C 定义的加密 identityToken (JWT)。钱包应用负责构建此响应。

示例:

"response" : {
  <encrpted_response>
}

您将此响应传回给服务器,以验证其真实性。您可以参阅验证凭据响应的步骤

Web

如需在 Chrome 上使用 Digital Credentials API 请求身份凭据,您需要注册 Digital Credentials API 源试用

const credentialResponse = await navigator.credentials.get({
          digital : {
          requests : [
            {
              protocol: "openid4vp",
              data: {<credential_request>} // This is an object, shouldn't be a string.
            }
          ]
        }
      })

将此 API 的响应发送回您的服务器以验证凭据响应

验证凭据响应的步骤

从应用或网站收到经过加密的 identityToken 后,您需要执行多项验证,然后才能信任响应。

  1. 使用私钥解密响应

    第 1 步是使用已保存的私钥解密令牌,并获取响应 JSON。

    Python 示例:

    from jwcrypto import jwe, jwk
    
    # Retrieve the Private Key from Datastore
    reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
    
    # Decrypt the JWE encrypted response from Google Wallet
    jwe_object = jwe.JWE()
    jwe_object.deserialize(encrypted_jwe_response_from_wallet)
    jwe_object.decrypt(reader_private_jwk)
    decrypted_payload_bytes = jwe_object.payload
    decrypted_data = json.loads(decrypted_payload_bytes)
    

    decrypted_data 将生成包含凭据的 vp_token JSON

    {
      "vp_token":
      {
        "cred1": "<credential_token>"
      }
    }
    
  2. 创建会话转写

    下一步是根据 ISO/IEC 18013-5:2021 使用 Android 或 Web 专用接管结构创建 SessionTranscript

    SessionTranscript = [
      null,                // DeviceEngagementBytes not available
      null,                // EReaderKeyBytes not available
      [
        "OpenID4VPDCAPIHandover",
        AndroidHandoverDataBytes   // BrowserHandoverDataBytes for Web
      ]
    ]
    

    对于 Android / 网站两种类型的接力,您都需要使用生成 credential_request 时所用的 Nonce。

    Android 接管

        AndroidHandoverData = [
          origin,             // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>",
          clientId,           // "android-origin:<app_package_name>",
          nonce,              // nonce that was used to generate credential request
        ]
    
        AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
        

    浏览器切换

        BrowserHandoverData =[
          origin,               // Origin URL
          clientId,             // "web-origin:<origin>"
          nonce,               //  nonce that was used to generate credential request
        ]
    
        BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
        

    使用 SessionTranscript 时,必须根据 ISO/IEC 18013-5:2021 第 9 条对 DeviceResponse 进行验证。这包括以下几个步骤:

  3. 检查州发卡机构证书。查看受支持的发卡机构的 IACA 证书

  4. 验证 MSO 签名 (18013-5 第 9.1.2 节)

  5. 计算和检查数据元素的 ValueDigest(18013-5 第 9.1.2 节)

  6. 验证 deviceSignature 签名 (18013-5 第 9.1.3 节)

{
  "version": "1.0",
  "documents": [
    {
      "docType": "org.iso.18013.5.1.mDL",
      "issuerSigned": {
        "nameSpaces": {...}, // contains data elements
        "issuerAuth": [...]  // COSE_Sign1 w/ issuer PK, mso + sig
      },
      "deviceSigned": {
        "nameSpaces": 24(<< {} >>), // empty
        "deviceAuth": {
          "deviceSignature": [...] // COSE_Sign1 w/ device signature
        }
      }
    }
  ],
  "status": 0
}

测试您的解决方案

如需测试您的解决方案,请构建并运行我们的开源引用持有器 Android 应用。 以下是构建和运行参考持有者应用的步骤:

  • 克隆参考应用代码库
  • Android Studio 中打开项目
  • 在 Android 设备或模拟器上构建并运行 appholder 目标。

基于零知识证明 (ZKP) 的验证

零知识证明 (ZKP) 是一种加密方法,可让个人(证明者)在不泄露实际底层数据本身的情况下,向验证者证明自己拥有某项身份信息或符合特定条件(例如年满 18 周岁、持有有效凭据)。本质上,它是一种在保护敏感详细信息隐私的同时确认身份声明真实性的方法。

依赖于直接共享身份数据的数字身份系统通常要求用户共享过多个人信息,从而增加数据泄露和身份盗用风险。ZKP 带来了范式转变,可实现最小披露量的验证。

数字身份中零知识证明的关键概念:

  • 证明方:尝试证明其身份某个方面信息的个人。
  • 验证方:请求身份属性证明的实体。
  • 证明:一种加密协议,可让证明者在不泄露秘密信息的情况下,让验证者相信其声明的真实性。

零知识证明的核心特性:

  • 完整性:如果陈述为真且证明者和验证者都诚实,验证者将会确信。
  • 健全性:如果陈述为假,不诚实的证明者无法(几乎肯定)说服诚实的验证者该陈述为真。
  • 零知识:除了知道该声明为真之外,验证者不会了解任何其他信息。不会泄露任何来自证明者身份的实际数据。

如需从 Google 钱包获取零知识证明,您需要将请求格式更改为 mso_mdoc_zk,并将 zk_system_type 添加到请求

  ...
  "dcql_query": {
    "credentials": [{
      "id": "cred1",
      "format": "mso_mdoc_zk",
      "meta": {
        "doctype_value": "org.iso.18013.5.1.mDL"
        "zk_system_type": [
         {
          "system": "longfellow-libzk-v1",
           "circuit_hash": "2b8e0c49b08eb1801b9bd7a82aa9eb3736a7519fc2b409asdhj1237034", // This will differ if you need more than 1 attribute.
           "num_attributes": 1,
           "version": 1
         }
       ],
       "verifier_message": "challenge"
      },
     "claims": [{
         ...