跳转至

eID Android SDK —— 集成指南

面向开发者的指南:如何在 Android 应用中加入 eID(Gerege Smart-ID)的注册(enroll)、 身份认证与电子签名功能。SDK 与 Go 后端(server/)通信,并在手机端执行分布式密钥生成与 2-of-2 门限 ECDSA。其密码学内核是独立的 Kotlin 实现,通过 golden 向量与 Go 服务端的 internal/crypto 保持逐字节兼容

包名: mn.eidmongolia.smartid · 客户端类: GeregeSmartIdClient · 模块: :android-sdk(Android library,.aar)。

1. 概览

SDK 通过单一的 GeregeSmartIdClient 提供以下高层操作:

操作 函数 说明
注册 enroll(...) KYC 授权 + 分布式密钥生成 → 2 份证书(签名 PIN2、身份认证 PIN1)
批准会话 approve(...) 使用门限 ECDSA 对经推送 / 二维码到达的会话进行签名确认
校验 PIN verifyPIN(...) / pinUnlocksSlot(...) 用 PIN 解锁并校验已保存的身份(不访问服务器)
修改 PIN changePIN(...) 用旧 PIN 解锁后以新 PIN 重新加密
KYC danInit/danStatus/danLivenessgsignInit/gsignVerifypassportInit DAN / G-Sign / 外国护照核验
控制台 pendingSign/pendingSession/recentActivity/sessionInfo 待处理请求 + 近期活动

每位公民 2 份证书。 enroll 会执行两次密钥生成:身份认证(PIN1、登录)与 签名(PIN2、具备法律效力的签名)。私钥永远不会在任何一处完整存在 —— 一半在手机上,一半在服务器上。

2. 前置条件

  • minSdk 24targetSdk 35、JDK 17、Kotlin 1.9.24(SDK 构建:compileSdk 34)。
  • Play Integrity 认证 —— 每个请求都会基于服务端的 nonce 获取一枚新令牌。 应用的 applicationId 与 Google Cloud 项目(cloudProjectNumber)必须已在服务端注册。
  • 后端 URL —— 默认 https://rp-api.eidmongolia.mn(可配置,见下文)。

(!)Play Integrity 在模拟器上不可用。 请以 SMARTID_REQUIRE_ATTESTATION=false 运行 Go 服务端(dev 默认值)—— 应用将在无认证的情况下继续运行。在模拟器 / 无 Play Services 的环境中,SDK 会优雅地捕获 AttestationUnavailableException 并跳过认证;在真机上, 认证错误(篡改 / 网络)按 fail-closed 处理(请求被取消)。

3. 安装

SDK 以 :android-sdk Gradle 模块形式引入(未发布为 Maven 坐标 —— 而是作为仓库内的相邻模块, 或以 .aar 形式使用)。示例应用在 settings.gradle.kts 中将其作为相邻模块引入:

// eIDMongolia/settings.gradle.kts
include(":app")
include(":android-sdk")
project(":android-sdk").projectDir = file("../android-sdk")   // android/android-sdk
// app/build.gradle.kts
dependencies {
    implementation(project(":android-sdk"))
    // ...
}

SDK 自身依赖以下组件(会传递引入):com.google.android.play:integrityandroidx.security:security-crypto(EncryptedSharedPreferences)、com.squareup.okhttp3:okhttpkotlinx-coroutines-android + -play-services

生物识别(应用侧必需)。 为授权需要用户认证的 Keystore 密钥,应用需要 BiometricPrompt(FragmentActivity),因此请在应用中加入生物识别依赖:

// app/build.gradle.kts
implementation("androidx.fragment:fragment-ktx:1.8.2")
implementation("androidx.biometric:biometric:1.1.0")   // BiometricPrompt 需要 FragmentActivity

应用的 MainActivity 必须是 FragmentActivity(或 AppCompatActivity)—— 这是 BiometricPrompt 的要求。

4. 配置 —— 后端 URL

应用会在构建期将后端基础 URL 固化进 BuildConfig.GEREGE_BACKEND_URL。 其取值来自 gradle 属性 gerege.backendUrl,为空时回退到默认值:

// app/build.gradle.kts  (defaultConfig)
val backendUrl = (project.findProperty("gerege.backendUrl") as String?)
    ?.takeIf { it.isNotBlank() } ?: "https://rp-api.eidmongolia.mn"
buildConfigField("String", "GEREGE_BACKEND_URL", "\"$backendUrl\"")

两种配置方式:

