โปรแกรมตรวจสอบคีย์ของระบบ Android

โปรแกรมตรวจสอบคีย์สำหรับ Android ช่วยให้ผู้ใช้มีวิธีที่ปลอดภัยและเป็นหนึ่งเดียวในการ ยืนยันว่าตนกำลังสื่อสารกับบุคคลที่ถูกต้องในแอปที่เข้ารหัสจากต้นทางถึงปลายทาง (E2EE) ของคุณ โดยจะปกป้องผู้ใช้จากการโจมตีแบบ Man-in-the-Middle ด้วยการ อนุญาตให้ผู้ใช้ยืนยันความถูกต้องของคีย์การเข้ารหัสสาธารณะของรายชื่อติดต่อ ผ่าน UI ของระบบที่เชื่อถือได้และสอดคล้องกัน

ฟีเจอร์นี้ให้บริการโดยโปรแกรมตรวจสอบคีย์ ซึ่งเป็นบริการของระบบที่เป็นส่วนหนึ่ง ของบริการของระบบ Google และจัดจำหน่ายโดยใช้ Play Store โดยทำหน้าที่เป็นที่เก็บคีย์สาธารณะ E2EE แบบรวมศูนย์ในอุปกรณ์

เหตุผลที่ควรผสานรวมกับโปรแกรมตรวจสอบคีย์

  • มอบ UX ที่เป็นหนึ่งเดียวกัน: คุณสามารถเปิดตัว UI มาตรฐานของระบบแทนการสร้างขั้นตอนการยืนยันของคุณเอง ซึ่งจะช่วยให้ผู้ใช้ได้รับประสบการณ์ที่สอดคล้องกันและเชื่อถือได้ในแอปทั้งหมด
  • เพิ่มความมั่นใจให้ผู้ใช้: สถานะการยืนยันที่ชัดเจนและได้รับการสนับสนุนจากระบบจะช่วยให้ผู้ใช้มั่นใจว่าการสนทนาของตนปลอดภัยและเป็นส่วนตัว
  • ลดค่าใช้จ่ายในการพัฒนา: ลดความซับซ้อนของ UI การยืนยันคีย์ ที่เก็บข้อมูล และการจัดการสถานะไปยังบริการของระบบ

คำสำคัญ

  • lookupKey: ตัวระบุแบบทึบถาวรสำหรับรายชื่อติดต่อ ซึ่งจัดเก็บไว้ในคอลัมน์ LOOKUP_KEY ของผู้ให้บริการรายชื่อติดต่อ contact ID ต่างจาก lookupKey ตรงที่ยังคงมีเสถียรภาพแม้ว่าจะมีการเปลี่ยนแปลงหรือผสานรายละเอียดการติดต่อพื้นฐาน ก็ตาม จึงเป็นวิธีที่แนะนำในการอ้างอิงรายชื่อติดต่อ
  • accountId: ตัวระบุเฉพาะแอปสำหรับบัญชีของผู้ใช้ในอุปกรณ์ รหัสนี้กำหนดโดยแอปของคุณและช่วยแยกความแตกต่างระหว่างบัญชีหลายบัญชี ที่ผู้ใช้รายเดียวอาจมี ซึ่งจะแสดงต่อผู้ใช้ใน UI ดังนั้นเราขอแนะนำให้ใช้ข้อมูลที่มีความหมาย เช่น หมายเลขโทรศัพท์ อีเมล หรือแฮนเดิลของผู้ใช้
  • deviceId: ตัวระบุที่ไม่ซ้ำกันสำหรับอุปกรณ์ที่เฉพาะเจาะจงซึ่งเชื่อมโยงกับบัญชีของผู้ใช้ ซึ่งช่วยให้ผู้ใช้มีอุปกรณ์หลายเครื่องได้ โดยแต่ละเครื่องจะมีชุดคีย์การเข้ารหัสของตัวเอง ไม่จำเป็นต้องเป็นอุปกรณ์จริง แต่อาจเป็นวิธีแยกความแตกต่างระหว่างคีย์หลายรายการที่ใช้สำหรับบัญชีเดียวกัน

