คู่มือนี้อธิบายวิธีที่ฝ่ายที่อาศัย (RP) และนักพัฒนาแอปเครื่องอ่านสามารถใช้การยืนยันข้อมูลเข้าสู่ระบบดิจิทัลแบบเข้าร่วมด้วยตนเอง (ออฟไลน์) ที่แสดงจาก Google Wallet ตามมาตรฐานสากล ISO/IEC 18013-5
ข้อมูลเข้าสู่ระบบดิจิทัลใน Google Wallet สามารถยืนยันได้อย่างปลอดภัยในสภาพแวดล้อมจริง (เช่น เครื่องชำระเงิน ณ จุดขาย สถานที่จัดกิจกรรม ประตูขนส่งสาธารณะ เครื่องอ่านของหน่วยงานบังคับใช้กฎหมาย และแอปเครื่องอ่านบนอุปกรณ์เคลื่อนที่) โดยไม่จำเป็นต้องมีการเชื่อมต่ออินเทอร์เน็ตที่ใช้งานอยู่ในขณะที่แสดงข้อมูล
ภาพรวมของการนำเสนอแบบออฟไลน์ (ISO/IEC 18013-5)
มาตรฐาน ISO/IEC 18013-5 กำหนดโปรโตคอลที่ได้มาตรฐานและทำงานร่วมกันได้สำหรับการนำเสนอแบบออฟไลน์ระหว่างผู้ถือ (อุปกรณ์เคลื่อนที่ของผู้ใช้ที่ใช้ Google Wallet) กับเครื่องอ่าน / ผู้ตรวจสอบ (เครื่องอ่านบัตรจริงหรือแอปบนอุปกรณ์เคลื่อนที่ที่ใช้ร่วมกัน)
ขั้นตอนการนำเสนอจะแบ่งออกเป็นระยะต่างๆ ดังนี้
- การมีส่วนร่วมของอุปกรณ์: เครื่องอ่านและ Wallet จะสร้างการติดต่อครั้งแรกผ่านการแตะ NFC (การส่งต่อแบบคงที่หรือแบบเจรจาต่อรอง) หรือการสแกนคิวอาร์โค้ด ในระยะนี้ ระบบจะแลกเปลี่ยนข้อมูลเมตาการมีส่วนร่วมของอุปกรณ์และคีย์สาธารณะแบบชั่วคราวของเครื่องอ่าน (
EReaderKey) - การเชื่อมต่อการรับส่งข้อมูล: มีการเจรจาช่องทาง บลูทูธพลังงานต่ำ (BLE) ที่ปลอดภัยและเข้ารหัส (โดยเครื่องอ่านจะทำหน้าที่ในโหมด Central Client หรือ Peripheral Server)
- คำขอจากอุปกรณ์: เครื่องอ่านจะส่ง
DeviceRequestที่เข้ารหัส CBOR ซึ่งระบุประเภทเอกสารที่ขอ (เช่นorg.iso.18013.5.1.mDL) รวมถึงเนมสเปซและองค์ประกอบข้อมูลที่ขอ - ความยินยอมของผู้ใช้และการตรวจสอบสิทธิ์ของอุปกรณ์: Google Wallet จะแจ้งให้ผู้ใช้ตรวจสอบองค์ประกอบข้อมูลที่ขอและยืนยันการแชร์โดยใช้การตรวจสอบสิทธิ์ด้วยข้อมูลไบโอเมตริกหรือการล็อกหน้าจอ
- การตอบกลับของอุปกรณ์และการยืนยันการเข้ารหัส: Wallet จะส่ง
DeviceResponseที่เข้ารหัส CBOR กลับไป ซึ่งมี Mobile Security Object (MSO) ที่ลงนามแล้วและองค์ประกอบข้อมูลที่อุปกรณ์ลงนาม เครื่องอ่านจะยืนยันลายเซ็นที่เข้ารหัสเทียบกับใบรับรองรูทที่เชื่อถือได้
Multipaz Open-Source SDK
หากต้องการติดตั้งใช้งานแอปพลิเคชันเครื่องอ่านหรือเครื่องตรวจสอบ Google ขอแนะนำให้ใช้ Multipaz ซึ่งเป็น Kotlin Multiplatform (KMP) SDK แบบโอเพนซอร์สที่ Google พัฒนาขึ้นมาแต่แรกและมีส่วนร่วมใน OpenWallet Foundation (OWF)
Multipaz มีการใช้งานโปรโตคอลเครื่องอ่านและ Wallet ของ ISO/IEC 18013-5, ไปป์ไลน์การยืนยันการเข้ารหัส, การเข้ารหัส/ถอดรหัส CBOR และสคีมาประเภทเอกสารที่ขยายได้ซึ่งพร้อมใช้งานจริง
การผสานรวม Multipaz เข้ากับแอปเครื่องอ่านของคุณ
ขั้นตอนต่อไปนี้แสดงวิธีผสานรวม Multipaz SDK เข้ากับแอปพลิเคชันเครื่องอ่าน Android
ขั้นตอนที่ 1: เพิ่มทรัพยากร Dependency
ไลบรารี 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, การสแกนด้วยกล้อง (สำหรับการมีส่วนร่วมผ่านคิวอาร์โค้ด) และการรับส่งข้อมูลผ่านบลูทูธพลังงานต่ำ เพิ่มสิทธิ์ต่อไปนี้ลงใน 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: เริ่มต้นเครื่องมือการยืนยัน
ใช้VerificationHelperของ Multipaz หรือนามธรรมการเชื่อมต่อ Reader เพื่อจัดการเหตุการณ์การมีส่วนร่วมและจัดการวงจรการสื่อสาร 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 |
ID พาส / ID พาสทดสอบใน Google Wallet | 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)
)
)
)
การยืนยันการเข้ารหัสและการจัดการความน่าเชื่อถือ
การรับเพย์โหลดการตอบกลับเป็นเพียงขั้นตอนแรกเท่านั้น เครื่องอ่านต้องทำการยืนยันการเข้ารหัส 4 ขั้นตอนเพื่อตรวจสอบความถูกต้องและความสมบูรณ์ของข้อมูลเข้าสู่ระบบที่แสดง
| ขั้นตอนการยืนยัน | เป้าหมายการยืนยัน |
|---|---|
| 1. การตรวจสอบสิทธิ์ของผู้ออกบัตร | ยืนยัน IssuerAuth (COSE_Sign1) เทียบกับใบรับรองรูท IACA ที่เชื่อถือได้ |
| 2. การตรวจสอบกรอบเวลาที่ใช้ได้ | ตรวจสอบว่า validFrom ≤ เวลาปัจจุบัน ≤ validUntil |
| 3. การตรวจสอบความสมบูรณ์ของข้อมูล | คำนวณไดเจสต์ SHA-256 ขององค์ประกอบที่แสดงผลและจับคู่กับ MSO ValueDigests |
| 4. การตรวจสอบสิทธิ์อุปกรณ์ | ยืนยันลายเซ็น DeviceSigned หรือ MAC โดยใช้ DeviceKey ที่เชื่อมโยงกับ SessionTranscript |
1. ไปป์ไลน์การยืนยัน 4 ขั้นตอน
- การตรวจสอบสิทธิ์ของผู้ออกบัตร (
IssuerAuth):- Mobile Security Object (MSO) ได้รับการลงนามโดยผู้ออก (เพย์โหลด
IssuerAuth) - เครื่องอ่านจะยืนยันลายเซ็น
COSE_Sign1โดยใช้ใบรับรองผู้ลงนามเอกสาร และตรวจสอบว่าใบรับรองเชื่อมโยงกับใบรับรองรูทของผู้ออกใบรับรอง (IACA) ที่เชื่อถือได้
- Mobile Security Object (MSO) ได้รับการลงนามโดยผู้ออก (เพย์โหลด
- การยืนยันกรอบเวลาที่ใช้ได้:
- เครื่องอ่านจะตรวจสอบการประทับเวลา
validityInfo.validFromและvalidityInfo.validUntilใน MSO เทียบกับนาฬิกาปัจจุบันของเครื่องอ่านเพื่อให้แน่ใจว่าข้อมูลเข้าสู่ระบบยังไม่หมดอายุ
- เครื่องอ่านจะตรวจสอบการประทับเวลา
- การยืนยันความสมบูรณ์ของข้อมูล (
ValueDigests):- สำหรับ
IssuerSignedItemที่ได้รับแต่ละรายการ เครื่องอ่านจะคำนวณไดเจสต์ (เช่น SHA-256) และยืนยันว่าตรงกับรายการแฮชที่เกี่ยวข้องในพจนานุกรมValueDigestsของ MSO
- สำหรับ
- การตรวจสอบสิทธิ์อุปกรณ์ (
DeviceSigned):- เครื่องอ่านจะตรวจสอบว่าอุปกรณ์ที่แสดงข้อมูลเข้าสู่ระบบมีคีย์ส่วนตัวที่สอดคล้องกับ
DeviceKeyที่เผยแพร่ภายใน MSO ที่ลงนามแล้ว - ซึ่งทำได้โดยการยืนยัน
DeviceAuth(DeviceSignatureหรือDeviceMac) ผ่านSessionTranscriptซึ่งจะเชื่อมโยงเซสชันกับคีย์ชั่วคราวของเครื่องอ่าน และป้องกันการโจมตีแบบ Replay และการโจมตีแบบแทรกกลางการสื่อสาร
- เครื่องอ่านจะตรวจสอบว่าอุปกรณ์ที่แสดงข้อมูลเข้าสู่ระบบมีคีย์ส่วนตัวที่สอดคล้องกับ
2. การจัดการใบรับรองรูท IACA ที่เชื่อถือได้
เครื่องอ่านที่ใช้งานจริงต้องมี Trust Store ภายในเครื่องที่ปลอดภัยซึ่งมีใบรับรองรูท IACA ที่เชื่อถือได้ ดังนี้
- ใบรับรอง IACA เวอร์ชันที่ใช้งานจริง: ดาวน์โหลดและกำหนดค่าใบรับรองรูทจากหน่วยงานที่ออกใบรับรองอย่างเป็นทางการ โปรดดูรายการผู้ออกใบรับรองและใบรับรอง IACA ที่รองรับ
- AAMVA VICAL: สำหรับเขตอำนาจศาลของสหรัฐอเมริกา ระบบเครื่องอ่านสามารถผสานรวมกับบริการ Verified Issuer Certificate Authority List (VICAL) ของ American Association of Motor Vehicle Administrators (AAMVA) เพื่อซิงค์ Trust Anchor ของรัฐโดยอัตโนมัติ
- รูทการทดสอบแซนด์บ็อกซ์: เมื่อทดสอบกับข้อมูลเข้าสู่ระบบแซนด์บ็อกซ์ ให้ตรวจสอบว่าเครื่องอ่านเชื่อถือ 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 Wallet โดยการลงนามโครงสร้าง ReaderAuthentication โดยใช้ใบรับรองเครื่องอ่าน X.509 ที่ได้รับอนุญาต
- เหตุผลที่แนะนำ: การตรวจสอบสิทธิ์เครื่องอ่านช่วยให้แอปหรือเทอร์มินัลเครื่องอ่านแสดงข้อมูลประจำตัวที่เชื่อถือได้แก่ผู้ใช้ แม้ว่าจะเป็นตัวเลือกสำหรับแอตทริบิวต์สาธารณะพื้นฐาน (เช่น การยืนยัน
age_over_21) แต่เราขอแนะนำอย่างยิ่งให้ใช้การผสานรวมเครื่องอ่านเพื่อเพิ่มความไว้วางใจของผู้ใช้ และอาจต้องใช้ตามกฎหมายหรือนโยบายเมื่อขอแอตทริบิวต์ที่มีความละเอียดอ่อน (เช่น หมายเลขประกันสังคมแบบเต็ม ที่อยู่สำหรับพักอาศัย หรือการรับรองเฉพาะของรัฐ) - วิธีการทำงาน: เครื่องอ่านจะรวมเชนใบรับรองและลงนามในสำเนาข้อความของเซสชัน Google Wallet จะแสดงข้อมูลประจำตัวที่ยืนยันแล้วและชื่อองค์กรของเครื่องอ่านให้ผู้ใช้เห็นในหน้าจอขอความยินยอมก่อนที่จะเปิดเผยข้อมูล
เครื่องมือทดสอบและพัฒนา
หากต้องการเร่งการผสานรวม ให้ใช้เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์และการติดตั้งใช้งานอ้างอิงต่อไปนี้
- แอปอ้างอิง Multipaz
- โคลนที่เก็บ Multipaz และเรียกใช้แอปตัวอย่าง
IdentityReaderAndroid เพื่อทดสอบขั้นตอนการยืนยันตัวตนต่อหน้า
- โคลนที่เก็บ Multipaz และเรียกใช้แอปตัวอย่าง
- วิธีสร้างบัตรประจำตัวทดสอบใน Google Wallet
- ทำตามคำแนะนำในหัวข้อสร้าง ID พาสทดสอบใน Google Wallet เพื่อจัดสรรเอกสารรับรองการทดสอบจำลองลงใน Google Wallet โดยใช้โปรแกรมจำลองหนังสือเดินทางอิเล็กทรอนิกส์ Utopia
- การทดสอบ Verifier บนเว็บ
- ใช้ verifier.multipaz.org เพื่อตรวจสอบคำขอ CBOR, สำรวจการค้นหาการอ้างสิทธิ์ และทดสอบการนำเสนอตามเว็บ W3C / ISO 18013-7
การแก้ปัญหาและการวินิจฉัยภาคสนาม
ตารางต่อไปนี้แสดงปัญหาที่พบได้ทั่วไปในระหว่างการยืนยันแบบออฟไลน์และวิธีแก้ไขที่แนะนำ
| ปัญหา / ลักษณะปัญหา | สาเหตุของปัญหา | วิธีแก้ปัญหาที่แนะนำ |
|---|---|---|
| การเชื่อมต่อ BLE หมดเวลา / เชื่อมต่อไม่สำเร็จ |
|
|
| การมีส่วนร่วมผ่านการแตะ NFC ไม่สำเร็จหรือลดลง | ผู้ใช้นำอุปกรณ์เคลื่อนที่ออกจากเสาอากาศของเครื่องอ่านก่อนที่จะโอนบันทึกการส่งต่อ BLE เสร็จสมบูรณ์ |
|
UNTRUSTED_ISSUER / เชนใบรับรองไม่สำเร็จ |
ใบรับรอง Document Signer ไม่เชื่อมโยงกับใบรับรอง IACA ที่เชื่อถือได้ใน Trust Store ในเครื่องของเครื่องอ่าน |
|
INVALID_VALIDITY_INFO / MSO หมดอายุ |
|
|
DEVICE_AUTHENTICATION_FAILED |
ข้อความถอดเสียงของเซสชันไม่ตรงกันระหว่างเครื่องอ่านและ Wallet หรือลายเซ็นอุปกรณ์ชั่วคราวไม่ถูกต้อง |
|
| ข้อขัดข้องเกี่ยวกับสิทธิ์ใน Android 12 ขึ้นไป | แอปพลิเคชันพยายามสแกนหรือกระจายข้อมูลผ่าน BLE โดยไม่มีสิทธิ์รันไทม์ |
|
หลักเกณฑ์ด้าน UX และความเป็นส่วนตัวสำหรับเครื่องอ่านบัตรแบบต่อหน้า
เมื่อออกแบบเครื่องอ่านจริงและแอปที่ใช้ร่วมกัน ให้ทำดังนี้
- ฝึกการเปิดเผยข้อมูลแบบเลือกใน UI: แสดงเฉพาะการตัดสินใจหรือแอตทริบิวต์ที่จำเป็นขั้นต่ำต่อผู้ปฏิบัติงาน (เช่น แสดงเครื่องหมายถูกสีเขียวที่โดดเด่นและ "ยืนยันอายุ 21 ปีขึ้นไปแล้ว" แทนที่จะแสดงวันเกิด ที่อยู่ และหมายเลขใบอนุญาตแบบเต็มของผู้ใช้)
- ตัวบ่งชี้การโต้ตอบทางกายภาพที่ชัดเจน: ติดป้ายกำกับโซนเป้าหมาย NFC อย่างชัดเจน และแสดงสัญญาณภาพ (เช่น ภาพเคลื่อนไหวหรือแถบความคืบหน้า) ที่แสดงแต่ละระยะ: แตะ / สแกน → กำลังเชื่อมต่อ → กำลังยืนยัน → เสร็จสมบูรณ์
- การจัดการข้อมูลชั่วคราว: อย่าจัดเก็บหรือบันทึกองค์ประกอบข้อมูลส่วนตัวที่ได้รับจาก Wallet เว้นแต่กฎหมายที่เกี่ยวข้องจะกำหนดไว้อย่างชัดแจ้งและเปิดเผยผ่าน
intentToRetain = true