# 1) 命令行属性:
./gradlew :app:assembleDebug -Pgerege.backendUrl=https://<go-backend>

# 2) 或写入 eIDMongolia/gradle.properties:
#    gerege.backendUrl=https://smartid-staging.example.mn

在应用中通过 AppConfig 读取:

object AppConfig {
    val backendUrl: String get() = BuildConfig.GEREGE_BACKEND_URL
    const val cloudProjectNumber: Long = 399766783708L   // Google Cloud 项目(Play Integrity)
}

若要连接本地 Go 服务端(http),需要在 network_security_config 中允许明文流量 (局域网测试)。在公网主机上,SDK 会将 TLS 固定到 Let's Encrypt 根证书; 对 localhost/.local/私有 IP 会自动跳过固定。

5. 基本用法

5.1 创建客户端

val client = GeregeSmartIdClient(
    context = this,                            // Context(Activity/Application)
    baseUrl = AppConfig.backendUrl,            // BuildConfig.GEREGE_BACKEND_URL
    cloudProjectNumber = AppConfig.cloudProjectNumber,  // Play Integrity
    account = "default",                       // (可选)keystore 槽位名称
)

5.2 生物识别认证器(必需)

approve / verifyPIN / changePIN 等加载类操作需要 BiometricAuthenticator 来解锁 Keystore 中要求用户认证的密钥。SDK 不依赖任何 UI 框架 —— 由应用通过 BiometricPrompt 进行桥接。Compose 中有辅助函数:

val auth = rememberBiometricAuthenticator()   // eIDMongolia 的 etalon/core/BiometricAuth.kt

BiometricAuthenticator 的契约(SDK 接口):

interface BiometricAuthenticator {
    suspend fun authenticate(cipher: Cipher, reason: String): Cipher
}

用户取消 / 生物识别失败 → 抛出异常(fail-closed):解封与签名随即中止, 不存在静默绕过(与 iOS Secure Enclave 的 .userPresence 保持一致)。

5.3 注册(enroll)

enroll 使用 KYC 授权(danAuthCode)+ PIN 执行分布式密钥生成,获取证书并保存在手机上。 若提供 authPin,则会创建两把独立的门限密钥以支持 双证书配置(签名 + 身份认证):

suspend fun enroll(
    danAuthCode: String,
    pin: CharArray,                        // PIN2 —— 签名(不可否认)
    pushToken: String,                     // FCM 令牌
    authPin: CharArray? = null,            // PIN1 —— 身份认证(提供则为双证书)
    reviewLatin: (suspend (LatinNameProposal) -> Pair<String, String>?)? = null,
    progress: ProgressHandler? = null,
): String   // → documentNumber
val documentNumber = client.enroll(
    danAuthCode = danCode,
    pin = "1234".toCharArray(),            // PIN2(签名)
    pushToken = fcmToken,
    authPin = "5678".toCharArray(),        // PIN1(身份认证)
)

enroll 内部会校验服务端的密钥生成证明(防 rogue-key)与密钥绑定 (证书 ↔ 联合公钥 Q = qClient+qServer)—— 若服务端返回的是对应其他密钥的证书, 则在保存之前直接拒绝。

预热(可选)。 在打开注册页面时调用 client.prewarmEnroll(), 可提前在后台完成开销较大的 Paillier 密钥生成,从而缩短 enroll 的等待时间。

5.4 身份认证与签名(approve)

使用 PIN 批准经推送 / 二维码到达的 sessionId。若 authentication = true, 则使用 auth 密钥(PIN1、登录);否则使用 sign 密钥(PIN2、签名)

suspend fun approve(
    sessionId: String,
    pin: CharArray,
    auth: BiometricAuthenticator,
    authentication: Boolean = false,       // true → PIN1/auth;false → PIN2/sign
    confirmVc: String? = null,             // 用户确认的验证码(WYSIWYS)
    progress: ProgressHandler? = null,
): String   // → 状态("OK" 等)
// 身份认证(登录):
val status = client.approve(sessionId, "5678".toCharArray(), auth, authentication = true)

// 签名(sign):
val status = client.approve(sessionId, "1234".toCharArray(), auth)

approve 内部,手机会执行 3 轮门限 ECDSA(commit → prove → finish),并在把最终签名 发送到服务器之前,独立用 ECDSA 校验其与会话消息(SIGN:摘要;AUTH: ACSP_V2(rpChallenge, SPKI))是否匹配(WYSIWYS / transcript 绑定)。若不匹配,则不产出签名。

5.5 校验 / 修改 PIN