เริ่มต้นใช้งาน

ก่อนเริ่มต้น ให้ตั้งค่าแอปเพื่อสื่อสารกับบริการ Key Verifier

ประกาศสิทธิ์: ใน AndroidManifest.xml ให้ประกาศสิทธิ์ต่อไปนี้ นอกจากนี้ คุณยังต้องขอสิทธิ์เหล่านี้จากผู้ใช้ในขณะรันไทม์ด้วย

<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />

รับอินสแตนซ์ไคลเอ็นต์: รับอินสแตนซ์ของ ContactKeys ซึ่งเป็น จุดแรกเข้าสู่ API

import com.google.android.gms.contactkeys.ContactKeys

val contactKeyClient = ContactKeys.getClient(context)

คำแนะนำสำหรับนักพัฒนาแอปรับส่งข้อความ

ในฐานะนักพัฒนาแอปรับส่งข้อความ บทบาทหลักของคุณคือการเผยแพร่คีย์สาธารณะของผู้ใช้และคีย์ของรายชื่อติดต่อไปยังบริการตรวจสอบคีย์

เผยแพร่คีย์สาธารณะของผู้ใช้

หากต้องการอนุญาตให้ผู้อื่นค้นหาและยืนยันผู้ใช้ของคุณ ให้เผยแพร่คีย์สาธารณะของผู้ใช้ไปยัง ที่เก็บในอุปกรณ์ พิจารณาสร้างคีย์ใน ที่เก็บคีย์ของ Android เพื่อเพิ่มความปลอดภัย

import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun publishSelfKey(
    contactKeyClient: ContactKeyClient,
    accountId: String,
    deviceId: String,
    publicKey: ByteArray
) {
    try {
        Tasks.await(
          contactKeyClient.updateOrInsertE2eeSelfKey(
            deviceId,
            accountId,
            publicKey
          )
        )
        // Self key published successfully.
    } catch (e: Exception) {
        // Handle error.
    }
}

เชื่อมโยงคีย์สาธารณะกับรายชื่อติดต่อ

เมื่อแอปได้รับคีย์สาธารณะสำหรับรายชื่อติดต่อของผู้ใช้รายใดรายหนึ่ง คุณต้อง จัดเก็บและเชื่อมโยงคีย์ดังกล่าวกับรายชื่อติดต่อนั้นในที่เก็บส่วนกลาง ซึ่งจะ ช่วยให้ยืนยันคีย์ได้และช่วยให้แอปอื่นๆ แสดงสถานะการยืนยัน สำหรับรายชื่อติดต่อได้ หากต้องการดำเนินการนี้ คุณต้องมี lookupKey ของรายชื่อติดต่อจาก ผู้ให้บริการรายชื่อติดต่อของ Android โดยปกติแล้วจะเกิดขึ้นเมื่อดึงข้อมูลคีย์ จากเซิร์ฟเวอร์การกระจายคีย์หรือระหว่างการซิงค์คีย์ในเครื่องเป็นระยะ

import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun storeContactKey(
    contactKeyClient: ContactKeyClient,
    contactLookupKey: String,
    contactAccountId: String,
    contactDeviceId: String,
    contactPublicKey: ByteArray
) {
    try {
        Tasks.await(
            contactKeyClient.updateOrInsertE2eeContactKey(
                contactLookupKey,
                contactDeviceId,
                contactAccountId,
                contactPublicKey
            )
        )
        // Contact's key stored successfully.
    } catch (e: Exception) {
        // Handle error.
    }
}

เรียกข้อมูลคีย์และสถานะการยืนยัน

หลังจากเผยแพร่คีย์แล้ว ผู้ใช้จะยืนยันคีย์ผ่านการสแกนคิวอาร์โค้ดด้วยตนเองได้ UI ของแอปควรแสดงว่าการสนทนาใช้คีย์ที่ ยืนยันแล้วหรือไม่ คีย์แต่ละรายการมีสถานะการยืนยันที่คุณใช้เพื่อแจ้ง UI ได้

