Android-Systemschlüsselprüfer

Der Key Verifier für Android bietet Nutzern eine einheitliche und sichere Möglichkeit, zu überprüfen, ob sie in Ihrer App mit Ende‑zu‑Ende-Verschlüsselung (E2EE) mit der richtigen Person kommunizieren. Er schützt Nutzer vor Man-in-the-Middle-Angriffen, indem er ihnen ermöglicht, die Authentizität der öffentlichen Verschlüsselungsschlüssel eines Kontakts über eine vertrauenswürdige und einheitliche System-UI zu bestätigen.

Diese Funktion wird vom Key Verifier bereitgestellt, einem Systemdienst, der Teil der Google-Systemdienste ist und über den Play Store verteilt wird. Es dient als zentrales On-Device-Repository für öffentliche E2EE-Schlüssel.

Gründe für die Integration von Key Verifier

  • Einheitliche Benutzeroberfläche:Anstatt einen eigenen Bestätigungsablauf zu erstellen, können Sie die Standard-Benutzeroberfläche des Systems starten. So erhalten Nutzer eine einheitliche und vertrauenswürdige Umgebung in allen ihren Apps.
  • Nutzervertrauen stärken:Ein klarer, systemgestützter Bestätigungsstatus versichert Nutzern, dass ihre Unterhaltungen sicher und vertraulich sind.
  • Entwicklungsaufwand reduzieren:Die Komplexität der Benutzeroberfläche für die Schlüsselbestätigung, die Speicherung und die Statusverwaltung wird an den Systemdienst ausgelagert.

Wichtige Begriffe

  • lookupKey:Eine undurchsichtige, dauerhafte Kennung für einen Kontakt, die in der Spalte LOOKUP_KEY des Kontaktdatenanbieters gespeichert ist. Im Gegensatz zu einer contact ID bleibt eine lookupKey stabil, auch wenn die zugrunde liegenden Kontaktdaten geändert oder zusammengeführt werden. Daher ist sie die empfohlene Methode zum Verweisen auf einen Kontakt.
  • accountId:Eine app-spezifische Kennung für das Konto eines Nutzers auf einem Gerät. Diese ID wird von Ihrer App definiert und hilft dabei, zwischen mehreren Konten zu unterscheiden, die ein einzelner Nutzer möglicherweise hat. Diese wird dem Nutzer in der Benutzeroberfläche angezeigt. Es wird empfohlen, etwas Sinnvolles wie eine Telefonnummer, E-Mail-Adresse oder einen Nutzernamen zu verwenden.
  • deviceId:Eine eindeutige Kennung für ein bestimmtes Gerät, das mit dem Konto eines Nutzers verknüpft ist. So kann ein Nutzer mehrere Geräte mit jeweils eigenen kryptografischen Schlüsseln haben. Muss nicht unbedingt ein physisches Gerät sein, kann aber eine Möglichkeit sein, zwischen mehreren Schlüsseln zu unterscheiden, die für dasselbe Konto verwendet werden.

Erste Schritte

Bevor Sie beginnen, müssen Sie Ihre App für die Kommunikation mit dem Key Verifier Service einrichten.

Berechtigungen deklarieren:Deklarieren Sie in Ihrer AndroidManifest.xml-Datei die folgenden Berechtigungen. Sie müssen diese auch zur Laufzeit vom Nutzer anfordern.

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

Client-Instanz abrufen:Rufen Sie eine Instanz von ContactKeys ab. Dies ist Ihr Einstiegspunkt in die API.

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

val contactKeyClient = ContactKeys.getClient(context)

Hinweise für Entwickler von Messaging-Apps

Als Entwickler einer Messaging-App besteht Ihre Hauptaufgabe darin, die öffentlichen Schlüssel Ihrer Nutzer und die Schlüssel ihrer Kontakte im Key Verifier-Dienst zu veröffentlichen.

Öffentliche Schlüssel eines Nutzers veröffentlichen

Damit andere Ihren Nutzer finden und bestätigen können, müssen Sie den öffentlichen Schlüssel des Nutzers im On-Device-Repository veröffentlichen. Für zusätzliche Sicherheit können Sie Schlüssel im Android-Keystore erstellen.

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.
    }
}

Öffentliche Schlüssel mit Kontakten verknüpfen

Wenn Ihre App einen öffentlichen Schlüssel für einen der Kontakte des Nutzers empfängt, müssen Sie ihn im zentralen Repository speichern und dem entsprechenden Kontakt zuordnen. Dadurch kann der Schlüssel bestätigt werden und andere Apps können den Bestätigungsstatus für den Kontakt anzeigen. Dazu benötigen Sie den lookupKey des Kontakts aus dem Android Contacts Provider. Dies wird in der Regel beim Abrufen eines Schlüssels von Ihrem Schlüsselverteilungsserver oder bei einer regelmäßigen Synchronisierung lokaler Schlüssel ausgelöst.

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.
    }
}

