Z tego przewodnika dowiesz się, jak strony uzależnione (RP) i deweloperzy czytników mogą wdrożyć weryfikację stacjonarną (offline) cyfrowych dokumentów tożsamości przedstawianych z Portfela Google zgodnie z międzynarodowym standardem ISO/IEC 18013-5.
Cyfrowe dokumenty tożsamości w Portfelu Google można bezpiecznie weryfikować w środowiskach fizycznych (takich jak terminale płatnicze, miejsca wydarzeń, bramki transportu publicznego, czytniki organów ścigania i aplikacje do odczytu mobilnego) bez konieczności aktywnego połączenia z internetem w momencie ich okazywania.
Omówienie prezentacji offline (ISO/IEC 18013-5)
Norma ISO/IEC 18013-5 definiuje ustandaryzowany, interoperacyjny protokół prezentacji offline między posiadaczem (urządzeniem mobilnym użytkownika z Portfelem Google) a czytnikiem / weryfikatorem (terminalem fizycznym lub towarzyszącą aplikacją mobilną).
Proces prezentacji składa się z kilku etapów:
- Zaangażowanie urządzenia: czytnik i portfel nawiązują początkowe połączenie przez zbliżenie NFC (statyczne lub negocjowane przekazanie) lub zeskanowanie kodu QR. Na tym etapie wymieniane są metadane zaangażowania urządzenia i tymczasowy klucz publiczny czytnika (
EReaderKey). - Połączenie do przesyłania danych: negocjowany jest bezpieczny, szyfrowany kanał Bluetooth Low Energy (BLE) (czytnik działa w trybie Central Client lub Peripheral Server).
- Żądanie urządzenia: czytnik przesyła zakodowany w formacie CBOR obiekt
DeviceRequest, który określa żądany typ dokumentu (np.org.iso.18013.5.1.mDL) oraz konkretne żądane przestrzenie nazw i elementy danych. - Zgoda użytkownika i uwierzytelnianie urządzenia: Portfel Google prosi użytkownika o sprawdzenie żądanych elementów danych i potwierdzenie udostępnienia za pomocą uwierzytelniania biometrycznego lub blokady ekranu.
- Odpowiedź urządzenia i weryfikacja kryptograficzna: portfel odsyła zakodowany w formacie CBOR obiekt
DeviceResponsezawierający podpisany obiekt zabezpieczeń mobilnych (MSO) i podpisane przez urządzenie elementy danych. Czytnik weryfikuje podpisy kryptograficzne na podstawie zaufanych certyfikatów głównych.
Pakiet SDK Multipaz Open-Source
Aby wdrożyć aplikację czytnika lub weryfikatora, Google zaleca użycie Multipaz, czyli pakietu SDK Kotlin Multiplatform (KMP) o otwartym kodzie źródłowym, który został pierwotnie opracowany przez Google i przekazany do OpenWallet Foundation (OWF).
Multipaz zapewnia gotową do wykorzystania w środowisku produkcyjnym implementację protokołów czytnika i portfela ISO/IEC 18013-5, potoki weryfikacji kryptograficznej, kodowanie i dekodowanie CBOR oraz rozszerzalne schematy typów dokumentów.
Integracja Multipaz z aplikacją czytnika
Poniższe kroki pokazują, jak zintegrować pakiet SDK Multipaz z aplikacją czytnika na Androida.
Krok 1. Dodaj zależności
Biblioteki Multipaz są publikowane w Maven Central. Dodaj wymagane moduły do pliku build.gradle.kts aplikacji:
// 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")
}
Krok 2. Skonfiguruj uprawnienia na Androidzie
Weryfikacja stacjonarna wymaga uprawnień sprzętowych do korzystania z NFC, skanowania aparatem (w przypadku kodów QR) i przesyłania danych przez Bluetooth Low Energy. Dodaj do pliku AndroidManifest.xml te uprawnienia:
<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>
Krok 3. Zainicjuj mechanizm weryfikacji
Użyj abstrakcji połączenia VerificationHelper lub czytnika Multipaz, aby obsługiwać zdarzenia zaangażowania i zarządzać cyklem życia komunikacji 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()
}
}
Krok 4. Utwórz DeviceRequest
Określ typ dokumentu i poszczególne elementy danych, które aplikacja musi zweryfikować. Zawsze stosuj zasadę minimalnego ujawniania informacji (np. proś tylko o age_over_21, a nie o pełne birth_date podczas weryfikacji wieku):
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)
}
}
Obsługa dodatkowych typów dokumentów (DocTypes)
Multipaz obsługuje standardowe dane uwierzytelniające i zapewnia rozszerzalną architekturę do żądania niestandardowych lub specyficznych dla domeny typów dokumentów.
1. Wbudowane standardowe typy dokumentów
Biblioteka multipaz-doctypes udostępnia wstępnie zdefiniowane modele schematów dla standardowych danych logowania:
| Identyfikator typu dokumentu | Standard / zakres | Typowa przestrzeń nazw | Typowe elementy |
|---|---|---|---|
org.iso.18013.5.1.mDL |
cyfrowe prawo jazdy zgodne z normą 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 |
Dane identyfikacyjne osoby w cyfrowym portfelu tożsamości 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 |
Cyfrowy dokument tożsamości w Portfelu Google / dokumenty testowe | com.google.wallet.idcard.1 |
given_name, family_name, birth_date, document_number, portrait |
2. Prośba o niestandardowe typy dokumentów
Aby poprosić o niestandardowe typy dokumentów, podczas tworzenia DeviceRequest zdefiniuj ciąg znaków docType i odpowiednie mapowania przestrzeni nazw:
// 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)
)
)
)
Weryfikacja kryptograficzna i zarządzanie zaufaniem
Otrzymanie ładunku odpowiedzi to dopiero pierwszy krok. Czytniki muszą przeprowadzić 4-etapową weryfikację kryptograficzną, aby potwierdzić autentyczność i integralność przedstawionego dokumentu.
| Krok weryfikacji | Cel weryfikacji |
|---|---|
| 1. Uwierzytelnianie wydawcy | Weryfikacja certyfikatu IssuerAuth (COSE_Sign1) pod kątem zaufanych certyfikatów głównych IACA |
| 2. Sprawdzanie okresu ważności | Sprawdź, czy validFrom ≤ bieżąca godzina ≤ validUntil |
| 3. Sprawdzanie integralności danych | Oblicz skróty SHA-256 zwróconych elementów i dopasuj je do MSO ValueDigests |
| 4. Uwierzytelnianie urządzenia | Weryfikuj podpis DeviceSigned lub MAC za pomocą DeviceKey powiązanego z SessionTranscript |
1. 4-etapowy proces weryfikacji
- Uwierzytelnianie wystawcy (
IssuerAuth):- Obiekt zabezpieczeń mobilnych (MSO) jest podpisany przez organ wydający (
IssuerAuthładunek). - Czytnik weryfikuje podpis
COSE_Sign1za pomocą certyfikatu osoby podpisującej dokument i sprawdza, czy łańcuch certyfikatów prowadzi do zaufanego certyfikatu głównego urzędu certyfikacji wydającego dokumenty (IACA).
- Obiekt zabezpieczeń mobilnych (MSO) jest podpisany przez organ wydający (
- Weryfikacja okna ważności:
- Czytnik sprawdza sygnatury czasowe
validityInfo.validFromivalidityInfo.validUntilw obiekcie MSO względem bieżącego zegara czytnika, aby upewnić się, że dokument nie wygasł.
- Czytnik sprawdza sygnatury czasowe
- Weryfikacja integralności danych (
ValueDigests):- Dla każdego otrzymanego elementu
IssuerSignedItemczytnik oblicza jego skrót (np. SHA-256) i sprawdza, czy jest on zgodny z odpowiednim wpisem skrótu w słownikuValueDigestsMSO.
- Dla każdego otrzymanego elementu
- Uwierzytelnianie urządzenia (
DeviceSigned):- Czytnik sprawdza, czy urządzenie, które przedstawia dane uwierzytelniające, ma klucz prywatny odpowiadający kluczowi
DeviceKeyopublikowanemu w podpisanym obiekcie MSO. - Odbywa się to przez weryfikację
DeviceAuth(DeviceSignaturelubDeviceMac) przezSessionTranscript, powiązanie sesji z kluczem efemerycznym czytnika i zapobieganie atakom typu „replay” i „man-in-the-middle”.
- Czytnik sprawdza, czy urządzenie, które przedstawia dane uwierzytelniające, ma klucz prywatny odpowiadający kluczowi
2. Zarządzanie zaufanymi certyfikatami głównymi IACA
Czytniki produkcyjne muszą utrzymywać bezpieczny, lokalny magazyn zaufania zawierający zaufane certyfikaty główne IACA:
- Produkcyjne certyfikaty IACA: pobierz i skonfiguruj certyfikaty główne od oficjalnych urzędów certyfikacji. Zapoznaj się z listą obsługiwanych wydawców i certyfikatów IACA.
- AAMVA VICAL: w przypadku jurysdykcji w Stanach Zjednoczonych systemy czytników mogą integrować się z usługą Verified Issuer Certificate Authority List (VICAL) organizacji American Association of Motor Vehicle Administrators (AAMVA), aby automatycznie synchronizować kotwice zaufania stanu.
- Certyfikaty główne do testowania w piaskownicy: podczas testowania z użyciem danych logowania w piaskownicy upewnij się, że czytnik ufa certyfikatowi głównemu IACA piaskownicy 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
}
Uwierzytelnianie czytnika (zalecane)
Uwierzytelnianie czytnika umożliwia aplikacji czytnika kryptograficzne potwierdzenie swojej tożsamości w Portfelu Google przez podpisanie struktury ReaderAuthentication za pomocą autoryzowanego certyfikatu czytnika X.509.
- Dlaczego jest to zalecane: uwierzytelnianie czytnika umożliwia aplikacji czytnika lub terminalowi przedstawienie użytkownikowi zaufanej tożsamości. Chociaż jest to opcjonalne w przypadku podstawowych atrybutów publicznych (np.weryfikacji
age_over_21), zdecydowanie zalecamy to w przypadku integracji czytników, aby zwiększyć zaufanie użytkowników. Może to być wymagane przez prawo lub zasady, gdy prosisz o atrybuty poufne (takie jak pełny numer PESEL, adres zamieszkania lub określone potwierdzenia stanu). - Jak to działa: czytnik dołącza swój łańcuch certyfikatów i podpisuje transkrypcję sesji. Przed udostępnieniem danych Portfel Google wyświetla użytkownikowi na ekranie zgody zweryfikowaną tożsamość i nazwę organizacji czytnika.
Narzędzia do testowania i programowania
Aby przyspieszyć integrację, użyj tych narzędzi dla programistów i przykładowych implementacji:
- Przykładowe aplikacje Multipaz:
- Skopiuj repozytorium Multipaz i uruchom przykładową aplikację
IdentityReaderna Androida, aby przetestować procesy weryfikacji fizycznej.
- Skopiuj repozytorium Multipaz i uruchom przykładową aplikację
- Tworzenie dokumentu tożsamości w Portfelu Google:
- Postępuj zgodnie z instrukcjami podanymi w przewodniku Tworzenie testowego cyfrowego dokumentu tożsamości w Portfelu Google, aby udostępnić symulowany testowy cyfrowy dokument tożsamości w Portfelu Google za pomocą symulatora e-paszportu Utopia.
- Testowanie weryfikatora internetowego:
- Użyj strony verifier.multipaz.org, aby sprawdzać żądania CBOR, przeglądać zapytania dotyczące uprawnień i testować prezentacje internetowe W3C / ISO 18013-7.
Rozwiązywanie problemów i diagnostyka w terenie
W tabeli poniżej znajdziesz typowe problemy występujące podczas weryfikacji offline i zalecane rozwiązania:
| Problem / objaw | Główna przyczyna | Zalecane rozwiązanie |
|---|---|---|
| Przekroczenie limitu czasu połączenia BLE / błąd połączenia |
|
|
| Niepowodzenie lub spadek zaangażowania po zbliżeniu urządzenia NFC | Użytkownik odsuwa urządzenie mobilne od anteny czytnika, zanim rekord przekazania BLE zostanie w pełni przesłany. |
|
UNTRUSTED_ISSUER / Błąd łańcucha certyfikatów |
Certyfikat osoby podpisującej dokument nie jest powiązany z żadnym zaufanym certyfikatem IACA w lokalnym magazynie zaufania czytnika. |
|
INVALID_VALIDITY_INFO / Expired MSO |
|
|
DEVICE_AUTHENTICATION_FAILED |
Niezgodność transkrypcji sesji między czytnikiem a portfelem lub nieprawidłowy tymczasowy podpis urządzenia. |
|
| Awaria uprawnień na Androidzie 12 lub nowszym | Aplikacja próbowała skanować lub rozgłaszać przez BLE bez uprawnień w czasie działania. |
|
Wytyczne dotyczące UX i prywatności w przypadku czytników zbliżeniowych
Podczas projektowania czytników fizycznych i aplikacji towarzyszących:
- Stosuj selektywne ujawnianie informacji w interfejsie: wyświetlaj operatorowi tylko decyzję lub minimalny wymagany atrybut (np. widoczny zielony znacznik wyboru i komunikat „Wiek powyżej 21 lat został zweryfikowany” zamiast pełnej daty urodzenia, adresu i numeru prawa jazdy użytkownika).
- Wyraźne wskaźniki interakcji fizycznej: wyraźnie oznacz obszar docelowy NFC i wyświetlaj wskazówki wizualne (np. animacje lub paski postępu) pokazujące każdy etap: Kliknij / Zeskanuj → Łączenie → Weryfikacja → Gotowe.
- Postępowanie z danymi tymczasowymi: nie przechowuj ani nie rejestruj elementów danych osobowych otrzymanych z portfela, chyba że jest to wyraźnie wymagane przez obowiązujące przepisy i ujawnione w
intentToRetain = true.