This guide explains how Relying Parties (RPs) and reader developers can implement in-person (offline) verification of digital credentials presented from Google Wallet according to the international ISO/IEC 18013-5 standard.
Digital credentials in Google Wallet can be securely verified in physical environments (such as point-of-sale terminals, event venues, transit gates, law enforcement readers, and mobile reader apps) without requiring an active internet connection at the time of presentation.
Overview of Offline Presentation (ISO/IEC 18013-5)
The ISO/IEC 18013-5 standard defines a standardized, interoperable protocol for offline presentation between a Holder (the user's mobile device running Google Wallet) and a Reader / Verifier (a physical terminal or companion mobile app).
The presentation flow occurs in distinct phases:
- Device Engagement: The reader and wallet establish initial contact via NFC Tap (Static or Negotiated Handover) or QR Code scan. During this phase, device engagement metadata and the reader's ephemeral public key (
EReaderKey) are exchanged. - Data Transport Connection: A secure, encrypted Bluetooth Low Energy (BLE) channel is negotiated (with the reader acting in Central Client or Peripheral Server mode).
- Device Request: The reader transmits a CBOR-encoded
DeviceRequestspecifying the requested document type (such asorg.iso.18013.5.1.mDL) and the specific namespaces and data elements requested. - User Consent & Device Authentication: Google Wallet prompts the user to review the requested data elements and confirm sharing using biometric authentication or screen lock.
- Device Response & Cryptographic Verification: The wallet sends back a CBOR-encoded
DeviceResponsecontaining the signed Mobile Security Object (MSO) and device-signed data elements. The reader verifies the cryptographic signatures against trusted root certificates.
The Multipaz Open-Source SDK
To implement a reader or verifier application, Google recommends using Multipaz, an open-source Kotlin Multiplatform (KMP) SDK originally developed by Google and contributed to the OpenWallet Foundation (OWF).
Multipaz provides a production-ready implementation of ISO/IEC 18013-5 reader and wallet protocols, cryptographic verification pipelines, CBOR encoding/decoding, and extensible document type schemas.
Integrating Multipaz into Your Reader App
The following steps demonstrate how to integrate the Multipaz SDK into an Android reader application.
Step 1: Add Dependencies
Multipaz libraries are published on Maven Central. Add the required modules to your application's build.gradle.kts file:
// build.gradle.kts
dependencies {
// Core Multipaz library (protocol engine, CBOR, crypto)
implementation("org.multipaz:multipaz:0.100.0")
// Android-specific platform bindings (NFC, BLE, Keystore)
implementation("org.multipaz:multipaz-android:0.100.0")
// Standardized document types (mDL, EU PID, etc.)
implementation("org.multipaz:multipaz-doctypes:0.100.0")
}
Step 2: Configure Android Permissions
In-person verification requires hardware permissions for NFC engagement, camera scanning (for QR engagement), and Bluetooth Low Energy data transport. Add the following permissions to your AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- NFC Engagement -->
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<!-- Camera for QR Code Engagement -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- Bluetooth Low Energy Transport (Android 12+) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- Legacy Bluetooth Permissions for Android 11 and lower -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
</manifest>
Step 3: Initialize the Verification Engine
Use Multipaz's VerificationHelper or Reader connection abstractions to handle engagement events and manage the BLE communication lifecycle:
import org.multipaz.verification.VerificationHelper
import org.multipaz.cbor.Cbor
import org.multipaz.crypto.Crypto
class ReaderManager(private val context: android.content.Context) {
private var verificationHelper: VerificationHelper? = null
fun startListeningForEngagement() {
verificationHelper = VerificationHelper.Builder(
context = context,
listener = object : VerificationHelper.Listener {
override fun onDeviceConnected() {
// BLE channel established, send request
sendDeviceRequest()
}
override fun onResponseReceived(deviceResponseBytes: ByteArray) {
// Process and verify the received credential payload
handleDeviceResponse(deviceResponseBytes)
}
override fun onError(error: Throwable) {
// Handle transport or protocol errors
}
override fun onDeviceDisconnected(transportTransportSpecificTermination: Boolean) {
// Connection closed
}
}
).build()
}
}
Step 4: Construct the DeviceRequest
Specify the document type and individual data elements your application needs to verify. Always apply the principle of minimal disclosure (e.g., requesting only age_over_21 rather than full birth_date when verifying age):
fun sendDeviceRequest() {
// Specify the DocType and requested elements
val docType = "org.iso.18013.5.1.mDL"
val namespace = "org.iso.18013.5.1"
// Map of requested elements: elementName -> intentToRetain
val requestedElements = mapOf(
"family_name" to false,
"given_name" to false,
"age_over_21" to false,
"portrait" to false,
"driving_privileges" to false
)
// Build ISO/IEC 18013-5 DeviceRequest structure
val deviceRequestBytes = verificationHelper?.buildDeviceRequest(
docType = docType,
itemsToRequest = mapOf(namespace to requestedElements)
)
if (deviceRequestBytes != null) {
verificationHelper?.sendDeviceRequest(deviceRequestBytes)
}
}
Supporting Additional Document Types (DocTypes)
Multipaz supports standardized credentials out-of-the-box and provides an extensible architecture to request custom or domain-specific document types.
1. Built-in Standard Document Types
The multipaz-doctypes library provides predefined schema models for standard credentials:
| Document Type Identifier | Standard / Scope | Typical Namespace | Common Elements |
|---|---|---|---|
org.iso.18013.5.1.mDL |
ISO/IEC 18013-5 Mobile Driver's License | org.iso.18013.5.1 |
family_name, given_name, birth_date, issue_date, expiry_date, issuing_authority, document_number, portrait, driving_privileges, age_over_18, age_over_21 |
eu.europa.ec.eudi.pid.1 |
EU Digital Identity Wallet (EUDIW) Person Identification Data | eu.europa.ec.eudi.pid.1 |
family_name, first_name, birth_date, nationality, issuing_country, issuing_authority, personal_administrative_number |
com.google.wallet.idcard.1 |
Google Wallet ID Pass / Test IDs | com.google.wallet.idcard.1 |
given_name, family_name, birth_date, document_number, portrait |
2. Requesting Custom Document Types
To request custom document types, define the target docType string and corresponding namespace mappings when constructing the DeviceRequest:
// Example: Requesting a custom event ticket credential
val customDocType = "com.example.events.ticket"
val customNamespace = "com.example.events.ticket.1"
val customRequestedItems = mapOf(
"ticket_id" to false,
"event_name" to false,
"seat_section" to false,
"vip_access" to false
)
val multiDocRequestBytes = verificationHelper?.buildMultiDocDeviceRequest(
documents = listOf(
DocumentRequest(
docType = customDocType,
namespaces = mapOf(customNamespace to customRequestedItems)
)
)
)
Cryptographic Verification & Trust Management
Receiving a response payload is only the first step. Readers must perform a four-step cryptographic verification to validate the authenticity and integrity of the presented credential.
| Verification Step | Verification Target |
|---|---|
| 1. Issuer Authentication | Verify IssuerAuth (COSE_Sign1) against trusted IACA root certificates |
| 2. Validity Window Check | Ensure validFrom ≤ current time ≤ validUntil |
| 3. Data Integrity Check | Compute SHA-256 digests of returned elements and match against MSO ValueDigests |
| 4. Device Authentication | Verify DeviceSigned signature or MAC using the DeviceKey bound to the SessionTranscript |
1. The 4-Step Verification Pipeline
- Issuer Authentication (
IssuerAuth):- The Mobile Security Object (MSO) is signed by the Issuing Authority (
IssuerAuthpayload). - The reader verifies the
COSE_Sign1signature using the Document Signer certificate and ensures the certificate chains up to a trusted Issuing Authority CA (IACA) root certificate.
- The Mobile Security Object (MSO) is signed by the Issuing Authority (
- Validity Window Verification:
- The reader checks
validityInfo.validFromandvalidityInfo.validUntiltimestamps in the MSO against the reader's current clock to ensure the credential is not expired.
- The reader checks
- Data Integrity Verification (
ValueDigests):- For every received
IssuerSignedItem, the reader computes its digest (e.g. SHA-256) and verifies that it matches the corresponding hash entry in the MSO'sValueDigestsdictionary.
- For every received
- Device Authentication (
DeviceSigned):- The reader validates that the device presenting the credential holds the private key corresponding to the
DeviceKeypublished inside the signed MSO. - This is accomplished by verifying the
DeviceAuth(eitherDeviceSignatureorDeviceMac) over theSessionTranscript, binding the session to the reader's ephemeral key and preventing replay and man-in-the-middle attacks.
- The reader validates that the device presenting the credential holds the private key corresponding to the
2. Managing Trusted IACA Root Certificates
Production readers must maintain a secure, local trust store containing trusted IACA root certificates:
- Production IACA Certificates: Download and configure root certificates from official issuing authorities. Refer to our Supported Issuers and IACA Certificates list.
- AAMVA VICAL: For US jurisdictions, reader systems can integrate with the American Association of Motor Vehicle Administrators (AAMVA) Verified Issuer Certificate Authority List (VICAL) service to automatically synchronize state trust anchors.
- Sandbox Testing Roots: When testing against sandbox credentials, ensure the reader trusts the Google Sandbox IACA Root.
import org.multipaz.crypto.X509Cert
// Configure trusted IACA certificates in the trust store
val trustedCertificates = mutableListOf<X509Cert>()
// Add official state IACA certificates
trustedCertificates.add(X509Cert.fromPem(sampleStateIacaPem))
// Add Google Sandbox IACA root certificate for testing
trustedCertificates.add(X509Cert.fromPem(googleSandboxIacaPem))
val verifier = MultipazVerifier(trustStore = trustedCertificates)
val verificationResult = verifier.verify(deviceResponseBytes, sessionTranscript)
if (verificationResult.isIssuerAuthorized && verificationResult.isDeviceAuthenticated) {
// Credential is valid and authentic
} else {
// Reject presentation: cryptographic validation failed
}
Reader Authentication (Recommended)
Reader Authentication allows the reader application to cryptographically prove its identity to Google Wallet by signing the ReaderAuthentication structure using an authorized X.509 reader certificate.
- Why It Is Recommended: Reader Authentication allows your reader app or terminal to present a trusted identity to the user. While optional for basic public attributes (e.g., verifying
age_over_21), it is strongly recommended for reader integrations to increase user trust and may be legally or policy-required when requesting sensitive attributes (such as full Social Security Number, residential address, or specific state endorsements). - How It Works: The reader includes its certificate chain and signs the session transcript. Google Wallet displays the verified identity and organizational name of the reader to the user on the consent screen prior to data release.
Testing & Development Tools
To accelerate integration, use the following developer tools and reference implementations:
- Multipaz Reference Apps:
- Clone the Multipaz repository and run the
IdentityReaderAndroid sample app to test physical verification flows.
- Clone the Multipaz repository and run the
- Create a Test ID in Google Wallet:
- Follow our Create a Test ID Pass in Google Wallet guide to provision a simulated test credential into Google Wallet using the Utopia ePassport Simulator.
- Web-Based Verifier Testing:
- Use verifier.multipaz.org to inspect CBOR requests, explore claim queries, and test W3C / ISO 18013-7 web-based presentations.
Troubleshooting & Field Diagnostics
The following table lists common issues encountered during offline verification and recommended resolutions:
| Issue / Symptom | Root Cause | Recommended Resolution |
|---|---|---|
| BLE connection timeout / Failure to connect |
|
|
| NFC tap engagement fails or drops | User pulls mobile device away from the reader antenna before the BLE handover record is fully transferred. |
|
UNTRUSTED_ISSUER / Certificate chain failure |
The Document Signer certificate does not chain to any trusted IACA certificate in the reader's local trust store. |
|
INVALID_VALIDITY_INFO / Expired MSO |
|
|
DEVICE_AUTHENTICATION_FAILED |
Session transcript mismatch between reader and wallet, or invalid ephemeral device signature. |
|
| Android 12+ permission crash | Application attempted to scan or advertise over BLE without runtime permissions. |
|
UX & Privacy Guidelines for In-Person Readers
When designing physical readers and companion apps:
- Practice Selective Disclosure in the UI: Only display the decision or minimum required attribute to the operator (e.g. show a prominent green checkmark and "Age 21+ Verified" rather than displaying the user's full date of birth, address, and license number).
- Clear Physical Interaction Indicators: Label the NFC target zone clearly and display visual cues (such as animations or progress bars) showing each stage: Tap / Scan → Connecting → Verifying → Complete.
- Ephemeral Data Handling: Do not store or log personal data elements received from the wallet unless explicitly required by applicable law and disclosed via
intentToRetain = true.