ทำความเข้าใจสถานะการยืนยัน:

  • UNVERIFIED: นี่คือสถานะเริ่มต้นของคีย์ใหม่ทุกรายการ ซึ่งหมายความว่าคีย์มีอยู่ แต่ผู้ใช้ยังไม่ได้ยืนยันความถูกต้อง ใน UI คุณ ควรถือว่านี่เป็นสถานะที่เป็นกลางและโดยปกติแล้วจะไม่แสดงตัวบ่งชี้พิเศษ ใดๆ

  • VERIFIED: สถานะนี้บ่งบอกถึงระดับความน่าเชื่อถือสูง ซึ่งหมายความว่าผู้ใช้ ได้ทำขั้นตอนการยืนยัน (เช่น การสแกนคิวอาร์โค้ด) เสร็จสมบูรณ์แล้ว และ ยืนยันว่าคีย์เป็นของผู้ติดต่อที่ต้องการ ใน UI คุณควรแสดงตัวบ่งชี้ที่ชัดเจนและเป็นบวก เช่น เครื่องหมายถูกสีเขียวหรือ โล่

  • VERIFICATION_FAILED: นี่คือสถานะคำเตือน ซึ่งหมายความว่าคีย์ ที่เชื่อมโยงกับรายชื่อติดต่อไม่ตรงกับคีย์ที่ ยืนยันแล้วก่อนหน้านี้ ซึ่งอาจเกิดขึ้นได้หากรายชื่อติดต่อมีอุปกรณ์ใหม่ แต่อาจบ่งบอกถึงความเสี่ยงด้านความปลอดภัยได้ด้วย ใน UI ให้แจ้งเตือนผู้ใช้ พร้อมคำเตือนที่ชัดเจนและแนะนำให้ผู้ใช้ยืนยันอีกครั้งก่อนส่งข้อมูลที่ละเอียดอ่อน

คุณเรียกดูสถานะรวมของคีย์ทั้งหมดที่เชื่อมโยงกับรายชื่อติดต่อได้ เราขอแนะนำให้ใช้ VerificationState.leastVerifiedFrom() เพื่อแก้ไขสถานะ เมื่อมีคีย์หลายรายการ เนื่องจากจะจัดลำดับความสำคัญของ VERIFICATION_FAILED เหนือ VERIFIED ได้อย่างถูกต้อง

  • การดูสถานะรวมในระดับรายชื่อติดต่อ
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.contactkeys.constants.VerificationState
import com.google.android.gms.tasks.Tasks

suspend fun displayContactVerificationStatus(
    contactKeyClient: ContactKeyClient,
    contactLookupKey: String
) {
    try {
        val keysResult = Tasks.await(contactKeyClient.getAllE2eeContactKeys(contactLookupKey))
        val states =
          keysResult.keys.map { VerificationState.fromState(it.localVerificationState) }
        val contactStatus = VerificationState.leastVerifiedFrom(states)
        updateUi(contactLookupKey, contactStatus)
    } catch (e: Exception) {
        // Handle error.
    }
}
  • การดูสถานะรวมที่ระดับบัญชี
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.contactkeys.constants.VerificationState
import com.google.android.gms.tasks.Tasks

suspend fun displayAccountVerificationStatus(
    contactKeyClient: ContactKeyClient,
    accountId: String
) {
    try {
        val keys = Tasks.await(contactKeyClient.getE2eeAccountKeysForAccount(accountId))
        val states = keys.map { VerificationState.fromState(it.localVerificationState) }
        val accountStatus = VerificationState.leastVerifiedFrom(states)
        updateUi(accountId, accountStatus)
    } catch (e: Exception) {
        // Handle error.
    }
}

สังเกตการเปลี่ยนแปลงที่สำคัญแบบเรียลไทม์