suspend fun verifyPIN(slot: PINSlot, pin: CharArray, auth: BiometricAuthenticator): Boolean
suspend fun changePIN(slot: PINSlot, oldPin: CharArray, newPin: CharArray, auth: BiometricAuthenticator)

// slot: GeregeSmartIdClient.PINSlot.AUTH(PIN1)| .SIGN(PIN2)

检查本地状态(无需服务器):hasRegistration(): BooleandeleteRegistration()

5.6 KYC(简述)

在获取 enroll 所需的 danAuthCode 之前,需通过 SDK 完成 KYC:

val (state, verifyUrl) = client.danInit(registrationNumber)   // DAN 核验 URL
val verified = client.danStatus(state)                        // 轮询
val (passed, score) = client.danLiveness(state, selfieBytes)  // 人脸比对

此外还有 gsignInit/gsignVerify(G-Sign)与 passportInit(外国护照)等变体 —— 详见源码。

6. 密钥存储与生物识别

SDK 使用 PBKDF2(PIN)+AES-GCM 加密身份数据(xClient + Paillier 私钥 + 证书), 保存在 EncryptedSharedPreferences 中,并额外用 Android Keystore 中 要求用户认证(生物识别)的密钥进行封装(以阻断离线 PIN 暴力破解, 与 iOS Secure Enclave 保持一致)。PIN 与 x_client 绝不会离开设备发送到服务器。

深入的技术细节 —— 密钥存储、RSA-OAEP 封装 / 解封、v1→v2 迁移、 setInvalidatedByBiometricEnrollment、StrongBox 回退 —— 请见单独文档: Android 生物识别密钥

7. 构建 / 运行示例应用

前置条件:已安装 Android SDK 并配置 ANDROID_HOME;如需 FCM 推送,还需真实的 Firebase 项目文件 app/google-services.json(示例文件为 app/google-services.json.example)。

cd android/eIDMongolia
# 如需 FCM 推送(可选):替换为真实的 Firebase 文件
cp app/google-services.json.example app/google-services.json

# Debug APK —— 通过属性传入后端 URL
./gradlew :app:assembleDebug -Pgerege.backendUrl=https://<go-backend>
# → app/build/outputs/apk/debug/app-debug.apk

或在 Android Studio 中打开并运行 eIDMongolia。若要单独构建 SDK 库: cd android/android-sdk && gradle assembleReleasebuild/outputs/aar/...-release.aar

缺少 google-services.json 不会导致构建失败 —— 应用仍可编译并运行 (FCM 推送被禁用;Play Integrity 与 enroll/approve 无需 google-services.json 即可工作)。

8. 应用所使用的 Go 后端接口

均对应 Go 服务端的 internal/httpapi(与 iOS 相同):

  • GET /v3/attestation/challenge —— 认证 nonce
  • POST /v3/enrollment/init · /v3/enrollment/complete
  • GET /v3/mobile/session/{id} · /v3/mobile/pending/{doc} · /v3/mobile/activity/{doc}
  • POST /v3/mobile/session/{id}/sign/{commit,prove,finish} —— 2-of-2 门限 ECDSA(3 轮)
  • KYC:/v3/kyc/dan/*/v3/kyc/gsign/*

服务端会在响应中返回 X-Next-Attestation-Nonce 头(SDK 的 nonce 缓存优化)。

9. 链接(源码)

  • Android SDK 客户端: https://github.com/gerege-systems/eid-platform-mn/blob/main/android/android-sdk/src/main/kotlin/mn/eidmongolia/smartid/GeregeSmartIdClient.kt
  • Keystore 存储: https://github.com/gerege-systems/eid-platform-mn/blob/main/android/android-sdk/src/main/kotlin/mn/eidmongolia/smartid/AndroidSecureKeyStore.kt
  • 生物识别认证器接口: https://github.com/gerege-systems/eid-platform-mn/blob/main/android/android-sdk/src/main/kotlin/mn/eidmongolia/smartid/BiometricAuthenticator.kt
  • 示例应用入口: https://github.com/gerege-systems/eid-platform-mn/blob/main/android/eIDMongolia/app/src/main/kotlin/mn/eidmongolia/example/MainActivity.kt
  • Android README(构建 / 运行): https://github.com/gerege-systems/eid-platform-mn/blob/main/android/README.md
  • SDK README(Kotlin 架构): https://github.com/gerege-systems/eid-platform-mn/blob/main/android/android-sdk/README.md
  • 生物识别密钥(深入技术细节):ANDROID_BIOMETRIC_KEYS.md