Panduan ini menjelaskan cara Pihak Pengandalkan (RP) Langsung—penjual, bisnis, dan developer aplikasi perorangan—mengintegrasikan Digital Credentials API untuk meminta dan memvalidasi kredensial identitas digital di seluruh aplikasi Android dan web.
Pilih jalur Anda RP, Registrar Pemverifikasi, atau Tatap Muka
Cara kerja integrasi Pihak Pengandel
Setiap integrasi Pihak Pengandel mengikuti empat langkah. Langkah A dan C berjalan di server Anda; Langkah B berjalan di aplikasi Android atau browser Anda. Pilih langkah mana pun untuk langsung melihat detail penerapannya:

Format, Kredensial & Kemampuan yang Didukung
Google Wallet mendukung Tanda Pengenal Digital berbasis ISO mdoc melalui OpenID4VP (Versi 1.0) di Android 9 (level API 28) dan yang lebih tinggi serta di seluruh browser yang didukung.
Kredensial & Atribut yang Didukung
Cari ID doctype_value (mDL, tanda pengenal digital, Tanda Pengenal Berfoto) dan jalur klaim atribut yang dapat Anda minta dari Google Wallet.
Penerbit & Sertifikat IACA yang Didukung
Download root certificate IACA resmi untuk negara bagian AS, penerbit internasional, dan penerbit Sandbox Utopia untuk memvalidasi issuerAuth.
Langkah A: Memformat & Menandatangani Permintaan (Sisi Server)
Untuk meminta kredensial dari dompet mana pun, Anda harus memformat permintaan menggunakan
OpenID4VP di server Anda. Anda dapat meminta kredensial tertentu atau beberapa
kredensial dalam satu objek dcql_query.
Contoh Permintaan JSON
Berikut adalah contoh permintaan requestJson mdoc untuk mendapatkan kredensial identitas
dari dompet mana pun di perangkat Android atau web.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
Enkripsi Permintaan
client_metadata berisi kunci publik enkripsi untuk setiap permintaan.
Anda harus menyimpan kunci pribadi untuk setiap permintaan dan menggunakannya untuk mengautentikasi
dan mengizinkan token yang Anda terima dari aplikasi dompet.
Metadata OpenID4VP Terintegrasi
Saat memformat permintaan kredensial, Anda harus menyertakan kolom gw_rp_metadata_bytes di dalam objek client_metadata (seperti yang ditunjukkan dalam contoh kode permintaan di bawah). Kolom ini berisi metadata pihak tepercaya berenkode Base64URL yang diperlukan oleh Google Wallet untuk memverifikasi identitas Anda dan menampilkan branding Anda kepada pengguna.
Payload Permintaan Kredensial
Parameter credential_request di requestJson berisi kolom berikut.
Kredensial Tertentu
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
"nonce": "1234",
"dcql_query": {
"credentials": [
{
"id": "cred1",
"format": "mso_mdoc",
"meta": {
// Use org.iso.18013.5.1.mDL for mDL,
// com.google.wallet.idcard.1 for ID pass, or
// org.iso.23220.photoid.1 for ID pass (using photo ID document type)
"doctype_value": "org.iso.18013.5.1.mDL"
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
}
]
},
"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", // This is required
"alg" : "ECDH-ES", // This is required
}
]
},
"vp_formats_supported": {
"mso_mdoc": {
"deviceauth_alg_values": [
-7
],
"issuerauth_alg_values": [
-7
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
Kredensial yang Memenuhi Syarat
Berikut adalah contoh permintaan untuk mDL, tanda pengenal digital, dan tanda pengenal digital (menggunakan jenis dokumen tanda pengenal berfoto). Pengguna dapat melanjutkan dengan salah satunya.
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
"nonce": "1234",
"dcql_query": {
"credentials": [
{
"id": "mdl-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.18013.5.1.mDL"
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
},
{ // Credential type 2: ID pass
"id": "id_pass-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "com.google.wallet.idcard.1"
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
},
{ // Credential type 3: ID pass (using photo ID document type)
"id": "photo_id-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.23220.photoid.1"
},
"claims": [
{
"path": [
"org.iso.23220.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.23220.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.23220.1",
"age_over_18"
],
"intent_to_retain": false
}
]
}
]
credential_sets : [
{
"options": [
[ "mdl-request" ],
[ "id_pass-request" ],
[ "photo_id-request" ]
]
}
]
},
"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", // This is required
"alg" : "ECDH-ES", // This is required
}
]
},
"vp_formats_supported": {
"mso_mdoc": {
"deviceauth_alg_values": [
-7
],
"issuerauth_alg_values": [
-7
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
Anda dapat meminta sejumlah atribut yang didukung dari kredensial identitas apa pun yang disimpan di Google Wallet.
Permintaan Bertanda Tangan
Permintaan Bertanda Tangan (JWT Secured Authorization Requests) mengenkapsulasi permintaan presentasi yang dapat diverifikasi di dalam Token Web JSON (JWT) yang ditandatangani secara kriptografis menggunakan infrastruktur PKI Anda, sehingga memastikan integritas permintaan dan membuktikan identitas Anda kepada Google Wallet.
Prasyarat
Sebelum menerapkan perubahan kode untuk permintaan bertanda tangan, pastikan Anda memiliki:
- Kunci Pribadi: Anda memerlukan kunci pribadi (misalnya, Elliptic Curve
ES256) untuk menandatangani permintaan, yang dikelola di server Anda. Untuk pengujian sandbox sebagai Pihak Penyalur, gunakan Kunci Pribadi Pengujian Sandbox. - Sertifikat: Anda memerlukan sertifikat X.509 standar yang berasal dari pasangan kunci Anda.
- Pendaftaran: Pastikan sertifikat publik Anda (atau Root CA untuk Registrar Pemverifikasi) dipercaya oleh Google Wallet.
Logika Konstruksi Permintaan
Saat membuat dan menandatangani permintaan OpenID4VP, perhatikan baik-baik
format encoding yang diharapkan untuk setiap kolom—mencampuradukkan base64 dan base64url
atau padding (=) akan menyebabkan validasi tanda tangan atau asal gagal:
| Kolom | Encoding / Format yang Diperlukan | Karakter & Padding |
|---|---|---|
client_id (x509_hash:<hash>) |
base64url tanpa padding | Menggunakan alfabet yang aman untuk URL (-, _); menghapus = di akhir |
expected_origins (android:apk-key-hash:<hash>) |
Base64 standar tanpa padding | Menggunakan alfabet standar (+, /); menghapus = di akhir |
expected_origins (https://...) |
URL huruf kecil tanpa garis miring di akhir | Contoh: https://example.com (tanpa / di akhir) |
Header JWS x5c |
Base64 standar dengan padding | Menggunakan alfabet standar (+, /); mempertahankan padding = di akhir |
Untuk membuat permintaan, gunakan kunci pribadi Anda dan gabungkan payload dalam JWS:
def construct_openid4vp_request(
doctypes: list[str],
requested_fields: list[dict],
nonce_base64: str,
jwe_encryption_public_jwk: jwk.JWK,
is_zkp_request: bool,
is_signed_request: bool,
state: dict,
origin: str
) -> dict:
# ... [Existing logic to build 'presentation_definition' and basic 'request_payload'] ...
# ------------------------------------------------------------------
# SIGNED REQUEST IMPLEMENTATION (JAR)
# ------------------------------------------------------------------
if is_signed_request:
try:
# 1. Load the Verifier's Certificate
# We must load the PEM string into a cryptography x509 object
verifier_cert_obj = x509.load_pem_x509_certificate(
CERTIFICATE.encode('utf-8'),
backend=default_backend()
)
# 2. Calculate Client ID (x509_hash)
# We calculate the SHA-256 hash of the DER-encoded certificate.
cert_der = verifier_cert_obj.public_bytes(serialization.Encoding.DER)
verifier_fingerprint_bytes = hashlib.sha256(cert_der).digest()
# Important: Use URL-safe Base64 (base64url: '-', '_') without padding ('=') for client_id
verifier_fingerprint_b64 = base64.urlsafe_b64encode(verifier_fingerprint_bytes).decode('utf-8').rstrip("=")
# Format the client_id as required by the spec
client_id = f'x509_hash:{verifier_fingerprint_b64}'
# 3. Update Request Payload with JAR specific fields
request_payload["client_id"] = client_id
# Explicitly set expected origins to prevent relay attacks
# Important: Use standard Base64 (not URL-safe) without padding for android:apk-key-hash:
# origin = f"android:apk-key-hash:{base64.b64encode(apk_cert_hash).decode('utf-8').rstrip('=')}"
# Format for web origin (lowercase URL without trailing slash): origin = "https://example.com"
if origin:
request_payload["expected_origins"] = [origin]
# 4. Create Signed JWT (JWS)
# Load the signing private key
signing_key = jwk.JWK.from_pem(PRIVATE_KEY.encode('utf-8'))
# Initialize JWS with the JSON payload
jws_token = jws.JWS(json.dumps(request_payload).encode('utf-8'))
# Construct the JOSE Header
# 'x5c' (X.509 Certificate Chain) is critical: it allows the wallet
# to validate your key against the one registered in the console.
# Important: Use standard Base64 ('+', '/') WITH padding ('=') for x5c
x5c_value = base64.b64encode(cert_der).decode('utf-8')
protected_header = {
"alg": "ES256", # Algorithm (e.g., ES256 or RS256)
"typ": "oauth-authz-req+jwt", # Standard type for JAR
"kid": "1", # Key ID
"x5c": [x5c_value] # Embed the certificate
}
# Sign the token
jws_token.add_signature(
key=signing_key,
alg=None,
protected=json_encode(protected_header)
)
# 5. Return the Request Object
# Instead of returning the raw JSON, we return the signed JWT string
# under the 'request' key.
return {"request": jws_token.serialize(compact=True)}
except Exception as e:
print(f"Error signing OpenID4VP request: {e}")
return None
# ... [Fallback for unsigned requests] ...
return request_payload
Langkah B: Picu API (Sisi Klien)
Seluruh permintaan API harus dibuat di sisi server. Bergantung pada platformnya, teruskan JSON yang dihasilkan ke API platform.
Dalam Aplikasi (Android)
Untuk meminta kredensial identitas dari aplikasi Android Anda, ikuti langkah-langkah berikut:
Memperbarui dependensi
Dalam build.gradle project, update dependensi untuk menggunakan Credential
Manager (beta):
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Mengonfigurasi Pengelola Kredensial
Untuk mengonfigurasi dan melakukan inisialisasi objek CredentialManager, tambahkan logika yang mirip dengan berikut ini:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
Atribut Identitas Permintaan
Alih-alih menentukan parameter individual untuk permintaan identitas, aplikasi
menyediakan semuanya bersama-sama sebagai string JSON dalam CredentialOption.
Credential Manager meneruskan string JSON ini ke dompet digital yang tersedia tanpa memeriksa kontennya. Setiap dompet kemudian bertanggung jawab untuk:
- Mengurai string JSON untuk memahami permintaan identitas.
- Menentukan kredensial tersimpannya, jika ada, yang memenuhi permintaan.
Sebaiknya partner membuat permintaan di server, bahkan untuk integrasi aplikasi Android.
Anda akan menggunakan requestJson dari Format & Tanda Tangani Permintaan
sebagai request dalam panggilan fungsi GetDigitalCredentialOption().
// 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)
}
}
Menangani respons kredensial
Setelah Anda mendapatkan respons kembali dari dompet, verifikasi apakah respons tersebut
berhasil dan berisi respons 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}")
}
}
Respons credentialJson berisi identityToken (JWT) terenkripsi,
yang ditentukan oleh W3C. Aplikasi Wallet bertanggung jawab untuk membuat respons ini.
Contoh:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrypted_response>
}
}
Teruskan respons ini kembali ke server Anda untuk memvalidasi keasliannya. Lihat Mendekripsi & Memvalidasi Respons.
Web
Untuk meminta Kredensial Identitas menggunakan Digital Credentials API di Chrome atau browser lain yang didukung, buat permintaan berikut:
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
Kirim respons dari API ini kembali ke server Anda untuk memvalidasi respons kredensial.
Langkah C: Dekripsi & Validasi Respons (Sisi Server)
Setelah dompet menampilkan identityToken (JWT) terenkripsi, Anda harus melakukan
validasi sisi server yang ketat sebelum memercayai data.
Mendekripsi Respons
Gunakan kunci pribadi yang sesuai dengan kunci publik yang dikirim dalam client_metadata permintaan untuk mendekripsi JWE. Tindakan ini akan menghasilkan vp_token.
Contoh Python:
from jwcrypto import jwe, jwk
# Retrieve the Private Key from Datastore
reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
# Save public key thumbprint for session transcript
encryption_public_jwk_thumbprint = reader_private_jwk.thumbprint()
# 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 akan menghasilkan JSON vp_token yang berisi kredensial:
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
Merekonstruksi SessionTranscript & Memvalidasi DeviceResponse
- Buat SessionTranscript dari ISO/IEC 18013-5:2021 dengan struktur Handover khusus Android atau Web:
SessionTranscript = [
null, // DeviceEngagementBytes not available
null, // EReaderKeyBytes not available
[
"OpenID4VPDCAPIHandover",
AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web
]
]
Untuk penyerahan Android dan web, gunakan nonce yang sama dengan yang Anda gunakan untuk
membuat credential_request.
Android Handover
AndroidHandoverData = [ origin, // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>", nonce, // nonce that was used to generate credential request, encryption_public_jwk_thumbprint, // Encryption public key (JWK) Thumbprint ] AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
Penyerahan Browser
BrowserHandoverData =[ origin, // Origin URL nonce, // nonce that was used to generate credential request encryption_public_jwk_thumbprint, // Encryption public key (JWK) Thumbprint ] BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
Dengan menggunakan SessionTranscript, Respons Perangkat harus divalidasi sesuai
dengan ISO/IEC 18013-5:2021 klausul 9.
Validasi ini mencakup empat pemeriksaan:
- Periksa Sertifikat Penerbit: Ekstrak rantai sertifikat penandatanganan penerbit dari
issuerAuthdan validasi berdasarkan root certificate IACA tepercaya. Lihat Penerbit & Sertifikat IACA yang Didukung (dan tambahkan sertifikat Sandbox Utopia saat menguji dengan ID pengujian sandbox). - Memverifikasi tanda tangan MSO (ISO/IEC 18013-5 Pasal 9.1.2).
- Menghitung dan memeriksa
ValueDigestsuntuk Elemen Data (ISO/IEC 18013-5 Pasal 9.1.2). - Memverifikasi tanda tangan
deviceSignature(ISO/IEC 18013-5 Pasal 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
}
Langkah D (Opsional): Verifikasi Usia yang Menjaga Privasi (ZKP)
Untuk mendukung Bukti Zero-Knowledge (misalnya, memverifikasi bahwa pengguna berusia di atas 18 tahun
tanpa melihat tanggal lahir persisnya), ubah format permintaan Anda menjadi
mso_mdoc_zk dan berikan konfigurasi zk_system_type yang diperlukan.
Untuk ringkasan umum tentang apa itu ZKP dan kemampuannya, lihat FAQ.
...
"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": "f88a39e561ec0be02bb3dfe38fb609ad154e98decbbe632887d850fc612fea6f", // This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 5,
"block_enc_hash": 4096,
"block_enc_sig": 2945,
}
{
"system": "longfellow-libzk-v1",
"circuit_hash": "137e5a75ce72735a37c8a72da1a8a0a5df8d13365c2ae3d2c2bd6a0e7197c7c6", // This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 6,
"block_enc_hash": 4096,
"block_enc_sig": 2945,
}
],
"verifier_message": "challenge"
},
"claims": [{
...
"client_metadata": {
"jwks": {
"keys": [ // sample request encryption key
{
...
Anda akan mendapatkan bukti zero-knowledge terenkripsi dari dompet. Anda dapat memvalidasi bukti ini terhadap sertifikat IACA penerbit menggunakan library longfellow-zk Google.
verifier-service berisi server berbasis Docker yang siap di-deploy dan memungkinkan Anda memvalidasi respons terhadap sertifikat IACA penerbit.
Anda dapat mengubah certs.pem untuk mengelola sertifikat penerbit IACA yang ingin Anda percayai.
SDK, Implementasi Referensi & Dukungan
Gunakan library open source, server referensi, dan alat verifikasi web kami untuk mempercepat implementasi Anda:
| Resource / Library | Yang disediakan alat ini | Link |
|---|---|---|
| Implementasi Referensi Pemverifikasi Identitas | Server dan klien contoh yang menunjukkan pembuatan permintaan OpenID4VP, penandatanganan JWS, dekripsi JWE, dan validasi respons mdoc. | Repositori GitHub |
| Multipaz SDK | Library open source (OpenWallet Foundation) termasuk DeviceResponseParser untuk decoding CBOR, rekonstruksi SessionTranscript, dan validasi tanda tangan ISO/IEC 18013-5/7. |
openwallet-foundation/multipaz |
| Library ZKP & Layanan Verifier Longfellow | Library open source dan server berbasis Docker untuk memverifikasi pernyataan atribut dan usia Zero-Knowledge Proof (mso_mdoc_zk). |
google/longfellow-zk |
| Alat Verifikasi Web Interaktif | Pemverifikasi web yang dihosting untuk menguji permintaan W3C Digital Credentials API / OpenID4VP terhadap Google Wallet. | verifier.multipaz.org |
| Spesifikasi OpenID4VP 1.0 | Spesifikasi protokol OpenID resmi untuk Verifiable Presentations 1.0. | Spesifikasi OpenID4VP |
| FAQ & Dukungan Developer | Jawaban atas pertanyaan umum terkait integrasi teknis, atau kirim email ke wallet-identity-rp-support@google.com untuk mendapatkan bantuan proses debug. |
FAQ Integrasi Teknis |