Schlüssel und Bestätigungsstatus abrufen

Nachdem Sie Schlüssel veröffentlicht haben, können Nutzer sie durch Scannen eines QR‑Codes vor Ort überprüfen. Die Benutzeroberfläche Ihrer App sollte widerspiegeln, ob für eine Unterhaltung ein bestätigter Schlüssel verwendet wird. Jeder Schlüssel hat einen Bestätigungsstatus, den Sie für Ihre Benutzeroberfläche verwenden können.

Bestätigungsstatus:

  • UNVERIFIED: Dies ist der Standardstatus für jeden neuen Schlüssel. Das bedeutet, dass der Schlüssel vorhanden ist, der Nutzer seine Authentizität aber noch nicht bestätigt hat. In Ihrer Benutzeroberfläche sollten Sie dies als neutralen Status behandeln und in der Regel keinen speziellen Hinweis anzeigen.

  • VERIFIED: Dieser Status weist auf ein hohes Maß an Vertrauen hin. Das bedeutet, dass der Nutzer einen Bestätigungsvorgang (z. B. einen QR-Code-Scan) erfolgreich abgeschlossen und bestätigt hat, dass der Schlüssel zum vorgesehenen Kontakt gehört. In Ihrer Benutzeroberfläche sollten Sie einen eindeutigen, positiven Indikator wie ein grünes Häkchen oder Schild anzeigen.

  • VERIFICATION_FAILED: Dies ist ein Warnstatus. Das bedeutet, dass der Schlüssel, der mit dem Kontakt verknüpft ist, nicht mit dem zuvor bestätigten Schlüssel übereinstimmt. Das kann passieren, wenn ein Kontakt ein neues Gerät erhält, aber auch auf ein potenzielles Sicherheitsrisiko hinweisen. Weisen Sie den Nutzer in Ihrer Benutzeroberfläche mit einer deutlichen Warnung darauf hin und empfehlen Sie ihm, die Identität noch einmal zu bestätigen, bevor er vertrauliche Informationen sendet.

Sie können einen zusammengefassten Status für alle Schlüssel abrufen, die mit einem Kontakt verknüpft sind. Wir empfehlen, VerificationState.leastVerifiedFrom() zu verwenden, um den Status aufzulösen, wenn mehrere Schlüssel vorhanden sind, da VERIFICATION_FAILED dann korrekt vor VERIFIED priorisiert wird.

  • Aggregierten Status auf Kontaktdaten-Ebene abrufen
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.
    }
}
  • Zusammengefassten Status auf Kontoebene abrufen
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.
    }
}

Wichtige Änderungen in Echtzeit beobachten

Damit in der Benutzeroberfläche Ihrer App immer der richtige Vertrauensstatus angezeigt wird, sollten Sie auf Updates achten. Die empfohlene Methode ist die Flow-basierte API, die eine neue Liste von Schlüsseln ausgibt, wenn ein Schlüssel für ein abonniertes Konto hinzugefügt oder entfernt wird oder sich der Bestätigungsstatus ändert. Das ist besonders nützlich, um die Mitgliederliste einer Gruppenunterhaltung auf dem neuesten Stand zu halten. Der Bestätigungsstatus eines Schlüssels kann sich ändern, wenn:

  • Der Nutzer schließt einen Bestätigungsvorgang erfolgreich ab (z. B. QR-Code-Scan).
  • Der Schlüssel eines Kontakts wird geändert, sodass er nicht mehr mit dem zuvor bestätigten Wert übereinstimmt.
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)
            }
    }
}

Schlüsselbestätigung vor Ort durchführen

Die sicherste Methode für Nutzer, einen Schlüssel zu bestätigen, ist die persönliche Bestätigung, oft durch Scannen eines QR‑Codes oder Vergleichen einer Zahlenfolge. Die Key Verifier-App bietet Standard-UI-Abläufe für diesen Prozess, die Ihre App starten kann. Nach einem Überprüfungsversuch wird der Überprüfungsstatus des Schlüssels automatisch aktualisiert und Ihre App wird benachrichtigt, wenn Sie Schlüsselaktualisierungen beobachten.

  • Schlüsselbestätigung für einen vom Nutzer ausgewählten Kontakt starten Starten Sie die von getScanQrCodeIntent bereitgestellte PendingIntent mit der lookupKey des ausgewählten Kontakts. In der Benutzeroberfläche kann der Nutzer alle Schlüssel für den angegebenen Kontakt überprüfen.
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.
    }
}
  • Schlüsselbestätigung für ein vom Nutzer ausgewähltes Konto starten Wenn der Nutzer ein Konto bestätigen möchte, das nicht direkt mit einem Kontakt verknüpft ist (oder ein bestimmtes Konto eines Kontakts), können Sie die von getScanQrCodeIntentForAccount bereitgestellte PendingIntent starten. Dies wird in der Regel für den Paketnamen und die Konto-ID Ihrer eigenen App verwendet.
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.
    }
}