You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android原生应用Ktor客户端基于公钥的SSL固定实现指导

Android原生应用中Ktor客户端实现公钥SSL固定

问题背景

需要实现以下流程:

  • 从登录API获取公钥并存储到偏好设置
  • 配置Ktor客户端,每次网络请求时验证服务器公钥,防范中间人攻击

常见问题排查

你之前尝试配置sslManager但代码未执行,大概率是因为Ktor Android引擎的sslManager仅在特定场景下触发,更可靠的方式是直接自定义SSLSocketFactory和HostnameVerifier并绑定到引擎配置中。

完整实现步骤

1. 从偏好设置读取已存储的公钥哈希

假设你已将公钥的SHA-256哈希(格式如sha256/xxxxxxxx=)存储到SharedPreferences中:

// 从SharedPreferences获取固定的公钥哈希
fun getPinnedPublicKeyHash(context: Context): String {
    val prefs = context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)
    return prefs.getString("pinned_public_key", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") ?: ""
}

2. 实现公钥验证逻辑

创建自定义的X509TrustManager来验证服务器证书的公钥哈希,同时配合HostnameVerifier确保主机名匹配:

import java.security.MessageDigest
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLException
import javax.net.ssl.X509TrustManager
import android.util.Base64

class PinnedPublicKeyTrustManager(private val pinnedHash: String) : X509TrustManager {

    override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        // 客户端证书验证,按需实现(通常不需要)
    }

    override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        chain ?: throw SSLException("服务器证书链为空")
        
        // 获取证书链中的第一个证书(服务器端证书)
        val serverCert = chain[0]
        // 提取公钥并计算SHA-256哈希
        val publicKey = serverCert.publicKey
        val publicKeyBytes = publicKey.encoded
        val sha256Digest = MessageDigest.getInstance("SHA-256").digest(publicKeyBytes)
        val serverPublicKeyHash = "sha256/${Base64.encodeToString(sha256Digest, Base64.NO_WRAP)}"

        // 对比固定的哈希值
        if (!serverPublicKeyHash.equals(pinnedHash, ignoreCase = true)) {
            throw SSLException("服务器公钥不匹配,疑似中间人攻击")
        }
    }

    override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
}

// 自定义主机名验证器
class PinnedHostnameVerifier(private val allowedHost: String) : HostnameVerifier {
    override fun verify(hostname: String?, session: SSLSession?): Boolean {
        return hostname.equals(allowedHost, ignoreCase = true)
    }
}

3. 配置Ktor客户端

将自定义的信任管理器和主机名验证器绑定到Ktor的Android引擎中:

import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json

fun createKtorClient(context: Context): HttpClient {
    val pinnedPublicKeyHash = getPinnedPublicKeyHash(context)
    val pinnedHost = "qual.abc.com"

    // 创建SSL上下文
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(
        null,
        arrayOf(PinnedPublicKeyTrustManager(pinnedPublicKeyHash)),
        SecureRandom()
    )

    return HttpClient(Android) {
        expectSuccess = true

        install(ContentNegotiation) {
            json(Json {
                prettyPrint = true
                isLenient = true
                ignoreUnknownKeys = true
                explicitNulls = false
            })
        }

        engine {
            // 绑定自定义SSLSocketFactory和HostnameVerifier
            sslSocketFactory = sslContext.socketFactory
            hostnameVerifier = PinnedHostnameVerifier(pinnedHost)
        }
    }
}

关键说明

  • 公钥哈希格式:必须以sha256/开头,后面是公钥SHA-256摘要的Base64编码值,确保和你从登录API获取并存储的格式一致。
  • 异常处理:验证失败时会抛出SSLException,你可以在Ktor请求时通过catch块捕获并处理(如提示用户网络风险、终止请求)。
  • 兼容性:该实现适配Android API 21+,如果需要兼容更低版本,需额外处理SSL上下文的创建逻辑。

内容的提问来源于stack exchange,提问作者arun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:30:57