帮公司的安卓小哥来提个问题:之前项目是没有配置证书的,可以被抓包,现在为了防止抓包,就需要配置 https 证书。首先静态配置证书是属于写到 network_security_config_app.xml 这个证书,直接配置到网络认证文件里,这个种情况有个不好就是如果哪天证书过期了,那就无法通过网络认证了,但是不希望如此,希望如果过期了那就忽略认证,所以需要动态配置 ssl 不直接配置到文件中,通过代码去认证,先去判断证书有效,如果无效或者不能找到证书直接走信任证书,如果有效那就走证书认证,目前没配置的表现就是可以被抓包。请问大家,有过这方面的经验吗? GPT 代码帮忙解决了,但是因为是知识盲区想请教一下诸位。 package com.wlld.common.network
import android.content.Context import android.util.Log import com.wlld.common.utils.LogUtils import okhttp3.OkHttpClient import javax.net.ssl.SSLContext import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager import java.security.SecureRandom
/**
OkHttp SSL 配置工具类
提供动态证书校验支持 */ object SSLConfigUtil {
private const val TAG = "SSLConfigUtil" private var dynamicTrustManager: DynamicSSLTrustManager? = null
/**
配置 OkHttp 使用动态 SSL 证书校验 */ fun OkHttpClient.Builder.configureDynamicSSL(context: Context): OkHttpClient.Builder { LogUtils.d(TAG, "🔧 开始配置动态 SSL...") return try { // 创建动态信任管理器 dynamicTrustManager = DynamicSSLTrustManager.create(context) LogUtils.d(TAG, "✅ 动态信任管理器创建成功")
// 创建 SSL 上下文
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf<TrustManager>(dynamicTrustManager!!), SecureRandom())
LogUtils.d(TAG, "✅ SSL 上下文初始化完成")
// 配置 SSL Socket Factory
sslSocketFactory(sslContext.socketFactory, dynamicTrustManager!!)
LogUtils.d(TAG, "✅ SSL Socket Factory 配置完成")
LogUtils.d(TAG, "🎉 动态 SSL 配置应用成功!")
this
} catch (e: Exception) { Log.e(TAG, "❌ 动态 SSL 配置失败", e) // 降级到不安全的配置(仅用于紧急情况) configureUnsafeSSL() } }
/**
配置不安全的 SSL (信任所有证书)
仅在紧急情况下使用 */ private fun OkHttpClient.Builder.configureUnsafeSSL(): OkHttpClient.Builder { Log.w(TAG, "Using unsafe SSL configuration - trusts all certificates") return try { val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager { override fun checkClientTrusted(chain: Array<out java.security.cert.X509Certificate>, authType: String) {} override fun checkServerTrusted(chain: Array<out java.security.cert.X509Certificate>, authType: String) {} override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf() })
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, SecureRandom())
sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
hostnameVerifier { _, _ -> true }
this
} catch (e: Exception) { Log.e(TAG, "Failed to configure unsafe SSL", e) this } }
/**
/**
/**
/**
/**
/**
/**
package com.wlld.common.network
import android.content.Context import android.util.Log import com.wlld.common.R import java.io.ByteArrayInputStream import java.io.InputStream import java.security.KeyStore import java.security.KeyStoreException import java.security.NoSuchAlgorithmException import java.security.PrivateKey import java.security.cert.Certificate import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.util.Date import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509TrustManager
/**
动态 HTTPS 证书校验工具类
当证书过期时跳过校验,确保应用正常运行 */ class DynamicSSLTrustManager( private val context: Context ) : X509TrustManager {
private val defaultTrustManager: X509TrustManager private val customCertificates: MutableList<X509Certificate> = mutableListOf() private var skipValidation = false
companion object { private const val TAG = "DynamicSSLTrustManager"
// 检查证书是否过期
fun isCertificateExpired(certificate: X509Certificate): Boolean {
return try {
val expirationDate = certificate.notAfter
expirationDate.before(Date())
} catch (e: Exception) {
Log.e(TAG, "证书过期检查出错", e)
true // 如果无法检查,默认认为过期
}
}
// 创建动态信任管理器
fun create(context: Context): DynamicSSLTrustManager {
return DynamicSSLTrustManager(context)
}
}
init { Log.d(TAG, "🚀 DynamicSSLTrustManager 初始化开始")
// 初始化默认信任管理器
defaultTrustManager = createDefaultTrustManager()
Log.d(TAG, "✅ 默认信任管理器初始化完成")
// 加载自定义证书
loadCustomCertificates()
Log.d(TAG, "🎯 DynamicSSLTrustManager 初始化完成 - 跳过校验模式: $skipValidation, 自定义证书数量: ${customCertificates.size}")
}
private fun createDefaultTrustManager(): X509TrustManager { return try { val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) trustManagerFactory.init(null as KeyStore?) val trustManagers = trustManagerFactory.trustManagers trustManagers.firstOrNull { it is X509TrustManager } as? X509TrustManager ?: throw IllegalStateException("Default X509TrustManager not found") } catch (e: Exception) { Log.e(TAG, "Failed to create default trust manager", e) throw RuntimeException("Failed to initialize SSL trust manager", e) } }
private fun loadCustomCertificates() { try { // 加载 assets 中的证书文件 loadCertificateFromAssets("weilaiqiyuan.cer")
// 可以继续加载其他证书
// loadCertificateFromAssets("other_certificate.cer")
Log.d(TAG, "已加载 ${customCertificates.size} 个自定义证书")
// 如果没有找到任何证书,启用跳过校验模式
if (customCertificates.isEmpty()) {
Log.w(TAG, "未找到任何自定义证书,启用跳过校验模式")
skipValidation = true
}
} catch (e: Exception) {
Log.e(TAG, "加载自定义证书失败,启用跳过校验模式", e)
skipValidation = true
}
}
private fun loadCertificateFromAssets(fileName: String) { try { context.assets.open(fileName).use { inputStream -> val certificate = loadCertificateFromStream(inputStream) if (certificate != null) { customCertificates.add(certificate) Log.d(TAG, "已加载证书: $fileName, 过期时间: ${certificate.notAfter}")
// 检查证书是否过期
if (isCertificateExpired(certificate)) {
Log.w(TAG, "证书 $fileName 已过期,启用跳过校验模式")
skipValidation = true
}
}
}
} catch (e: Exception) {
Log.e(TAG, "从 assets 加载证书失败: $fileName", e)
}
}
private fun loadCertificateFromStream(inputStream: InputStream): X509Certificate? { return try { val certificateFactory = CertificateFactory.getInstance("X.509") certificateFactory.generateCertificate(inputStream) as X509Certificate } catch (e: Exception) { Log.e(TAG, "解析证书失败", e) null } }
/**
动态添加证书 */ fun addCertificate(certificate: X509Certificate) { customCertificates.add(certificate) Log.d(TAG, "已添加新证书, 过期时间: ${certificate.notAfter}")
// 检查新添加的证书是否过期 if (isCertificateExpired(certificate)) { Log.w(TAG, "新添加的证书已过期,启用跳过校验模式") skipValidation = true } }
/**
/**
/**
/**
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) { // if (skipValidation) { // Log.w(TAG, "⚠️ 跳过客户端证书校验(证书过期或未找到)- 这可能导致抓包风险!") // return // } // // // 先检查自定义证书 // if (validateCustomCertificates(chain)) { // Log.d(TAG, "使用有效的自定义证书") // return // } // // // 使用默认校验 // try { // defaultTrustManager.checkClientTrusted(chain, authType) // } catch (e: CertificateException) { // Log.w(TAG, "默认客户端证书校验失败", e) // // // 检查是否因为过期导致的失败 // if (!checkCertificatesValidity(chain as Array<X509Certificate>)) { // Log.w(TAG, "客户端证书已过期,启用跳过校验模式") // skipValidation = true // return // } // // throw e // } }
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) { Log.d(TAG, "🔍 checkServerTrusted 被调用 - 验证服务器证书,authType: $authType") if (skipValidation) { Log.i(TAG, "跳过服务端证书校验(证书过期或未找到)") return }
// 先检查自定义证书
if (validateCustomCertificates(chain)) {
return
}
// 使用默认校验
try {
defaultTrustManager.checkServerTrusted(chain, authType)
} catch (e: CertificateException) {
Log.w(TAG, "默认服务端证书校验失败", e)
// 检查是否因为过期导致的失败
if (!checkCertificatesValidity(chain as Array<X509Certificate>)) {
Log.w(TAG, "服务端证书已过期,启用跳过校验模式")
skipValidation = true
return
}
throw e
}
}
override fun getAcceptedIssuers(): Array<X509Certificate> { return if (skipValidation) { // 跳过校验时返回空数组,接受所有证书 emptyArray() } else { // 返回默认接受的证书颁发机构 defaultTrustManager.acceptedIssuers + customCertificates.toTypedArray() } }
/**
// // 如果自定义证书有效,优先使用 // for (customCert in customCertificates) { // if (!isCertificateExpired(customCert)) { // // 检查自定义证书是否覆盖 weilaiqiyuan.com 域名 // val subjectCN = extractCommonName(customCert.subjectDN.toString()) // if (subjectCN.contains("*.weilaiqiyuan.com") || subjectCN.contains("weilaiqiyuan.com")) { // Log.d(TAG, "使用有效的自定义证书验证 weilaiqiyuan.com 域名: $subjectCN") // return true // } // } // }
// 检查链中的证书是否与自定义证书匹配
for (cert in chain) {
for (customCert in customCertificates) {
if (cert.subjectDN == customCert.subjectDN) {
// 找到匹配的自定义证书
if (!isCertificateExpired(customCert)) {
// 🔍 严格验证证书:比较公钥指纹,防止证书伪造
if (verifyCertificateFingerprint(cert, customCert)) {
Log.d(TAG, "✅ 证书验证通过(指纹匹配): ${cert.subjectDN}")
return true
} else {
Log.w(TAG, "❌ 证书验证失败(指纹不匹配)- 可能是抓包攻击!")
// 指纹不匹配,拒绝连接
throw CertificateException("证书指纹验证失败,可能存在中间人攻击")
}
} else {
Log.w(TAG, "自定义证书已过期: ${cert.subjectDN}")
return false
}
}
}
}
return false
}
/**
* 验证证书指纹,防止证书伪造
*/
private fun verifyCertificateFingerprint(cert1: X509Certificate, cert2: X509Certificate): Boolean {
return try {
// 比较公钥的 SHA-256 指纹
val pubkey1 = cert1.publicKey.encoded
val pubkey2 = cert2.publicKey.encoded
val digest1 = java.security.MessageDigest.getInstance("SHA-256").digest(pubkey1)
val digest2 = java.security.MessageDigest.getInstance("SHA-256").digest(pubkey2)
val fingerprint1 = digest1.joinToString("") { "%02X".format(it) }
val fingerprint2 = digest2.joinToString("") { "%02X".format(it) }
val isValid = fingerprint1 == fingerprint2
Log.d(TAG, "证书指纹对比: $fingerprint1 vs $fingerprint2, 匹配: $isValid")
isValid
} catch (e: Exception) {
Log.e(TAG, "证书指纹验证失败", e)
false
}
}
/**
* 从 SubjectDN 中提取 Common Name
*/
private fun extractCommonName(subjectDN: String): String {
val cnPattern = "CN=([^,]+)".toRegex()
val match = cnPattern.find(subjectDN)
return match?.groupValues?.get(1) ?: ""
}
/**
* 获取自定义证书信息
*/
fun getCertificateInfo(): List<String> {
return customCertificates.map { cert ->
"Subject: ${cert.subjectDN}, Issuer: ${cert.issuerDN}, Expires: ${cert.notAfter}, Expired: ${isCertificateExpired(cert)}"
}
}
}
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.