En esta guía, se explica cómo las partes de confianza (RP) y los desarrolladores de lectores pueden implementar la verificación presencial (sin conexión) de las credenciales digitales presentadas desde la Billetera de Google según el estándar internacional ISO/IEC 18013-5.
Las credenciales digitales de la Billetera de Google se pueden verificar de forma segura en entornos físicos (como terminales de punto de venta, lugares de eventos, puertas de transporte público, lectores de fuerzas del orden y apps de lectores móviles) sin requerir una conexión a Internet activa en el momento de la presentación.
Descripción general de la presentación sin conexión (ISO/IEC 18013-5)
El estándar ISO/IEC 18013-5 define un protocolo interoperable y estandarizado para la presentación sin conexión entre un titular (el dispositivo móvil del usuario que ejecuta la Billetera de Google) y un lector o verificador (una terminal física o una app complementaria para dispositivos móviles).
El flujo de presentación se produce en fases distintas:
- Interacción del dispositivo: El lector y la billetera establecen el contacto inicial a través de NFC Tap (entrega estática o negociada) o escaneo de código QR. Durante esta fase, se intercambian los metadatos de participación del dispositivo y la clave pública efímera del lector (
EReaderKey). - Conexión de transporte de datos: Se negocia un canal Bluetooth de bajo consumo (BLE) seguro y encriptado (con el lector actuando en modo de cliente central o servidor periférico).
- Solicitud del dispositivo: El lector transmite un
DeviceRequestcodificado en CBOR que especifica el tipo de documento solicitado (comoorg.iso.18013.5.1.mDL) y los espacios de nombres y elementos de datos específicos solicitados. - Consentimiento del usuario y autenticación del dispositivo: La Billetera de Google le solicita al usuario que revise los elementos de datos solicitados y confirme el uso compartido mediante la autenticación biométrica o el bloqueo de pantalla.
- Respuesta del dispositivo y verificación criptográfica: La billetera envía una
DeviceResponsecodificada en CBOR que contiene el objeto de seguridad móvil (MSO) firmado y los elementos de datos firmados por el dispositivo. El lector verifica las firmas criptográficas en comparación con los certificados raíz de confianza.
El SDK de código abierto de Multipaz
Para implementar una aplicación de lector o verificador, Google recomienda usar Multipaz, un SDK de Kotlin Multiplatform (KMP) de código abierto desarrollado originalmente por Google y aportado a la OpenWallet Foundation (OWF).
Multipaz proporciona una implementación lista para producción de los protocolos de lector y billetera ISO/IEC 18013-5, canalizaciones de verificación criptográfica, codificación y decodificación de CBOR, y esquemas de tipo de documento extensibles.
Cómo integrar Multipaz en tu app de lector
En los siguientes pasos, se muestra cómo integrar el SDK de Multipaz en una aplicación de lector de Android.
Paso 1: Agrega dependencias
Las bibliotecas de Multipaz se publican en Maven Central. Agrega los módulos necesarios al archivo build.gradle.kts de tu aplicación:
// 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")
}
Paso 2: Configura los permisos de Android
La verificación presencial requiere permisos de hardware para la interacción de NFC, el escaneo de la cámara (para la interacción de QR) y el transporte de datos de Bluetooth de bajo consumo. Agrega los siguientes permisos a tu 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>
Paso 3: Inicializa el motor de verificación
Usa las abstracciones de conexión VerificationHelper o Reader de Multipaz para controlar los eventos de interacción y administrar el ciclo de vida de la comunicación BLE:
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()
}
}
Paso 4: Construye el DeviceRequest
Especifica el tipo de documento y los elementos de datos individuales que tu aplicación necesita verificar. Siempre aplica el principio de divulgación mínima (p.ej., solicita solo age_over_21 en lugar de birth_date completa cuando verificas la edad):
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)
}
}
Cómo admitir tipos de documentos adicionales (DocTypes)
Multipaz admite credenciales estandarizadas listas para usar y proporciona una arquitectura extensible para solicitar tipos de documentos personalizados o específicos del dominio.
1. Tipos de documentos estándar integrados
La biblioteca multipaz-doctypes proporciona modelos de esquema predefinidos para credenciales estándar:
| Identificador del tipo de documento | Estándar o alcance | Espacio de nombres típico | Elementos comunes |
|---|---|---|---|
org.iso.18013.5.1.mDL |
Licencia de conducir digital ISO/IEC 18013-5 | 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 |
Datos de identificación de personas de la Billetera de identidad digital de la UE (EUDIW) | 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 |
Pase de ID de la Billetera de Google o IDs de prueba | com.google.wallet.idcard.1 |
given_name, family_name, birth_date, document_number, portrait |
2. Cómo solicitar tipos de documentos personalizados
Para solicitar tipos de documentos personalizados, define la cadena docType de destino y las asignaciones de espacio de nombres correspondientes cuando construyas el 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)
)
)
)
Verificación criptográfica y administración de confianza
Recibir una carga útil de respuesta es solo el primer paso. Los lectores deben realizar una verificación criptográfica de cuatro pasos para validar la autenticidad y la integridad de la credencial presentada.
| Paso de verificación | Destino de verificación |
|---|---|
| 1. Autenticación de la entidad emisora | Verifica IssuerAuth (COSE_Sign1) en comparación con los certificados raíz de IACA de confianza. |
| 2. Verificación del período de validez | Asegúrate de que validFrom ≤ hora actual ≤ validUntil. |
| 3. Verificación de la integridad de los datos | Calcula los resúmenes SHA-256 de los elementos que se muestran y compáralos con ValueDigests de MSO. |
| 4. Autenticación en dispositivos | Verifica la firma o el MAC DeviceSigned con la DeviceKey vinculada a la SessionTranscript. |
1. La canalización de verificación de 4 pasos
- Autenticación de la entidad emisora (
IssuerAuth):- La autoridad emisora (
IssuerAuthpayload) firma el objeto de seguridad móvil (MSO). - El lector verifica la firma
COSE_Sign1con el certificado del firmante del documento y se asegura de que la cadena de certificados llegue hasta un certificado raíz de autoridad certificadora (AC) de la entidad emisora (IACA) de confianza.
- La autoridad emisora (
- Verificación del período de validez:
- El lector verifica las marcas de tiempo
validityInfo.validFromyvalidityInfo.validUntilen el MSO en comparación con el reloj actual del lector para asegurarse de que la credencial no haya vencido.
- El lector verifica las marcas de tiempo
- Verificación de la integridad de los datos (
ValueDigests):- Para cada
IssuerSignedItemrecibido, el lector calcula su resumen (p.ej., SHA-256) y verifica que coincida con la entrada de hash correspondiente en el diccionarioValueDigestsdel MSO.
- Para cada
- Autenticación en dispositivos (
DeviceSigned):- El lector valida que el dispositivo que presenta la credencial tenga la clave privada correspondiente a la
DeviceKeypublicada dentro del MSO firmado. - Para ello, se verifica la
DeviceAuth(ya seaDeviceSignatureoDeviceMac) en laSessionTranscript, se vincula la sesión a la clave efímera del lector y se evitan los ataques de reproducción y de intermediario.
- El lector valida que el dispositivo que presenta la credencial tenga la clave privada correspondiente a la
2. Administra los certificados raíz de IACA de confianza
Los lectores de producción deben mantener un almacén de confianza local y seguro que contenga certificados raíz de IACA de confianza:
- Certificados de IACA de producción: Descarga y configura certificados raíz de autoridades emisoras oficiales. Consulta nuestra lista de entidades emisoras admitidas y certificados de IACA.
- AAMVA VICAL: En el caso de las jurisdicciones de EE.UU., los sistemas de lectores pueden integrarse con el servicio de la Lista de autoridades certificadoras verificadas (VICAL) de la Asociación Estadounidense de Administradores de Vehículos Motorizados (AAMVA) para sincronizar automáticamente los anclajes de confianza estatales.
- Raíces de pruebas de la zona de pruebas: Cuando realices pruebas con credenciales de la zona de pruebas, asegúrate de que el lector confíe en la raíz de IACA de la zona de pruebas de Google.
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
}
Autenticación del lector (recomendada)
La autenticación del lector permite que la aplicación del lector demuestre criptográficamente su identidad a la Billetera de Google firmando la estructura ReaderAuthentication con un certificado de lector X.509 autorizado.
- Por qué se recomienda: La autenticación del lector permite que tu app o terminal de lector le presente una identidad de confianza al usuario. Si bien es opcional para los atributos públicos básicos (p.ej., verificar
age_over_21), se recomienda para las integraciones de lectores aumentar la confianza del usuario y puede ser legal o requerida por la política cuando se solicitan atributos sensibles (como el número de Seguro Social completo, la dirección residencial o los endosos estatales específicos). - Cómo funciona: El lector incluye su cadena de certificados y firma la transcripción de la sesión. La Billetera de Google muestra la identidad verificada y el nombre de la organización del lector al usuario en la pantalla de consentimiento antes de la publicación de los datos.
Herramientas de prueba y desarrollo
Para acelerar la integración, usa las siguientes herramientas para desarrolladores y las implementaciones de referencia:
- Apps de referencia de Multipaz:
- Clona el repositorio de Multipaz y ejecuta la app de ejemplo de Android
IdentityReaderpara probar los flujos de verificación física.
- Clona el repositorio de Multipaz y ejecuta la app de ejemplo de Android
- Crea un ID de prueba en la Billetera de Google:
- Sigue nuestra guía para crear un pase de ID de prueba en la Billetera de Google para aprovisionar una credencial de prueba simulada en la Billetera de Google con el simulador de pasaporte electrónico de Utopia.
- Pruebas de verificadores basados en la Web:
- Usa verifier.multipaz.org para inspeccionar solicitudes CBOR, explorar consultas de reclamos y probar presentaciones basadas en la Web de W3C o ISO 18013-7.
Solución de problemas y diagnóstico de campo
En la siguiente tabla, se enumeran los problemas comunes que se producen durante la verificación sin conexión y las resoluciones recomendadas:
| Problema o síntoma | Causa raíz | Resolución recomendada |
|---|---|---|
| Tiempo de espera agotado de la conexión BLE o falla en la conexión |
|
|
| Falla o se interrumpe la participación de NFC Tap | El usuario aleja el dispositivo móvil de la antena del lector antes de que se transfiera por completo el registro de entrega de BLE. |
|
UNTRUSTED_ISSUER o falla en la cadena de certificados |
El certificado del firmante del documento no se encadena a ningún certificado de IACA de confianza en el almacén de confianza local del lector. |
|
INVALID_VALIDITY_INFO o MSO vencido |
|
|
DEVICE_AUTHENTICATION_FAILED |
No coincide la transcripción de la sesión entre el lector y la billetera, o la firma del dispositivo efímero no es válida. |
|
| Falla de permisos de Android 12 y versiones posteriores | La aplicación intentó escanear o publicar a través de BLE sin permisos de tiempo de ejecución. |
|
Lineamientos de UX y privacidad para lectores presenciales
Cuando diseñes lectores físicos y apps complementarias, ten en cuenta lo siguiente:
- Practica la divulgación selectiva en la IU: Solo muestra la decisión o el atributo mínimo requerido al operador (p.ej., muestra una marca de verificación verde destacada y "Edad verificada: 21 años o más" en lugar de mostrar la fecha de nacimiento, la dirección y el número de licencia completos del usuario).
- Indicadores claros de interacción física: Etiqueta claramente la zona objetivo de NFC y muestra indicadores visuales (como animaciones o barras de progreso) que muestren cada etapa: Presionar o escanear → Conectando → Verificando → Completado.
- Control de datos efímeros: No almacenes ni registres elementos de datos personales recibidos de la billetera, a menos que lo exija explícitamente la ley aplicable y se divulgue a través de
intentToRetain = true.