Android용 키 인증 도구는 사용자가 엔드 투 엔드 암호화 (E2EE) 앱에서 올바른 상대와 소통하고 있는지 확인할 수 있는 통합되고 안전한 방법을 제공합니다. 사용자가 신뢰할 수 있고 일관된 시스템 UI를 통해 연락처의 공개 암호화 키의 진위 여부를 확인할 수 있도록 하여 중간자 공격으로부터 사용자를 보호합니다.
이 기능은 Google 시스템 서비스의 일부이며 Play 스토어를 사용하여 배포되는 시스템 서비스인 키 확인자에 의해 제공됩니다. E2EE 공개 키의 중앙 집중식 온디바이스 저장소 역할을 합니다.
Key Verifier와 통합해야 하는 이유
- 통합 UX 제공: 자체 인증 흐름을 빌드하는 대신 시스템의 표준 UI를 실행하여 모든 앱에서 사용자에게 일관되고 신뢰할 수 있는 환경을 제공할 수 있습니다.
- 사용자 신뢰도 향상: 명확하고 시스템에서 지원하는 인증 상태는 사용자가 대화가 안전하고 비공개임을 확신할 수 있도록 합니다.
- 개발 오버헤드 감소: 키 확인 UI, 저장소, 상태 관리의 복잡성을 시스템 서비스로 오프로드합니다.
주요 용어
- lookupKey: 연락처의 불투명한 영구 식별자로, 연락처 제공자의 LOOKUP_KEY 열에 저장됩니다.
contact ID
와 달리lookupKey
는 기본 연락처 세부정보가 변경되거나 병합되더라도 안정적으로 유지되므로 연락처를 참조하는 데 권장되는 방법입니다. - accountId: 기기에서 사용자 계정의 앱별 식별자입니다. 이 ID는 앱에서 정의하며 단일 사용자가 보유할 수 있는 여러 계정을 구분하는 데 도움이 됩니다. 이는 UI에 사용자에게 표시되므로 전화번호, 이메일 주소, 사용자 핸들과 같이 의미 있는 것을 사용하는 것이 좋습니다.
- deviceId: 사용자 계정과 연결된 특정 기기의 고유 식별자입니다. 이를 통해 사용자는 각각 자체 암호화 키 집합이 있는 여러 기기를 사용할 수 있습니다. 실제 기기를 나타내지 않을 수도 있지만 동일한 계정에 사용되는 여러 키를 구분하는 방법일 수 있습니다.
시작하기
시작하기 전에 키 확인자 서비스와 통신하도록 앱을 설정하세요.
권한 선언: AndroidManifest.xml에서 다음 권한을 선언합니다. 런타임에 사용자에게도 요청해야 합니다.
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
클라이언트 인스턴스 가져오기: API의 진입점인 ContactKeys
의 인스턴스를 가져옵니다.
import com.google.android.gms.contactkeys.ContactKeys
val contactKeyClient = ContactKeys.getClient(context)
메시지 앱 개발자를 위한 안내
메시지 앱 개발자의 기본 역할은 사용자의 공개 키와 연락처의 키를 키 확인 도구 서비스에 게시하는 것입니다.
사용자의 공개 키 게시
다른 사용자가 사용자를 찾고 확인할 수 있도록 하려면 온디바이스 저장소에 공개 키를 게시하세요. 보안을 강화하려면 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.
}
}
공개 키를 연락처와 연결
앱이 사용자 연락처 중 하나의 공개 키를 수신하면 중앙 저장소에 저장하고 해당 연락처와 연결해야 합니다. 이렇게 하면 키를 인증할 수 있고 다른 앱에서 연락처의 인증 상태를 표시할 수 있습니다. 이렇게 하려면 Android 연락처 제공자의 연락처 lookupKey가 필요합니다. 이는 일반적으로 키 배포 서버에서 키를 가져오거나 로컬 키를 주기적으로 동기화하는 중에 트리거됩니다.
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 코드 스캔을 통해 키를 확인할 수 있습니다. 앱의 UI는 대화에서 인증된 키를 사용하는지 여부를 반영해야 합니다. 각 키에는 UI에 알리는 데 사용할 수 있는 인증 상태가 있습니다.
인증 상태 이해하기:
UNVERIFIED
: 모든 새 키의 기본 상태입니다. 키가 존재하지만 사용자가 아직 진위 여부를 확인하지 않았음을 의미합니다. UI에서 이를 중립 상태로 취급해야 하며 일반적으로 특수 표시기를 표시하지 않습니다.VERIFIED
: 이 상태는 높은 수준의 신뢰를 나타냅니다. 이는 사용자가 인증 흐름 (예: QR 코드 스캔)을 성공적으로 완료하고 키가 의도한 연락처에 속하는지 확인했음을 의미합니다. UI에 녹색 체크표시나 방패와 같은 명확하고 긍정적인 표시기를 표시해야 합니다.VERIFICATION_FAILED
: 경고 상태입니다. 연락처와 연결된 키가 이전에 인증된 키와 일치하지 않음을 의미합니다. 연락처가 새 기기를 사용하는 경우에도 이 문제가 발생할 수 있지만 잠재적인 보안 위험을 나타낼 수도 있습니다. UI에서 눈에 띄는 경고로 사용자에게 알리고 민감한 정보를 보내기 전에 다시 인증하도록 제안합니다.
연락처와 연결된 모든 키의 집계 상태를 가져올 수 있습니다. 여러 키가 있는 경우 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.
}
}
주요 변경사항을 실시간으로 확인
앱의 UI가 항상 올바른 신뢰 상태를 표시하는지 확인하려면 업데이트를 수신해야 합니다. 구독 계정의 키가 추가, 삭제되거나 인증 상태가 변경될 때마다 새 키 목록을 내보내는 흐름 기반 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 코드를 스캔하거나 숫자 시퀀스를 비교하는 방식으로 이루어집니다. 키 검증 도구 앱은 이 프로세스를 위한 표준 UI 흐름을 제공하며 앱에서 이를 실행할 수 있습니다. 인증 시도 후 API는 키의 인증 상태를 자동으로 업데이트하며, 키 업데이트를 관찰하는 경우 앱에 알림이 전송됩니다.
- 사용자가 선택한 연락처의 키 인증 프로세스 시작
선택한 연락처의
lookupKey
를 사용하여getScanQrCodeIntent
에서 제공한PendingIntent
를 실행합니다. UI를 통해 사용자는 지정된 연락처의 모든 키를 확인할 수 있습니다.
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.
}
}
- 사용자가 선택한 계정의 키 확인 프로세스 시작
사용자가 연락처에 직접 연결되지 않은 계정(또는 연락처의 특정 계정)을 확인하려는 경우
getScanQrCodeIntentForAccount
에서 제공하는PendingIntent
를 실행하면 됩니다. 일반적으로 자체 앱의 패키지 이름과 계정 ID에 사용됩니다.
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.
}
}