O Verificador de chaves para Android oferece uma maneira unificada e segura para os usuários verificarem se estão se comunicando com a pessoa certa no app criptografado de ponta a ponta (E2EE). Ele protege os usuários contra ataques man-in-the-middle permitindo que eles confirmem a autenticidade das chaves de criptografia públicas de um contato por uma interface do sistema confiável e consistente.
Esse recurso é fornecido pelo Verificador de chaves, um serviço do sistema que faz parte dos Serviços do sistema do Google e é distribuído usando a Play Store. Ele funciona como um repositório centralizado no dispositivo para chaves públicas E2EE.
Por que integrar com o Verificador de Chaves
- Ofereça uma UX unificada:em vez de criar seu próprio fluxo de verificação, inicie a interface padrão do sistema, oferecendo aos usuários uma experiência consistente e confiável em todos os apps.
- Aumentar a confiança do usuário:um status de verificação claro e apoiado pelo sistema garante aos usuários que as conversas deles são seguras e privadas.
- Reduzir a sobrecarga de desenvolvimento:descarregue a complexidade da interface do usuário de verificação de chaves, do armazenamento e do gerenciamento de status para o serviço do sistema.
Termos-chave
- lookupKey:um identificador opaco e persistente de um contato, armazenado na coluna LOOKUP_KEY do provedor de contatos. Ao contrário de um
contact ID
, umlookupKey
permanece estável mesmo que os detalhes de contato subjacentes sejam alterados ou mesclados, o que o torna a maneira recomendada de referenciar um contato. - accountId:um identificador específico do app para a conta de um usuário em um dispositivo. Esse ID é definido pelo seu app e ajuda a distinguir entre várias contas que um único usuário pode ter. Isso é mostrado ao usuário na interface. Recomendamos usar algo significativo, como um número de telefone, um endereço de e-mail ou um identificador de usuário.
- deviceId:um identificador exclusivo de um dispositivo específico associado à conta de um usuário. Isso permite que um usuário tenha vários dispositivos, cada um com seu próprio conjunto de chaves criptográficas. Não representa necessariamente um dispositivo físico, mas pode ser uma maneira de distinguir entre várias chaves usadas para a mesma conta.
Primeiros passos
Antes de começar, configure seu app para se comunicar com o serviço Key Verifier.
Declarar permissões:no AndroidManifest.xml, declare as seguintes permissões. Você também precisa pedir essas permissões ao usuário durante a execução.
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
Receba a instância do cliente:receba uma instância de ContactKeys
, que é seu
ponto de entrada para a API.
import com.google.android.gms.contactkeys.ContactKeys
val contactKeyClient = ContactKeys.getClient(context)
Orientações para desenvolvedores de apps de mensagens
Como desenvolvedor de apps de mensagens, sua principal função é publicar as chaves públicas dos usuários e dos contatos deles no serviço Key Verifier.
Publicar as chaves públicas de um usuário
Para permitir que outras pessoas encontrem e verifiquem seu usuário, publique a chave pública delas no repositório no dispositivo. Para mais segurança, crie chaves no 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.
}
}
Associar chaves públicas a contatos
Quando seu app recebe uma chave pública de um dos contatos do usuário, é necessário armazená-la e associá-la a esse contato no repositório central. Isso permite que a chave seja verificada e que outros apps mostrem o status de verificação do contato. Para fazer isso, você precisa da lookupKey do contato no provedor de contatos do Android. Isso geralmente é acionado ao buscar uma chave no servidor de distribuição de chaves ou durante uma sincronização periódica de chaves locais.
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.
}
}
Recuperar chaves e status de verificação
Depois de publicar as chaves, os usuários podem verificá-las lendo um QR code pessoalmente. A interface do usuário do app precisa refletir se uma conversa está usando uma chave verificada. Cada chave tem um status de verificação que pode ser usado para informar sua interface.
Entenda os estados de verificação:
UNVERIFIED
: esse é o estado padrão de todas as novas chaves. Isso significa que a chave existe, mas o usuário ainda não confirmou a autenticidade dela. Na sua interface, trate isso como um estado neutro e normalmente não mostre nenhum indicador especial.VERIFIED
: esse status indica um alto nível de confiança. Isso significa que o usuário concluiu um fluxo de verificação (como a leitura de um QR code) e confirmou que a chave pertence ao contato desejado. Na sua interface, mostre um indicador claro e positivo, como uma marca de seleção verde ou um escudo.VERIFICATION_FAILED
: este é um estado de aviso. Isso significa que a chave associada ao contato não corresponde à chave verificada anteriormente. Isso pode acontecer se um contato comprar um novo dispositivo, mas também pode indicar um possível risco de segurança. Na sua interface, alerte o usuário com um aviso em destaque e sugira que ele faça uma nova verificação antes de enviar informações sensíveis.
É possível recuperar um status agregado de todas as chaves associadas a um contato. Recomendamos usar VerificationState.leastVerifiedFrom()
para resolver o status quando várias chaves estão presentes, porque ele prioriza corretamente VERIFICATION_FAILED
em vez de VERIFIED
.
- Como acessar o status agregado no nível do contato
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.
}
}
- Como receber o status agregado no nível da conta
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.
}
}
Acompanhar as principais mudanças em tempo real
Para verificar se a interface do app sempre mostra o status de confiança correto, ouça as atualizações. A maneira recomendada é usar a API baseada em fluxo, que emite uma nova lista de chaves sempre que uma chave de uma conta inscrita é adicionada, removida ou tem o status de verificação alterado. Isso é especialmente útil para manter a lista de membros de uma conversa em grupo atualizada. O status de verificação de uma chave pode mudar quando:
- O usuário conclui um fluxo de verificação (por exemplo, leitura de QR code).
- A chave de um contato é modificada, fazendo com que ela não corresponda mais ao valor verificado anteriormente.
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)
}
}
}
Fazer a verificação de chaves pessoalmente
A maneira mais segura de verificar uma chave é pessoalmente, geralmente lendo um QR code ou comparando uma sequência de números. O app Key Verifier fornece fluxos de interface padrão para esse processo, que seu app pode iniciar. Após uma tentativa de verificação, a API atualiza automaticamente o status de verificação da chave, e seu app vai receber uma notificação se você estiver monitorando as atualizações da chave.
- Iniciar o processo de verificação de chaves para um contato selecionado pelo usuário
Inicie o
PendingIntent
fornecido porgetScanQrCodeIntent
usando olookupKey
do contato selecionado. A interface permite que o usuário verifique todas as chaves do contato em questão.
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.
}
}
- Iniciar o processo de verificação de chave para uma conta selecionada pelo usuário
Se o usuário quiser verificar uma conta não vinculada diretamente a um contato
(ou uma conta específica de um contato), inicie o
PendingIntent
fornecido porgetScanQrCodeIntentForAccount
. Normalmente, isso é usado para o nome do pacote e o ID da conta do seu próprio app.
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.
}
}