En esta guía, se explica cómo las partes de confianza (RP) y los desarrolladores de lectores pueden implementar la verificación en persona (sin conexión) de las credenciales digitales presentadas desde la Billetera de Google de acuerdo con 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 autoridades de orden público y apps de lectura para dispositivos móviles) sin necesidad de 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 estandarizado e interoperable 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 distintas fases:
- Interacción con el dispositivo: El lector y la billetera establecen el contacto inicial a través de un toque NFC (transferencia estática o negociada) o un 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 seguro y encriptado de Bluetooth de bajo consumo (BLE) (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 con la autenticación biométrica o el bloqueo de pantalla.
- Respuesta del dispositivo y verificación criptográfica: La billetera devuelve un
DeviceResponsecodificado 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 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 código abierto de Kotlin Multiplatform (KMP) 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 CBOR, y esquemas de tipos de documentos 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 para 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 con NFC, el escaneo con la cámara (para la interacción con QR) y el transporte de datos con 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 participación y administrar el ciclo de vida de la comunicación de 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: Crea la DeviceRequest
Especifica el tipo de documento y los elementos de datos individuales que tu aplicación necesita verificar. Aplica siempre el principio de divulgación mínima (p.ej., solicita solo age_over_21 en lugar de birth_date completo cuando verifiques 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)
}
}
Compatibilidad con 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 las credenciales estándar:
| Identificador del tipo de documento | Estándar / 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 personal 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 identificación de la Billetera de Google / IDs de prueba | com.google.wallet.idcard.1 |
given_name, family_name, birth_date, document_number y 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 | Objetivo de verificación |
|---|---|
| 1. Autenticación de la entidad emisora | Verifica IssuerAuth (COSE_Sign1) con los certificados raíz de la 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 mostrados y compáralos con el MSO ValueDigests |
| 4. Autenticación en dispositivos | Verifica la firma o MAC de DeviceSigned con el DeviceKey vinculado al SessionTranscript |
1. La canalización de verificación de 4 pasos
- Autenticación del emisor (
IssuerAuth):- La entidad emisora firma el objeto de seguridad móvil (MSO) (carga útil de
IssuerAuth). - El lector verifica la firma de
COSE_Sign1con el certificado de firma de documentos y se asegura de que el certificado se encadene a un certificado raíz de autoridad certificadora emisora (IACA) de confianza.
- La entidad emisora firma el objeto de seguridad móvil (MSO) (carga útil de
- Verificación de la ventana de validez:
- El lector verifica las marcas de tiempo
validityInfo.validFromyvalidityInfo.validUntilen el MSO 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):- Por 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.
- Por cada
- Autenticación de dispositivos (
DeviceSigned):- El lector valida que el dispositivo que presenta la credencial tenga la clave privada correspondiente al
DeviceKeypublicado dentro del MSO firmado. - Esto se logra verificando el
DeviceAuth(ya seaDeviceSignatureoDeviceMac) sobre elSessionTranscript, vinculando la sesión a la clave efímera del lector y evitando ataques de repetición y de intermediario.
- El lector valida que el dispositivo que presenta la credencial tenga la clave privada correspondiente al
2. Administra los certificados raíz de confianza de la IACA
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 las autoridades emisoras oficiales. Consulta nuestra lista de entidades emisoras y certificados de IACA compatibles.
- VICAL de la AAMVA: En las jurisdicciones de EE.UU., los sistemas de lectores pueden integrarse con el servicio de la lista de autoridades certificadoras de emisores verificados (VICAL) de la Asociación Estadounidense de Administradores de Vehículos Motorizados (AAMVA) para sincronizar automáticamente las anclas 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 la 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 (recomendado)
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 lectura 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 para aumentar la confianza de los usuarios y puede ser un requisito legal o de política cuando se solicitan atributos sensibles (como el número de seguridad social completo, la dirección residencial o las aprobaciones estatales específicas). - 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 que se divulguen 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 identificación de prueba en la Billetera de Google y aprovisionar una credencial de prueba simulada en la Billetera de Google con el simulador de pasaporte electrónico de Utopia.
- Pruebas del verificador basado en la Web:
- Usa verifier.multipaz.org para inspeccionar solicitudes de CBOR, explorar consultas de reclamaciones y probar presentaciones basadas en la Web de W3C / ISO 18013-7.
Solución de problemas y diagnóstico de campo
En la siguiente tabla, se enumeran los problemas habituales que se producen durante la verificación sin conexión y las soluciones recomendadas:
| Problema o síntoma | Causa raíz | Resolución recomendada |
|---|---|---|
| Tiempo de espera de conexión BLE agotado / No se pudo establecer la conexión |
|
|
| La interacción de pago sin contacto con NFC falla o se interrumpe | El usuario aleja el dispositivo móvil de la antena del lector antes de que se transfiera por completo el registro de transferencia de BLE. |
|
UNTRUSTED_ISSUER / Falla en la cadena de certificados |
El certificado de firma de documentos no se encadena a ningún certificado de IACA de confianza en el almacén de confianza local del lector. |
|
INVALID_VALIDITY_INFO / MSO vencido |
|
|
DEVICE_AUTHENTICATION_FAILED |
No coincide la transcripción de la sesión entre el lector y la billetera, o la firma efímera del dispositivo no es válida. |
|
| Falla de permisos en Android 12 y versiones posteriores | La aplicación intentó realizar un análisis o publicar anuncios a través de BLE sin permisos de tiempo de ejecución. |
|
Lineamientos de UX y privacidad para lectores en persona
Cuando diseñes lectores físicos y apps complementarias, ten en cuenta lo siguiente:
- Practica la divulgación selectiva en la IU: Muestra solo la decisión o el atributo mínimo requerido al operador (p.ej., muestra una marca de verificación verde destacada y "Edad mayor de 21 años verificada" en lugar de mostrar la fecha de nacimiento completa, la dirección y el número de licencia del usuario).
- Indicadores claros de interacción física: Etiqueta claramente la zona de destino de NFC y muestra indicadores visuales (como animaciones o barras de progreso) que muestren cada etapa: Presionar / escanear → Conectando → Verificando → Completado.
- Manejo 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.