如何在Jetpack Compose中基于自定义SSL Socket Factory实现无第三方库的SSL公钥固定?
在Jetpack Compose中实现公钥固定的SSL Pinning(无第三方库)
公钥固定(SSL Pinning)的核心是将服务器的公钥哈希硬编码到应用中,在建立SSL连接时验证服务器返回的证书链中是否包含匹配的公钥,以此防止中间人攻击。以下是完全基于Android原生API、自定义SSLSocketFactory的实现方案:
1. 获取服务器公钥的SHA-256哈希值
首先需要提取目标服务器公钥的Base64编码SHA-256哈希,可通过OpenSSL命令行获取:
openssl s_client -connect your-server-domain.com:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
将输出的字符串保存下来,作为后续验证的可信哈希值(建议保留多个备用哈希,应对服务器证书更新)。
2. 实现自定义X509TrustManager
自定义信任管理器是验证公钥的核心,它会在SSL握手时检查服务器公钥是否匹配预设的哈希:
import java.security.MessageDigest import java.security.cert.X509Certificate import javax.net.ssl.X509TrustManager class PinningTrustManager(private val allowedPublicKeyHashes: List<String>) : X509TrustManager { override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) { // 若无需验证客户端证书,可空实现;有需求时在此添加客户端证书校验逻辑 } override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) { chain ?: throw IllegalArgumentException("Certificate chain cannot be null") // 遍历证书链,提取每个证书的公钥并计算哈希 for (certificate in chain) { val publicKeyBytes = certificate.publicKey.encoded val sha256Digest = MessageDigest.getInstance("SHA-256").digest(publicKeyBytes) val base64Hash = android.util.Base64.encodeToString(sha256Digest, android.util.Base64.NO_WRAP) // 匹配到可信哈希则验证通过 if (allowedPublicKeyHashes.contains(base64Hash)) { return } } // 无匹配公钥时抛出安全异常,终止连接 throw SecurityException("Server public key pinning failed: no trusted public key found") } override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray() }
3. 构建带Pinning的OkHttpClient
通过自定义的PinningTrustManager创建SSLSocketFactory,并封装到OkHttpClientFactory中供全局使用:
import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocketFactory import javax.net.ssl.TrustManager import okhttp3.OkHttpClient object OkHttpClientFactory { // 替换为你获取到的可信公钥哈希,支持多个哈希值 private val TRUSTED_PUBLIC_KEY_HASHES = listOf( "YOUR_TRUSTED_PUBLIC_KEY_HASH_1", "YOUR_TRUSTED_PUBLIC_KEY_HASH_2" ) fun createPinningOkHttpClient(): OkHttpClient { val trustManager = PinningTrustManager(TRUSTED_PUBLIC_KEY_HASHES) // 初始化TLS上下文,绑定自定义信任管理器 val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, arrayOf<TrustManager>(trustManager), java.security.SecureRandom()) val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory return OkHttpClient.Builder() .sslSocketFactory(sslSocketFactory, trustManager) // 保留默认主机名验证(无需自定义,除非有特殊业务需求) .build() } }
4. 在Jetpack Compose中集成使用
将带Pinning的OkHttpClient与Retrofit(或其他网络库)结合,在Compose界面中发起网络请求:
import androidx.compose.foundation.layout.Column import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET // 定义API接口 interface ApiService { @GET("your-api-endpoint") suspend fun fetchData(): YourResponseModel } // 全局Retrofit实例(建议使用DI框架管理,如Hilt) private val retrofit = Retrofit.Builder() .baseUrl("https://your-server-domain.com/") .client(OkHttpClientFactory.createPinningOkHttpClient()) .addConverterFactory(GsonConverterFactory.create()) .build() private val apiService = retrofit.create(ApiService::class.java) @Composable fun NetworkRequestScreen() { val responseText = remember { mutableStateOf<String?>(null) } val errorText = remember { mutableStateOf<String?>(null) } Column { Button(onClick = { LaunchedEffect(Unit) { try { val response = apiService.fetchData() responseText.value = "请求成功:${response.toString()}" } catch (e: Exception) { errorText.value = "请求失败:${e.message}" } } }) { Text("获取数据") } responseText.value?.let { Text(it) } errorText.value?.let { Text(it) } } }
关键注意事项
- 多哈希预留:服务器更新证书时,新的公钥哈希需提前加入
TRUSTED_PUBLIC_KEY_HASHES列表,避免应用突然无法连接。 - 验证测试:修改哈希值为无效值,确认连接时会抛出
SecurityException,确保固定逻辑生效。 - 客户端证书:若服务器要求验证客户端证书,在
checkClientTrusted方法中添加相应校验逻辑。 - TLS版本:确保SSLContext使用TLS 1.2及以上版本,提升安全性。
内容的提问来源于stack exchange,提问作者Nachiket Gohil
相关产品推荐
相关产品推荐