คุณควรรับฟังข้อมูลอัปเดตเพื่อให้มั่นใจว่า UI ของแอปจะแสดงสถานะความน่าเชื่อถือที่ถูกต้องเสมอ วิธีที่แนะนำคือการใช้ Flow-based API ซึ่งจะส่งรายการคีย์ใหม่ทุกครั้งที่มีการเพิ่ม นำออก หรือเปลี่ยนสถานะการยืนยันของคีย์สำหรับบัญชีที่สมัครใช้บริการ ซึ่งจะมีประโยชน์อย่างยิ่งในการอัปเดตรายชื่อสมาชิกของการสนทนากลุ่ม ให้เป็นปัจจุบัน สถานะการยืนยันของคีย์อาจเปลี่ยนแปลงได้ในกรณีต่อไปนี้

  • ผู้ใช้ทำขั้นตอนการยืนยันเสร็จสมบูรณ์ (เช่น สแกนคิวอาร์โค้ด)
  • คีย์ของรายชื่อติดต่อได้รับการแก้ไข ทำให้ไม่ตรงกับค่าที่ยืนยันก่อนหน้านี้อีกต่อไป
fun observeKeyUpdates(contactKeyClient: ContactKeyClient, accountIds: List<String>) {
    lifecycleScope.launch {
        contactKeyClient.getAccountContactKeysFlow(accountIds)
            .collect { updatedKeys ->
                // A key was added, removed, or updated.
                // Refresh your app's UI and internal state.
                refreshUi(updatedKeys)
            }
    }
}

ดำเนินการยืนยันคีย์ด้วยตนเอง

วิธีที่ปลอดภัยที่สุดสำหรับผู้ใช้ในการยืนยันคีย์คือการยืนยันด้วยตนเอง ซึ่งมักจะทำโดยการสแกนคิวอาร์โค้ดหรือเปรียบเทียบลำดับตัวเลข แอป Key Verifier มีโฟลว์ UI มาตรฐานสำหรับกระบวนการนี้ ซึ่งแอปของคุณสามารถเปิดใช้ได้ หลังจากพยายามยืนยันแล้ว API จะอัปเดตสถานะการยืนยันของคีย์โดยอัตโนมัติ และแอปจะได้รับการแจ้งเตือนหากคุณสังเกตการอัปเดตคีย์

  • เริ่มกระบวนการยืนยันคีย์สำหรับรายชื่อติดต่อที่ผู้ใช้เลือก เปิด PendingIntent ที่ getScanQrCodeIntent ให้มาโดยใช้ lookupKey ของรายชื่อติดต่อที่เลือก UI ช่วยให้ผู้ใช้ยืนยันคีย์ทั้งหมด สำหรับรายชื่อติดต่อที่ระบุได้
import android.app.ActivityOptions
import android.app.PendingIntent
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun initiateVerification(contactKeyClient: ContactKeyClient, lookupKey: String) {
    try {
        val pendingIntent = Tasks.await(contactKeyClient.getScanQrCodeIntent(lookupKey))
        val options =
          ActivityOptions.makeBasic()
            .setPendingIntentBackgroundActivityStartMode(
              ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
            )
            .toBundle()
        pendingIntent.send(options)
    } catch (e: Exception) {
        // Handle error.
    }
}
  • เริ่มกระบวนการยืนยันคีย์สำหรับบัญชีที่ผู้ใช้เลือก หากผู้ใช้ต้องการยืนยันบัญชีที่ไม่ได้ลิงก์กับรายชื่อติดต่อโดยตรง (หรือบัญชีใดบัญชีหนึ่งของรายชื่อติดต่อ) คุณสามารถเปิด PendingIntent ที่ getScanQrCodeIntentForAccount ให้ไว้ได้ โดยปกติจะใช้สำหรับชื่อแพ็กเกจและรหัสบัญชีของแอปของคุณเอง
import android.app.ActivityOptions
import android.app.PendingIntent
import com.google.android.gms.contactkeys.ContactKeyClient
import com.google.android.gms.tasks.Tasks

suspend fun initiateVerification(contactKeyClient: ContactKeyClient, packageName: String, accountId: String) {
    try {
        val pendingIntent = Tasks.await(contactKeyClient.getScanQrCodeIntentForAccount(packageName, accountId))
        // Allow activity start from background on Android SDK34+
        val options =
          ActivityOptions.makeBasic()
            .setPendingIntentBackgroundActivityStartMode(
              ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
            )
            .toBundle()
        pendingIntent.send(options)
    } catch (e: Exception) {
        // Handle error.
    }
}