توفّر خدمة Key Verifier على Android طريقة موحّدة وآمنة للمستخدمين للتحقّق من أنّهم يتواصلون مع الشخص الصحيح في تطبيقك المشفّر بشكل تام بين الأطراف. وتوفّر هذه الخدمة الحماية للمستخدمين من هجمات الوسيط من خلال السماح لهم بالتأكّد من صحة مفاتيح التشفير العامة الخاصة بجهة الاتصال من خلال واجهة مستخدم موثوقة ومتسقة تابعة للنظام.
توفّر خدمة التحقّق من المفتاح هذه الميزة، وهي إحدى خدمات النظام التي تشكّل جزءًا من "خدمات نظام Google" ويتم توزيعها باستخدام "متجر Play". وهو بمثابة مستودع مركزي على الجهاز للمفاتيح العامة الخاصة بالتشفير التام بين الأطراف.
أسباب دمج Key Verifier
- توفير تجربة مستخدم موحّدة: بدلاً من إنشاء مسار التحقّق الخاص بك، يمكنك تشغيل واجهة المستخدم العادية للنظام، ما يمنح المستخدمين تجربة متّسقة وجديرة بالثقة في جميع تطبيقاتهم.
- تعزيز ثقة المستخدمين: تضمن حالة التحقّق الواضحة التي يدعمها النظام للمستخدمين أنّ محادثاتهم آمنة وخاصة.
- تقليل تكاليف التطوير: يمكنك تفويض خدمة النظام بالتعامل مع تعقيدات واجهة المستخدم والوحدات التخزينية وإدارة الحالة الخاصة بالتحقّق من المفتاح.
العبارات الرئيسية
- lookupKey: معرّف مبهم وثابت لجهة اتصال، ويتم تخزينه في العمود LOOKUP_KEY في موفّر جهات الاتصال. على عكس
contact ID
، يظلlookupKey
ثابتًا حتى إذا تم تغيير تفاصيل جهة الاتصال الأساسية أو دمجها، ما يجعله الطريقة المُوصى بها للإشارة إلى جهة اتصال. - accountId: معرّف خاص بالتطبيق لحساب المستخدم على أحد الأجهزة. يتم تحديد رقم التعريف هذا من خلال تطبيقك ويساعد في التمييز بين الحسابات المتعددة التي قد يملكها مستخدم واحد. يظهر هذا المعرّف للمستخدم في واجهة المستخدم، ويُنصح باستخدام معرّف ذي معنى، مثل رقم الهاتف أو عنوان البريد الإلكتروني أو اسم المستخدم.
- deviceId: معرّف فريد لجهاز معيّن مرتبط بحساب أحد المستخدمين. يتيح ذلك للمستخدم امتلاك أجهزة متعددة، ولكل منها مجموعة مفاتيح تشفير خاصة به. لا يمثّل هذا المعرّف جهازًا فعليًا بالضرورة، ولكن يمكن استخدامه للتمييز بين مفاتيح متعددة مستخدَمة للحساب نفسه.
خطوات البدء:
قبل البدء، عليك إعداد تطبيقك للتواصل مع خدمة Key Verifier.
تضمين الأذونات في نموذج البيان: في ملف AndroidManifest.xml، ضمِّن الأذونات التالية. ويجب أيضًا طلب هذه الأذونات من المستخدم في وقت التشغيل.
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
الحصول على مثيل العميل: احصل على مثيل من ContactKeys
، وهو نقطة الدخول إلى واجهة برمجة التطبيقات.
import com.google.android.gms.contactkeys.ContactKeys
val contactKeyClient = ContactKeys.getClient(context)
إرشادات لمطوّري تطبيقات المراسلة
بصفتك مطوّر تطبيق مراسلة، يتمثّل دورك الأساسي في نشر المفاتيح العامة للمستخدمين ومفاتيح جهات الاتصال الخاصة بهم في خدمة "التحقّق من المفاتيح".
نشر المفاتيح العامة للمستخدم
للسماح للمستخدمين الآخرين بالعثور على حسابك والتحقّق منه، عليك نشر مفتاحك العام في المستودع على الجهاز. لمزيد من الأمان، ننصحك بإنشاء المفاتيح في 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. يحدث ذلك عادةً عند استرداد مفتاح من خادم توزيع المفاتيح أو أثناء المزامنة الدورية للمفاتيح المحلية.
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.
}
}
استرداد المفاتيح وحالة التحقّق
بعد نشر المفاتيح، يمكن للمستخدمين التحقّق منها من خلال مسح رمز الاستجابة السريعة ضوئيًا شخصيًا. يجب أن توضّح واجهة مستخدم تطبيقك ما إذا كانت المحادثة تستخدم مفتاحًا تم التحقّق منه. لكل مفتاح حالة تحقّق يمكنك استخدامها لإعلام واجهة المستخدم.
فهم حالات التحقّق:
UNVERIFIED
: هذه هي الحالة التلقائية لكل مفتاح جديد. وهذا يعني أنّ المفتاح متوفّر، ولكن لم يؤكّد المستخدم صحته بعد. في واجهة المستخدم، يجب التعامل مع هذه الحالة على أنّها حالة محايدة، ولا يتم عادةً عرض أي مؤشر خاص.VERIFIED
: تشير هذه الحالة إلى مستوى عالٍ من الثقة. ويعني ذلك أنّ المستخدم أكمل بنجاح عملية التحقّق (مثل مسح رمز استجابة سريعة) و أكّد أنّ المفتاح يخص جهة الاتصال المقصودة. في واجهة المستخدم، عليك عرض مؤشر إيجابي وواضح، مثل علامة اختيار خضراء أو درع.VERIFICATION_FAILED
: هذه حالة تحذير. هذا يعني أنّ المفتاح المرتبط بجهة الاتصال لا يتطابق مع المفتاح الذي تم التحقّق منه سابقًا. قد يحدث ذلك إذا حصلت جهة اتصال على جهاز جديد، ولكن قد يشير أيضًا إلى خطر أمني محتمل. في واجهة المستخدم، نبِّه المستخدم من خلال تحذير بارز واقترح عليه إعادة إثبات الملكية قبل إرسال معلومات حساسة.
يمكنك استرداد حالة مجمّعة لجميع المفاتيح المرتبطة بجهة اتصال. ننصحك باستخدام VerificationState.leastVerifiedFrom()
لحلّ حالة VERIFICATION_FAILED
عند توفّر مفاتيح متعددة، لأنّها ستعطي الأولوية بشكل صحيح لـ 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.
}
}
مراقبة التغييرات الرئيسية في الوقت الفعلي
للتأكّد من أنّ واجهة مستخدم تطبيقك تعرض دائمًا حالة الثقة الصحيحة، عليك الاستماع إلى التحديثات. الطريقة المقترَحة هي استخدام واجهة برمجة التطبيقات المستندة إلى التدفق، والتي تعرض قائمة جديدة بالمفاتيح كلما تمت إضافة مفتاح لحساب مشترك أو تمت إزالته أو تم تغيير حالة التحقّق منه. ويفيد ذلك بشكل خاص في إبقاء قائمة أعضاء المحادثة الجماعية محدّثة. يمكن أن تتغيّر حالة التحقّق من المفتاح في الحالات التالية:
- يُكمل المستخدم عملية التحقّق بنجاح (على سبيل المثال، مسح رمز الاستجابة السريعة).
- تم تعديل مفتاح جهة اتصال، ما أدّى إلى عدم تطابقه مع القيمة التي تم التحقّق منها سابقًا.
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 عمليات عادية لواجهة المستخدم لهذه العملية، ويمكن لتطبيقك تشغيلها. بعد محاولة إثبات الملكية، تعدّل واجهة برمجة التطبيقات تلقائيًا حالة إثبات الملكية للمفتاح، وسيتم إشعار تطبيقك إذا كنت تراقب تحديثات المفتاح.
- بدء عملية التحقّق من المفتاح لجهة اتصال يختارها المستخدم
ابدأ
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.
}
}