В этом руководстве рассказывается, как полагающиеся стороны и разработчики считывающих устройств могут реализовать офлайн-проверку цифровых удостоверений личности, предъявляемых из Google Кошелька, в соответствии с международным стандартом ISO/IEC 18013-5.
Цифровые удостоверения личности в Google Кошельке можно безопасно проверять в физической среде (например, на терминалах оплаты, в местах проведения мероприятий, на турникетах, считывателях правоохранительных органов и в мобильных приложениях для считывания), не требуя активного подключения к интернету во время предъявления.
Обзор офлайн-презентации (ISO/IEC 18013-5)
Стандарт ISO/IEC 18013-5 определяет стандартизированный, совместимый протокол для офлайн-презентации между держателем (мобильным устройством пользователя с Google Кошельком) и считывателем / верификатором (физическим терминалом или сопутствующим мобильным приложением).
Процесс презентации состоит из нескольких этапов:
- Взаимодействие устройств. Терминал и кошелек устанавливают первоначальный контакт через NFC (статическая или согласованная передача) или сканирование QR-кода. На этом этапе происходит обмен метаданными взаимодействия устройства и временным открытым ключом считывателя (
EReaderKey). - Подключение для передачи данных. Устанавливается защищенный зашифрованный канал Bluetooth с низким энергопотреблением (BLE). При этом считыватель работает в режиме центрального клиента или периферийного сервера.
- Запрос устройства. Считыватель передает закодированный в CBOR
DeviceRequest, указывая тип запрашиваемого документа (например,org.iso.18013.5.1.mDL), а также конкретные пространства имен и элементы данных. - Согласие пользователя и аутентификация устройства. Google Кошелек предлагает пользователю проверить запрашиваемые элементы данных и подтвердить передачу с помощью биометрической аутентификации или блокировки экрана.
- Ответ устройства и криптографическая проверка. Кошелек отправляет обратно объект
DeviceResponse, закодированный в формате CBOR и содержащий подписанный объект MSO и элементы данных, подписанные устройством. Считыватель проверяет криптографические подписи по доверенным корневым сертификатам.
Multipaz Open-Source SDK
Для реализации приложения для считывания или проверки Google рекомендует использовать Multipaz – мультиплатформенный SDK Kotlin с открытым исходным кодом, изначально разработанный Google и переданный OpenWallet Foundation (OWF).
Multipaz предоставляет готовую к использованию реализацию протоколов считывателя и кошелька ISO/IEC 18013-5, конвейеры криптографической проверки, кодирование/декодирование CBOR и расширяемые схемы типов документов.
Как интегрировать Multipaz в приложение для считывания
Ниже описано, как интегрировать Multipaz SDK в приложение для считывания на Android.
Шаг 1. Добавьте зависимости
Библиотеки Multipaz опубликованы в Maven Central. Добавьте необходимые модули в файл build.gradle.kts приложения:
// 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")
}
Шаг 2. Настройте разрешения Android
Для личной проверки требуются разрешения на использование NFC, камеры (для сканирования QR-кодов) и Bluetooth с низким энергопотреблением. Добавьте в файл 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>
Шаг 3. Инициализируйте механизм проверки
Используйте абстракции Multipaz VerificationHelper или Reader connection, чтобы обрабатывать события взаимодействия и управлять жизненным циклом связи 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()
}
}
Шаг 4. Создайте DeviceRequest
Укажите тип документа и отдельные элементы данных, которые нужно проверить в приложении. Всегда применяйте принцип минимального раскрытия информации (например, запрашивайте только age_over_21, а не полную birth_date при проверке возраста):
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)
}
}
Поддержка дополнительных типов документов (DocType)
Multipaz поддерживает стандартные удостоверения личности и предоставляет расширяемую архитектуру для запроса документов определенных типов.
1. Встроенные стандартные типы документов
Библиотека multipaz-doctypes предоставляет стандартные модели схем для стандартных учетных данных:
| Идентификатор типа документа | Стандарт / область применения | Типичное пространство имен | Общие элементы |
|---|---|---|---|
org.iso.18013.5.1.mDL |
Цифровое водительское удостоверение 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 |
Идентификационные данные лица в цифровом кошельке ЕС (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 |
Цифровое удостоверение личности в Google Кошельке / тестовые удостоверения личности | com.google.wallet.idcard.1 |
given_name, family_name, birth_date, document_number, portrait |
2. Как запросить собственные типы документов
Чтобы запросить пользовательские типы документов, определите целевую строку docType и соответствующие сопоставления пространства имен при создании 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)
)
)
)
Криптографическая проверка и управление доверием
Получение полезной нагрузки ответа – это только первый шаг. Считыватели должны выполнять четырехэтапную криптографическую проверку, чтобы подтвердить подлинность и целостность предъявленного удостоверения.
| Этап проверки | Цель проверки |
|---|---|
| 1. Issuer Authentication | Проверьте IssuerAuth (COSE_Sign1) по доверенным корневым сертификатам IACA. |
| 2. Проверка срока действия | Убедитесь, что validFrom ≤ текущее время ≤ validUntil. |
| 3. Проверка целостности данных | Вычислите дайджесты SHA-256 возвращенных элементов и сопоставьте их с MSO ValueDigests. |
| 4. Аутентификация устройства | Проверьте подпись DeviceSigned или MAC-адрес, используя DeviceKey, связанный с SessionTranscript. |
1. Четыре этапа проверки
- Аутентификация эмитента (
IssuerAuth):- Объект мобильной безопасности (MSO) подписывается органом, выдавшим удостоверение (полезная нагрузка
IssuerAuth). - Терминал проверяет подпись
COSE_Sign1с помощью сертификата подписи документа и убеждается, что сертификат связан с доверенным корневым сертификатом центра сертификации, выдавшего документ (IACA).
- Объект мобильной безопасности (MSO) подписывается органом, выдавшим удостоверение (полезная нагрузка
- Проверка срока действия:
- Считыватель проверяет временные метки
validityInfo.validFromиvalidityInfo.validUntilв MSO, чтобы убедиться, что срок действия удостоверения не истек.
- Считыватель проверяет временные метки
- Проверка целостности данных (
ValueDigests):- Для каждого полученного элемента
IssuerSignedItemсчитывающее устройство вычисляет дайджест (например, SHA-256) и проверяет, соответствует ли он записи хеша в словареValueDigestsMSO.
- Для каждого полученного элемента
- Аутентификация устройства (
DeviceSigned):- Считыватель проверяет, что устройство, предоставляющее удостоверение, содержит закрытый ключ, соответствующий
DeviceKey, опубликованному в подписанном MSO. - Для этого проверяется
DeviceAuth(DeviceSignatureилиDeviceMac) поSessionTranscript, сеанс привязывается к эфемерному ключу считывателя и предотвращаются атаки с повторной передачей и атаки посредника.
- Считыватель проверяет, что устройство, предоставляющее удостоверение, содержит закрытый ключ, соответствующий
2. Управление доверенными корневыми сертификатами IACA
В рабочих считывателях должно быть защищенное локальное хранилище доверия, содержащее доверенные корневые сертификаты IACA:
- Рабочие сертификаты IACA. Скачайте и настройте корневые сертификаты от официальных центров сертификации. Ознакомьтесь со списком поддерживаемых эмитентов и сертификатов IACA.
- AAMVA VICAL. В США системы считывания могут интегрироваться с сервисом VICAL (Verified Issuer Certificate Authority List) Американской ассоциации администраторов автотранспорта (AAMVA), чтобы автоматически синхронизировать якоря доверия штатов.
- Корневые сертификаты для тестирования в изолированной среде. При тестировании с использованием учетных данных изолированной среды убедитесь, что считыватель доверяет корневому сертификату 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
}
Аутентификация читателей (рекомендуется)
Аутентификация считывающего устройства позволяет приложению для считывания криптографически подтвердить свою личность в Google Кошельке, подписав структуру ReaderAuthentication с помощью авторизованного сертификата считывающего устройства X.509.
- Почему это рекомендуется. Аутентификация считывающего устройства позволяет вашему приложению или терминалу подтвердить свою надежность пользователю. Для базовых общедоступных атрибутов (например, для подтверждения
age_over_21) это необязательно, но настоятельно рекомендуется для интеграции со считывающими устройствами, чтобы повысить доверие пользователей. Кроме того, это может требоваться по закону или правилам при запросе конфиденциальных атрибутов (например, полного номера социального страхования, адреса проживания или определенных государственных подтверждений). - Как это работает. Считывающее устройство включает цепочку сертификатов и подписывает расшифровку сеанса. Перед передачей данных Google Кошелек показывает пользователю подтвержденную личность и название организации считывающего устройства на окне запроса доступа.
Инструменты для тестирования и разработки
Чтобы ускорить интеграцию, используйте следующие инструменты разработчика и эталонные реализации:
- Приложения Multipaz Reference:
- Клонируйте репозиторий Multipaz и запустите пример приложения для Android
IdentityReader, чтобы протестировать процессы физической проверки.
- Клонируйте репозиторий Multipaz и запустите пример приложения для Android
- Как создать тестовое удостоверение личности в Google Кошельке
- Следуйте инструкциям в руководстве Как создать тестовое цифровое удостоверение в Google Кошельке, чтобы добавить в Google Кошелек тестовое цифровое удостоверение с помощью симулятора Utopia ePassport Simulator.
- Тестирование веб-верификатора
- Используйте verifier.multipaz.org, чтобы проверять запросы CBOR, изучать запросы на получение данных и тестировать веб-презентации W3C / ISO 18013-7.
Устранение неполадок и диагностика на месте
В таблице ниже перечислены распространенные проблемы, возникающие при офлайн-проверке, и рекомендуемые способы их решения.
| Проблема / признак | Основная причина | Рекомендуемое решение |
|---|---|---|
| Истекло время ожидания подключения BLE / Не удалось подключиться |
|
|
| Сбои или прерывания при бесконтактной оплате NFC | Пользователь убирает мобильное устройство от антенны считывателя до того, как запись о передаче BLE будет полностью передана. |
|
UNTRUSTED_ISSUER / Ошибка цепочки сертификатов |
Сертификат подписавшего документ не связан ни с одним доверенным сертификатом IACA в локальном хранилище доверия считывателя. |
|
INVALID_VALIDITY_INFO / Expired MSO |
|
|
DEVICE_AUTHENTICATION_FAILED |
Расшифровка сеанса не совпадает в считывателе и Кошельке, или подпись временного устройства недействительна. |
|
| Сбой, связанный с разрешениями, в Android 12 и более поздних версиях | Приложение пыталось выполнить сканирование или передать данные по BLE без динамических разрешений. |
|
Рекомендации по UX и правилам конфиденциальности для личных читателей
При разработке физических считывающих устройств и сопутствующих приложений:
- Выборочное раскрытие информации в интерфейсе. Показывайте оператору только решение или минимально необходимый атрибут. Например, вместо полной даты рождения, адреса и номера удостоверения личности пользователя можно показать заметную зеленую галочку и надпись Возраст 21+ подтвержден.
- Понятные индикаторы физического взаимодействия. Четко обозначьте целевую зону NFC и показывайте визуальные подсказки (например, анимацию или индикаторы выполнения), отражающие каждый этап: Нажмите / Отсканируйте → Подключение → Проверка → Готово.
- Обработка временных данных. Не храните и не регистрируйте элементы персональных данных, полученные из кошелька, если это не требуется действующим законодательством и не указано в
intentToRetain = true.