數位 ID 可用於應用程式內和網站流程。如要接受 Google 錢包的憑證,您必須:
- 按照提供的指示,使用應用程式或網頁進行整合,
- 請填寫這份表單,申請並同意接受 Google 錢包憑證的服務條款。
必要條件
如要測試數位身分證件,您必須先使用指定的測試帳戶 (必須是 Gmail 帳戶) 註冊公開 Beta 版計畫。接著,請將後續詳細資料提供給指定的 Google 聯絡窗口。
- 服務條款連結
- 標誌
- 網站
- 應用程式套件 ID (適用於 Android 應用程式整合)
- 包含開發 / 偵錯版本
- 應用程式簽名
$ $ANDROID_SDK/build-tools/$BUILD_TOOLS_VERSION/apksigner verify --print-certs -v $APK
- 用於加入公開測試版的 Gmail ID
支援的憑證格式
目前有幾項標準定義了數位身分證件資料格式,其中兩項在業界獲得廣泛關注:
雖然 Android 憑證管理工具支援這兩種格式,但 Google 錢包目前僅支援以 mdoc 為基礎的數位身分證件。
支援的憑證
Google 錢包支援 2 種憑證類型:
- 行動駕照 (mDL)
- 身分證件票證
您可以透過單一參數變更,在流程中要求任一憑證。
使用者體驗
本節將說明建議的線上簡報流程。這項流程會顯示向酒類外送應用程式呈現年齡的畫面,但網頁和其他類型的呈現方式,使用者體驗也類似。
![]() |
![]() |
![]() |
![]() |
![]() |
系統提示使用者在應用程式或網站中驗證年齡 | 使用者看到可用的符合資格憑證 | 使用者在 Google 錢包中看到確認頁面 | 使用者驗證以確認共用 | 傳送至應用程式或網站的資料 |
重點
- 應用程式或網站可彈性地建立 API 進入點。如步驟 1 所示,我們建議您顯示「使用數位身分證件驗證」等通用按鈕,因為我們預期未來會透過 API 提供 Google 錢包以外的選項。
- 步驟 2 中的選取器畫面是由 Android 算繪。系統會根據每個 Wallet 提供的註冊邏輯與信賴方傳送的要求進行比對,判斷是否符合資格的憑證
- 步驟 3 是由 Google 錢包算繪。Google 錢包會在這個畫面上顯示開發人員提供的名稱、標誌和隱私權政策。
新增數位身分證件流程
如果使用者沒有憑證,建議您在「使用數位身分證件驗證」按鈕旁邊提供連結,讓使用者可透過 Google 錢包深層連結新增數位身分證件。
![]() |
![]() |
系統提示使用者在應用程式或網站中驗證年齡 | 使用者前往 Google 錢包取得數位身分證件 |
沒有可用的數位身分證件
如果使用者選取「使用數位身分證件驗證」選項,但沒有數位身分證件,系統會顯示這則錯誤訊息。
![]() |
![]() |
系統提示使用者在應用程式或網站中驗證年齡 | 使用者沒有數位身分證件時,系統會顯示錯誤訊息 |
為了保護使用者隱私,這個 API 不支援可讓您靜默瞭解使用者是否有任何可用的數位 ID 的功能。因此,建議您加入如上所示的 onboarding 連結選項。
從錢包要求身分證件的要求格式
以下是 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)。錢包應用程式會負責建立此回應。
範例:
"response" : {
<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>" } }
建立工作階段轉錄稿
下一個步驟是使用 Android 或網頁專屬的移交結構,根據 ISO/IEC 18013-5:2021 建立 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。這包括幾個步驟,例如:檢查發證機構狀態。查看支援的發卡機構的 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
,並將 zk_system_type
新增至「Request」
...
"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": [{
...