數位身分證件可透過應用程式內和網站流程接受。 如要接受 Google 錢包中的憑證,您必須:
必要條件
如要測試數位身分證件的顯示方式,請先使用預計的測試帳戶 (必須是 Gmail 帳戶) 註冊公開 Beta 版計畫。接著,請將下列詳細資料提供給指定的 Google 聯絡人。
- 服務條款連結
- 標誌
- 網站
- 應用程式套件 ID (適用於 Android 應用程式整合)
- 包括開發 / 偵錯版本
- 應用程式簽章
$ $ANDROID_SDK/build-tools/$BUILD_TOOLS_VERSION/apksigner verify --print-certs -v $APK
- 用於加入公開 Beta 版的 Gmail ID
支援的憑證格式
目前有幾項提議的標準定義了數位身分證件的資料格式,其中兩項已在業界獲得廣泛採用:
Android 憑證管理工具支援這兩種格式,但 Google 錢包目前僅支援以 mdoc 為基礎的數位身分證件。
支援的憑證
Google 錢包支援 2 種憑證類型:
- 行動駕照 (mDL)
- 身分證件票證
您可以在流程中要求任一憑證,只要變更單一參數即可。
使用者體驗
本節將說明建議的線上簡報流程。流程顯示向酒類外送應用程式呈現年齡的畫面,但網頁和其他類型的呈現方式也類似。
![]() |
![]() |
![]() |
![]() |
![]() |
系統提示使用者在應用程式或網站中驗證年齡 | 使用者會看到可用的合格憑證 | 使用者在 Google 錢包中看到確認頁面 | 使用者驗證以確認共用 | 傳送至應用程式或網站的資料 |
重要附註
- 應用程式或網站可彈性建立 API 的進入點。如步驟 1 所示,我們建議顯示「使用數位身分證件驗證」等一般按鈕,因為我們預期 API 會提供 Google 錢包以外的選項。
- 步驟 2 中的選取器畫面是由 Android 算繪。系統會根據各錢包提供的註冊邏輯,以及憑證管理服務傳送的要求,判斷憑證是否符合資格
- 步驟 3 由 Google 錢包提供。Google 錢包會在這個畫面顯示開發人員提供的名稱、標誌和隱私權政策。
新增數位身分證件流程
如果使用者沒有憑證,建議在「使用數位身分證件驗證」按鈕旁提供連結,讓使用者透過深層連結前往 Google 錢包新增數位身分證件。
![]() |
![]() |
系統提示使用者在應用程式或網站中驗證年齡 | 使用者前往 Google 錢包取得數位身分證件 |
沒有可用的數位身分證件
如果使用者選取「使用數位身分證驗證」選項,但沒有數位身分證,系統會顯示這則錯誤訊息。
![]() |
![]() |
系統提示使用者在應用程式或網站中驗證年齡 | 如果使用者沒有數位身分證件,系統會顯示錯誤訊息 |
為保護使用者隱私,API 不支援靜默瞭解使用者是否有任何可用的數位 ID。因此建議您加入如圖所示的入門連結選項。
從錢包要求身分證件的請求格式
以下是 mdoc requestJson
要求範例,可從 Android 裝置或網頁上的任何錢包取得身分憑證。
{
"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 字串。憑證管理工具會將這個 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)。Google 錢包應用程式會負責製作這項回覆。
範例:
{
"protocol" : "openid4vp",
"data" : {
<encrpted_response>
}
}
您會將此回應傳回伺服器,驗證其真實性。您可以參閱驗證憑證回應的步驟。
網頁
如要在 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 後,您必須先執行多項驗證,才能信任回應。
使用私密金鑰解密回應
第一步是使用儲存的私密金鑰解密權杖,並取得回應 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>" } }
建立工作階段轉錄稿
下一個步驟是從 ISO/IEC 18013-5:2021 建立 SessionTranscript,並使用 Android 或網頁專屬的交接結構:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]
無論是 Android 或網頁交接,您都必須使用產生
credential_request
時使用的相同隨機值。Android Handover
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。這包括幾個步驟,例如:檢查州政府發行證書。查看支援的發卡機構 IACA 認證
驗證 MSO 簽章 (18013-5 第 9.1.2 節)
計算及檢查資料元素的 ValueDigest (18013-5 第 9.1.2 節)
驗證
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
,並在 Request 中新增 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": "2093f64f54c81fb2f7f96a46593951d04005784da3d479e4543e2190dcf205d6", //This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 2
},
{
"system": "longfellow-libzk-v1",
"circuit_hash": "2836f0df5b7c2c431be21411831f8b3d2b7694b025a9d56a25086276161f7a93", // This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 1
}
],
"verifier_message": "challenge"
},
"claims": [{
...