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
相关产品推荐
相关产品推荐

