הכלי Key Verifier ל-Android מספק למשתמשים דרך מאוחדת ומאובטחת לוודא שהם מתקשרים עם האדם הנכון באפליקציה עם הצפנה מקצה לקצה (E2EE). הוא מגן על המשתמשים מפני מתקפות מסוג 'אדם באמצע' בכך שהוא מאפשר להם לאמת את האותנטיות של מפתחות ההצפנה הציבוריים של איש או אשת הקשר באמצעות ממשק משתמש מהימן ועקבי של המערכת.
התכונה הזו מסופקת על ידי Key Verifier, שירות מערכת שפועל במסגרת Google System Services ומופץ דרך חנות Play. הוא משמש כמאגר מרכזי במכשיר של מפתחות ציבוריים להצפנה מקצה לקצה.
למה כדאי לשלב עם Key Verifier
- מספקים חוויית משתמש אחידה: במקום ליצור תהליך אימות משלכם, אתם יכולים להפעיל את ממשק המשתמש הרגיל של המערכת, וכך לספק למשתמשים חוויה עקבית ומהימנה בכל האפליקציות שלהם.
- הגברת תחושת הביטחון של המשתמשים: סטטוס אימות ברור שנתמך על ידי המערכת מבטיח למשתמשים שהשיחות שלהם מאובטחות ופרטיות.
- צמצום התקורות של הפיתוח: העברת המורכבות של ממשק המשתמש לאימות מפתחות, של האחסון ושל ניהול הסטטוס לשירות המערכת.
מונחי מפתח
- lookupKey: מזהה אטום וקבוע של איש קשר, שמאוחסן בעמודה LOOKUP_KEY של ספק אנשי הקשר. בניגוד ל
contact ID
,lookupKey
נשאר יציב גם אם פרטי איש הקשר הבסיסיים משתנים או מתמזגים, ולכן זו הדרך המומלצת להפניה לאיש קשר. - accountId: מזהה ספציפי לאפליקציה של חשבון משתמש במכשיר. המזהה הזה מוגדר על ידי האפליקציה ועוזר להבחין בין כמה חשבונות שיכולים להיות למשתמש יחיד. הערך הזה מוצג למשתמש בממשק המשתמש. מומלץ להשתמש בערך בעל משמעות כמו מספר טלפון, כתובת אימייל או שם משתמש.
- deviceId: מזהה ייחודי למכשיר ספציפי שמשויך לחשבון של משתמש. כך משתמש יכול להשתמש בכמה מכשירים, שלכל אחד מהם יש קבוצה משלו של מפתחות קריפטוגרפיים. לא בהכרח מייצג מכשיר פיזי, אבל יכול להיות דרך להבחין בין כמה מפתחות שמשמשים לאותו חשבון
תחילת העבודה
לפני שמתחילים, צריך להגדיר את האפליקציה כך שתתקשר עם שירות אימות המפתחות.
הצהרה על הרשאות: בקובץ 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)
הנחיות למפתחי אפליקציות להעברת הודעות
אם אתם מפתחים של אפליקציות להעברת הודעות, התפקיד העיקרי שלכם הוא לפרסם את המפתחות הציבוריים של המשתמשים ואת המפתחות של אנשי הקשר שלהם בשירות Key Verifier.
פרסום המפתחות הציבוריים של משתמש
כדי לאפשר לאחרים למצוא ולאמת את המשתמש שלכם, צריך לפרסם את המפתח הציבורי שלו במאגר שבמכשיר. כדי להוסיף שכבת אבטחה, כדאי ליצור מפתחות ב-Android Keystore.
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 Contacts Provider. הפעולה הזו מתבצעת בדרך כלל כשמאחזרים מפתח משרת הפצת המפתחות או במהלך סנכרון תקופתי של מפתחות מקומיים.
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.
}
}
אחזור מפתחות וסטטוס האימות
אחרי שמפרסמים מפתחות, המשתמשים יכולים לאמת אותם באמצעות סריקת קוד QR פנים אל פנים. ממשק המשתמש של האפליקציה צריך לשקף אם השיחה משתמשת במפתח מאומת. לכל מפתח יש סטטוס אימות שבו אפשר להשתמש כדי לעדכן את ממשק המשתמש.
הסבר על מצבי האימות:
UNVERIFIED
: זהו מצב ברירת המחדל של כל מפתח חדש. המשמעות היא שהמפתח קיים, אבל המשתמש עדיין לא אישר את האותנטיות שלו. בממשק המשתמש, צריך להתייחס לסטטוס הזה כאל סטטוס ניטרלי, ובדרך כלל לא להציג אינדיקטור מיוחד.
VERIFIED
: הסטטוס הזה מציין רמת מהימנות גבוהה. המשמעות היא שהמשתמש השלים בהצלחה תהליך אימות (כמו סריקה של קוד QR) ואישר שהמפתח שייך לאיש הקשר הרצוי. בממשק המשתמש, צריך להציג אינדיקטור ברור וחיובי, כמו סימן וי ירוק או מגן.
VERIFICATION_FAILED
: זהו מצב אזהרה. המשמעות היא שהמפתח שמשויך לאיש/אשת הקשר לא תואם למפתח שאומת בעבר. זה יכול לקרות אם לאיש קשר יש מכשיר חדש, אבל זה יכול גם להצביע על סיכון אבטחה פוטנציאלי. בממשק המשתמש, צריך להציג למשתמש אזהרה בולטת ולהציע לו לבצע אימות מחדש לפני שליחת מידע רגיש.
אפשר לאחזר סטטוס מצטבר של כל המפתחות שמשויכים לאיש קשר. מומלץ להשתמש ב-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.
}
}
מעקב אחרי שינויים חשובים בזמן אמת
כדי לוודא שממשק המשתמש של האפליקציה תמיד מציג את סטטוס האמון הנכון, צריך להאזין לעדכונים. הדרך המומלצת היא להשתמש ב-API מבוסס-הזרימה, שפולט רשימה חדשה של מפתחות בכל פעם שמתווסף מפתח לחשבון מנוי, מוסר מפתח או משתנה סטטוס האימות שלו. האפשרות הזו שימושית במיוחד כדי לשמור על רשימת המשתתפים של שיחה קבוצתית עדכנית. סטטוס האימות של מפתח יכול להשתנות במקרים הבאים:
- המשתמש משלים בהצלחה תהליך אימות (לדוגמה, סריקת קוד QR).
- המפתח של איש קשר משתנה, ולכן הוא כבר לא תואם לערך שאומת בעבר.
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)
}
}
}
אימות מפתחות פנים אל פנים
הדרך הכי מאובטחת לאמת מפתח היא באמצעות אימות פיזי, לרוב על ידי סריקת קוד QR או השוואה של רצף מספרים. אפליקציית Key Verifier מספקת תהליכי ממשק משתמש סטנדרטיים לתהליך הזה, שאפשר להפעיל באפליקציה שלכם. אחרי ניסיון אימות, ה-API מעדכן באופן אוטומטי את סטטוס האימות של המפתח, והאפליקציה תקבל הודעה אם אתם עוקבים אחרי עדכוני מפתח.
- התחלת תהליך אימות המפתחות לאיש קשר שנבחר על ידי המשתמש
מפעילים את
PendingIntent
שסופק על ידיgetScanQrCodeIntent
באמצעותlookupKey
של איש הקשר שנבחר. ממשק המשתמש מאפשר למשתמש לאמת את כל המפתחות של איש הקשר.
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.
}
}