Key Verifier для Android предоставляет пользователям унифицированный и безопасный способ убедиться, что они общаются с нужным человеком в вашем приложении со сквозным шифрованием (E2EE). Он защищает пользователей от атак типа «человек посередине», позволяя им подтверждать подлинность открытых ключей шифрования контакта через надежный и согласованный системный интерфейс.
Эта функция предоставляется Key Verifier — системной службой, входящей в состав системных служб 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
, который является вашей точкой входа в API.
import com.google.android.gms.contactkeys.ContactKeys
val contactKeyClient = ContactKeys.getClient(context)
Руководство для разработчиков приложений обмена сообщениями
Как разработчик приложения для обмена сообщениями, ваша основная задача — публиковать открытые ключи ваших пользователей и ключи их контактов в сервисе Key Verifier.
Опубликовать открытые ключи пользователя
Чтобы другие могли найти и проверить вашу учетную запись, опубликуйте их открытый ключ в репозитории на устройстве. Для дополнительной безопасности рекомендуем создать ключи в хранилище ключей 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.
}
}
Получить ключи и статус проверки
После публикации ключей пользователи смогут проверить их, отсканировав 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 на основе Flow, который генерирует новый список ключей при каждом добавлении, удалении или изменении статуса проверки ключа для подписанной учётной записи. Это особенно полезно для поддержания актуальности списка участников группового чата. Статус проверки ключа может измениться в следующих случаях:
- Пользователь успешно завершает процесс проверки (например, сканирование 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.
}
}