配置 https 证书,如果证书过期,希望不影响访问,这个怎么做。

2025 年 10 月 10 日
 zhixiz1007

帮公司的安卓小哥来提个问题:之前项目是没有配置证书的,可以被抓包,现在为了防止抓包,就需要配置 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

/**

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

/**

// // 如果自定义证书有效,优先使用 // 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)}"
    }
}

}

8118 次点击
所在节点    Android
32 条回复
iyaozhen
2025 年 10 月 11 日
@huangsijun17 现在还有哪个服务是 http 的?
guanzhangzhang
2025 年 10 月 11 日
你需要的是双向 SSL ,同一个 ca 签署的证书链才行,中间人攻击不是同一套 ca 签署的直接中断
buonoye
2025 年 10 月 11 日
我前阵子的项目也是同样的问题,客户端想防抓包。如果配置证书固定,证书过期的话,旧包就有问题;如果配置公钥固定,证书续期可以设置保持公钥不变,但是后端用的是宝塔申请的免费 https 证书,不能设置公钥不变,后来负责人不想买付费证书,就不了了之,不做防抓包处理了...
wldr
2025 年 10 月 11 日
我之前做的方式是 证书是从服务器下载的,当业务请求抛出 ssl 异常就 查询并下载更新证书。
abolast
2025 年 10 月 11 日
凭什么认为加证书了就没办法被抓包了,可以抓包的,也不难。只能说安卓小哥对 http 协议不了解。
buonoye
2025 年 10 月 11 日
@abolast 不只是加个证书,还要配置为证书固定 certificatePinning
AItsuki
2025 年 10 月 11 日
ssl pinning 确实是最有效的方案,证书公钥 hash 通过 firebase 远程配置之类的东西维护就好了。我们面向国外的金融项目都是这么整的,部分国家会要求 app 提供安全审查报告和源代码,要求使用 ssl pinnging 等非常多的安全保证。
cwwx2022
2025 年 10 月 11 日
我的建议是闲鱼买个证书,非常便宜
duzhuo
2025 年 10 月 12 日
https 也不是不能抓包啊
JensenQian
2025 年 10 月 12 日
自动续签啊
现在这么多的工具啊
或者买个一年的证书啊
xjzshttps
2025 年 10 月 12 日
前面说了很多办法,固定 CA 机构、公钥、自签个长时间的。

另外还可以考虑,双步验证。
即,预留一个接口用来更新证书信息。
当 https 请求出现证书错误时,客户端就忽略证书问题访问这个接口获得最新的证书(当然这个接口返回值要增减公钥签名)。
之后的请求就使用这个最新证书。

这样灵活性会比较好些,更新证书 api 不止能固定一个证书,还可以一次返回多个证书、多个公钥、甚至多个 CA 机构。
但是要小心保护好这个 api resp 签名用的公钥。
vZexc0m
2025 年 10 月 13 日
@iyaozhen #21 微信

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://v2ex.ih06.com/t/1164208

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX