Appearance
06_证书与加密
目录
HTTPS 证书原理
1.1 什么是数字证书
数字证书(Digital Certificate)是一种电子"身份证",用于证明某个公钥的所有权。它由可信的第三方机构(CA)签发,包含以下信息:
┌──────────────────────┐
│ 数字证书结构 │
├──────────────────────┤
│ 版本 │
│ 序列号 │
│ 签名算法 │
│ 颁发者 (CA) │
│ 有效期 │
│ 主体 (域名/IP) │
│ 公钥 │
│ 扩展信息 │
│ CA 数字签名 │
└──────────────────────┘1.2 证书的作用
三大核心作用:
- 身份认证
证书证明:这个公钥确实属于 www.example.com- 加密通信
客户端使用证书中的公钥加密数据
只有服务器能用私钥解密- 数据完整性
数字签名确保数据未被篡改1.3 证书格式
X.509 标准:
X.509 v3 是数字证书的国际标准
包含:
- 版本信息
- 序列号
- 签名算法标识符
- 颁发者名称
- 有效期
- 主体名称
- 主体公钥信息
- 颁发者唯一标识符
- 主体唯一标识符
- 扩展项
- 签名算法
- 签名值证书文件类型:
.cer / .crt - 证书文件(Base64 编码或二进制)
.pem - 证书文件(Base64 编码,包含 BEGIN/END 标记)
.der - 证书文件(二进制 DER 编码)
.key - 私钥文件
.p12 / .pfx - PKCS#12 格式(证书 + 私钥)1.4 证书链
完整的证书链:
┌─────────────────────────┐
│ 终端实体证书 │
│ (www.example.com) │
├─────────────────────────┤
│ 中间证书 │
│ (Intermediate CA) │
├─────────────────────────┤
│ 根证书 │
│ (Root CA) │
└─────────────────────────┘验证流程:
1. 服务器发送证书链
2. 客户端验证终端证书的签名(使用中间证书)
3. 客户端验证中间证书的签名(使用根证书)
4. 根证书在客户端信任库中,验证通过1.5 Android 证书存储
系统信任库:
系统根证书位置:
/system/etc/security/cacerts/
用户添加证书:
设置 -> 安全 -> 加密与凭据 -> 安装证书查看证书:
kotlin
fun printCertificates(url: String) {
val ss = SocketFactory.getDefault().createSocket() as SSLSocket
ss.startHandshake()
val certificates = ss.session.peerCertificates
for (cert in certificates) {
println("Subject: ${cert.subjectDN}")
println("Issuer: ${cert.issuerDN}")
println("Valid From: ${cert.notBefore}")
println("Valid To: ${cert.notAfter}")
println("---")
}
}证书颁发机构
2.1 什么是 CA
CA(Certificate Authority,证书颁发机构)是受信任的第三方组织,负责:
- 验证申请者的身份
- 签发数字证书
- 管理证书生命周期
- 证书吊销
2.2 主流 CA 机构
全球知名 CA:
DigiCert - 收购了 Symantec、Comodo
GlobalSign
Sectigo (原 Comodo)
Let's Encrypt (免费)
SSL.com国内 CA:
DigiCert 中国
GlobalSign 中国
CFCA (中国金融认证中心)2.3 证书类型
DV (Domain Validated):
- 仅验证域名所有权
- 签发快速(分钟级)
- 价格便宜
- 显示:HTTPS 锁标志OV (Organization Validated):
- 验证域名 + 组织信息
- 签发时间:几天
- 价格中等
- 显示:组织信息EV (Extended Validation):
- 严格验证组织合法性
- 签发时间:1-2 周
- 价格昂贵
- 显示:绿色地址栏(部分浏览器)2.4 Let's Encrypt
免费自动化证书:
优势:
- 免费
- 自动化签发
- 90 天有效期
- ACME 协议自动化
适用场景:
- 个人项目
- 开发测试
- 预算有限的商业项目签发流程:
1. 客户端向 ACME 服务器发送请求
2. ACME 发送挑战(HTTP/TXT)
3. 服务器验证域名所有权
4. ACME 签发证书
5. 证书有效期 90 天2.5 证书吊销
吊销原因:
- 私钥泄露
- 信息变更
- 证书误发
- 服务停止吊销检查方式:
- CRL (Certificate Revocation List)
CA 定期发布吊销列表
客户端下载并检查
缺点:列表可能很大,更新不及时- OCSP (Online Certificate Status Protocol)
客户端实时询问 CA 证书状态
响应:good / revoked / unknown
缺点:增加延迟,隐私问题- OCSP Stapling
服务器定期获取 OCSP 响应
在 TLS 握手时附带发送
优点:更快,保护隐私证书验证
3.1 验证流程
完整的证书验证:
┌─────────────────────────────────┐
│ 1. 证书链完整性检查 │
│ - 是否有完整的证书链 │
│ - 终端证书 -> 中间 -> 根 │
├─────────────────────────────────┤
│ 2. 签名验证 │
│ - 使用上级证书的公钥验证 │
│ - 逐级验证直到根证书 │
├─────────────────────────────────┤
│ 3. 有效期检查 │
│ - 当前时间在有效期内 │
├─────────────────────────────────┤
│ 4. 域名匹配 │
│ - CN 或 SAN 包含目标域名 │
├─────────────────────────────────┤
│ 5. 吊销状态检查 │
│ - CRL 或 OCSP │
├─────────────────────────────────┤
│ 6. 根证书信任 │
│ - 根证书在信任库中 │
└─────────────────────────────────┘3.2 域名验证
CN (Common Name):
cn = www.example.comSAN (Subject Alternative Name):
DNS: example.com
DNS: www.example.com
DNS: api.example.com
IP: 192.168.1.1验证规则:
1. 精确匹配
2. 通配符匹配:*.example.com 匹配 api.example.com
3. 通配符不匹配多级:*.example.com 不匹配 api.test.example.com3.3 Android 证书验证
自动验证(推荐):
kotlin
val client = OkHttpClient()
// 自动验证证书链、有效期、域名等验证结果处理:
kotlin
try {
val response = client.newCall(request).execute()
// 验证通过
} catch (e: SSLHandshakeException) {
// 验证失败
when (e.cause) {
is CertificateExpiredException -> {
// 证书过期
}
is CertificateNotYetValidException -> {
// 证书未生效
}
is UnknownCAException -> {
// 颁发者未知
}
is CertificateUnknownException -> {
// 证书未知
}
}
}3.4 自定义证书验证
验证回调:
kotlin
class CustomTrustManager(
private val customValidator: (List<X509Certificate>) -> Boolean
) : X509TrustManager {
private val defaultTrustManager: X509TrustManager
init {
val factory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
factory.init(null)
defaultTrustManager = factory.trustManagers
.firstOrNull { it is X509TrustManager } as X509TrustManager
}
override fun checkServerTrusted(
chain: Array<out X509Certificate>,
authType: String
) {
// 先执行默认验证
defaultTrustManager.checkServerTrusted(chain, authType)
// 再执行自定义验证
if (!customValidator(chain.toList())) {
throw SecurityException("Custom validation failed")
}
}
override fun checkClientTrusted(
chain: Array<out X509Certificate>,
authType: String
) {
defaultTrustManager.checkClientTrusted(chain, authType)
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return defaultTrustManager.acceptedIssuers
}
}SSL Pinning
4.1 什么是 SSL Pinning
SSL Pinning(证书锁定)是将证书或公钥"锁定"在应用程序中,只接受特定的证书,不接受 CA 签发的其他证书。
传统 HTTPS:
客户端信任所有受信任 CA 签发的证书SSL Pinning:
客户端只接受特定的证书/公钥
即使 CA 签发,不匹配也拒绝4.2 为什么要使用 SSL Pinning
防止中间人攻击:
场景:
1. 用户安装了恶意证书
2. 网络被劫持(恶意 WiFi)
3. 国家级的网络审查
SSL Pinning 即使有有效证书,也会拒绝非绑定的证书增强安全性:
- 防止证书颁发机构被攻破
- 防止用户误装恶意证书
- 防止企业监控工具4.3 SSL Pinning 实现方式
方式 1:公钥锁定(推荐)
kotlin
class PublicKeyPinningInterceptor : Interceptor {
private val pinnedPublicKeyHash = "sha256/xxxxxxxxxxxxxx"
override fun intercept(chain: Interceptor.Chain): Response {
val socket = chain.connection().socket() as SSLSocket
val session = socket.session
val certificates = session.peerCertificates
val publicKey = certificates[0].publicKey
val hash = computePublicKeyHash(publicKey)
if (hash != pinnedPublicKeyHash) {
throw SecurityException("Public key not pinned")
}
return chain.proceed(chain.request())
}
private fun computePublicKeyHash(key: PublicKey): String {
val sha256 = MessageDigest.getInstance("SHA-256")
val hash = sha256.digest(key.encoded)
return "sha256/" + Base64.encodeToString(hash, Base64.NO_WRAP)
}
}方式 2:证书锁定
kotlin
class CertificatePinningInterceptor : Interceptor {
private val pinnedCertificateHash = "sha256/yyyyyyyyyyyyyy"
override fun intercept(chain: Interceptor.Chain): Response {
val socket = chain.connection().socket() as SSLSocket
val certificates = socket.session.peerCertificates
val hash = computeCertificateHash(certificates[0])
if (hash != pinnedCertificateHash) {
throw SecurityException("Certificate not pinned")
}
return chain.proceed(chain.request())
}
private fun computeCertificateHash(cert: X509Certificate): String {
val sha256 = MessageDigest.getInstance("SHA-256")
val hash = sha256.digest(cert.encoded)
return "sha256/" + Base64.encodeToString(hash, Base64.NO_WRAP)
}
}4.4 OkHttp 内置 SSL Pinning
使用 CertificatePinner:
kotlin
val certificatePinner = CertificatePinner.Builder()
// 主域名及其备份
.add("api.example.com",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
)
// 通配符域名
.add("*.example.com",
"sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC="
)
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()备份证书:
建议至少添加 2 个哈希值:
1. 当前证书的哈希
2. 备用证书的哈希(证书轮换时使用)4.5 获取证书指纹
从浏览器获取:
Chrome:
1. 访问 https://example.com
2. 点击地址栏的锁图标
3. 连接是安全的 -> 证书有效
4. 按 Ctrl+I 或 Cmd+I 查看证书详情
5. 复制到命令行使用 openssl
命令行获取:
openssl s_client -connect example.com:443 -showcerts
openssl x509 -pubkey -noout -in cert.pem | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | base64从 Android 应用获取:
kotlin
fun getCertificateHash(url: String): String {
val urlObj = URL(url)
val hostname = urlObj.host
val port = urlObj.port ?: 443
val sslSocket = SSLSocketFactory.getDefault()
.createSocket(hostname, port) as SSLSocket
try {
sslSocket.startHandshake()
val certificates = sslSocket.session.peerCertificates
for (cert in certificates) {
val publicKey = cert.publicKey
val sha256 = MessageDigest.getInstance("SHA-256")
val hash = sha256.digest(publicKey.encoded)
val encodedHash = Base64.encodeToString(hash, Base64.NO_WRAP)
Log.d("CertPin", "sha256/$encodedHash")
}
} finally {
sslSocket.close()
}
return ""
}4.6 证书轮换策略
问题:证书会过期,需要轮换
解决方案:
kotlin
val certificatePinner = CertificatePinner.Builder()
// 当前证书
.add("api.example.com",
"sha256/CURRENT_CERT_HASH"
)
// 备用证书(下一个周期)
.add("api.example.com",
"sha256/BACKUP_CERT_HASH"
)
// 紧急备用
.add("api.example.com",
"sha256/EMERGENCY_CERT_HASH"
)
.build()轮换计划:
1. 新证书签发前,添加到备用列表
2. 证书到期前切换
3. 旧证书哈希保留一段时间
4. 逐步移除旧证书哈希防止中间人攻击
5.1 什么是中间人攻击
攻击流程:
用户 ────> 恶意设备 ────> 服务器
用户 <──── 恶意设备 <──── 服务器
恶意设备可以:
- 查看通信内容
- 修改通信内容
- 伪造响应攻击场景:
1. 恶意 WiFi(咖啡厅、机场)
2. 公共网络
3. 企业网络监控
4. 恶意应用安装证书
5. 国家级的网络审查5.2 防御措施
综合防御策略:
┌─────────────────────────────┐
│ 1. HTTPS (基础) │
│ - 加密通信 │
│ - 证书验证 │
├─────────────────────────────┤
│ 2. SSL Pinning (核心) │
│ - 公钥锁定 │
│ - 证书锁定 │
├─────────────────────────────┤
│ 3. 证书透明 (补充) │
│ - CT Logs │
│ - 证书监控 │
├─────────────────────────────┤
│ 4. 双向认证 (高安全) │
│ - 客户端证书 │
│ - mTLS │
├─────────────────────────────┤
│ 5. 应用完整性 (防御卸载) │
│ - 检测 Root │
│ - 检测调试 │
└─────────────────────────────┘5.3 检测 Root 设备
检测方案:
kotlin
fun isDeviceRooted(): Boolean {
// 1. 检查 su 二进制文件
val buildTags = Build.TAGS
if (buildTags != null && buildTags.contains("testkey")) {
return true
}
// 2. 检查常见 Root 应用
val rootApps = arrayOf(
"su", "Superuser", "SuperSU", "MagiskManager"
)
for (app in rootApps) {
try {
PackageManager.getPackageInfo(app, 0)
return true
} catch (e: PackageManager.NameNotFoundException) {
// 未安装
}
}
// 3. 检查常见 Root 路径
val rootPaths = arrayOf(
"/system/app/Superuser.apk",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/system/sbin/su",
"/sbin/su",
"/system/bin/freeroot",
"/data/local/su",
"/system/sd/su",
"/system/sbin/freeroot"
)
for (path in rootPaths) {
if (File(path).exists()) {
return true
}
}
return false
}5.4 检测调试环境
防调试检测:
kotlin
fun isDebugging(): Boolean {
// 检查是否被调试
return Debugger.isDebuggerConnected()
}
fun isAppDebuggable(): Boolean {
return (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
}
// 检测 Frida
fun isFridaDetected(): Boolean {
// 检查 Frida 端口
try {
val socket = Socket("127.0.0.1", 27042)
socket.close()
return true
} catch (e: Exception) {
return false
}
}
// 检测 Xposed
fun isXposedDetected(): Boolean {
// 检查 Xposed 特征
try {
val classLoader = Class.forName("de.robv.android.xposed.XposedBridge")
return true
} catch (e: ClassNotFoundException) {
return false
}
}5.5 代码混淆和加固
ProGuard/R8 配置:
pro
# 保持网络相关类
-keep class com.example.network.** { *; }
-keepclassmembers class com.example.network.** { *; }
# 保持加密相关类
-keep class javax.crypto.** { *; }
-keep class org.bouncycastle.** { *; }
# 保持 SSL Pinning
-keep class okhttp3.CertificatePinner { *; }加固方案:
1. 代码混淆
2. 反调试
3. 完整性校验
4. 运行时保护
5. 敏感数据加密存储数据加密
6.1 对称加密(AES)
AES 加密:
kotlin
object AESCrypt {
private const val ALGORITHM = "AES"
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
private const val KEY_SIZE = 256
// 生成密钥
fun generateKey(): SecretKey {
val keyGenerator = KeyGenerator.getInstance(ALGORITHM)
keyGenerator.init(KEY_SIZE)
return keyGenerator.generateKey()
}
// 加密
fun encrypt(plaintext: String, key: SecretKey): String {
val cipher = Cipher.getInstance(TRANSFORMATION)
// 生成 IV
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
val encrypted = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8))
// IV + 密文
val result = iv + encrypted
return Base64.encodeToString(result, Base64.NO_WRAP)
}
// 解密
fun decrypt(ciphertext: String, key: SecretKey): String {
val data = Base64.decode(ciphertext, Base64.NO_WRAP)
// 提取 IV
val iv = ByteArray(16)
System.arraycopy(data, 0, iv, 0, 16)
val ivSpec = IvParameterSpec(iv)
val cipherText = ByteArray(data.size - 16)
System.arraycopy(data, 16, cipherText, 0, cipherText.size)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)
val decrypted = cipher.doFinal(cipherText)
return String(decrypted, Charsets.UTF_8)
}
}6.2 非对称加密(RSA)
RSA 加密:
kotlin
object RSACrypt {
private const val ALGORITHM = "RSA"
private const val TRANSFORMATION = "RSA/ECB/PKCS1Padding"
private const val KEY_SIZE = 2048
// 生成密钥对
fun generateKeyPair(): KeyPair {
val keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM)
keyPairGenerator.initialize(KEY_SIZE)
return keyPairGenerator.generateKeyPair()
}
// 公钥加密
fun encrypt(publicKey: PublicKey, plaintext: String): String {
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val encrypted = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8))
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
}
// 私钥解密
fun decrypt(privateKey: PrivateKey, ciphertext: String): String {
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, privateKey)
val decoded = Base64.decode(ciphertext, Base64.NO_WRAP)
val decrypted = cipher.doFinal(decoded)
return String(decrypted, Charsets.UTF_8)
}
}6.3 混合加密(推荐)
最佳实践:AES + RSA:
kotlin
object HybridCrypt {
// 用 RSA 加密 AES 密钥,用 AES 加密数据
fun encrypt(
plaintext: String,
rsaPublicKey: PublicKey
): EncryptedData {
// 1. 生成 AES 密钥
val aesKey = AESCrypt.generateKey()
// 2. 用 AES 加密数据
val encryptedData = AESCrypt.encrypt(plaintext, aesKey)
// 3. 用 RSA 加密 AES 密钥
val encryptedKey = RSACrypt.encrypt(
rsaPublicKey,
Base64.encodeToString(aesKey.encoded, Base64.NO_WRAP)
)
return EncryptedData(encryptedKey, encryptedData)
}
fun decrypt(
encryptedData: EncryptedData,
rsaPrivateKey: PrivateKey
): String {
// 1. 用 RSA 私钥解密 AES 密钥
val aesKeyEncoded = RSACrypt.decrypt(
rsaPrivateKey,
encryptedData.encryptedKey
)
val aesKeyBytes = Base64.decode(aesKeyEncoded, Base64.NO_WRAP)
val aesKey = SecretKeyFactory.getInstance("AES")
.generateSecret(DSecretKeySpec(aesKeyBytes, "AES"))
// 2. 用 AES 解密数据
return AESCrypt.decrypt(encryptedData.encryptedData, aesKey)
}
}
data class EncryptedData(
val encryptedKey: String,
val encryptedData: String
)6.4 Bouncy Castle 扩展
添加依赖:
gradle
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'初始化:
kotlin
Security.addProvider(BouncyCastleProvider())使用高级算法:
kotlin
// 使用 GCM 模式(更安全)
val transformation = "AES/GCM/NoPadding"
val cipher = Cipher.getInstance(transformation, "BC")
// 使用椭圆曲线
val keyAgreement = KeyAgreement.getInstance("ECDH", "BC")敏感数据传输
7.1 敏感数据分类
高敏感数据:
- 密码
- 银行卡号
- 身份证号
- 手机号
- 生物特征中敏感数据:
- 邮箱
- 地址
- 交易记录低敏感数据:
- 公开信息
- 配置信息7.2 敏感数据加密传输
应用层加密:
kotlin
class SecureApiService {
private val serverPublicKey = loadServerPublicKey()
suspend fun login(username: String, password: String): LoginResponse {
// 加密密码
val encryptedPassword = RSACrypt.encrypt(
serverPublicKey,
password
)
return api.login(username, encryptedPassword)
}
suspend fun transferMoney(amount: Double, cardNumber: String): TransferResponse {
// 加密卡号
val encryptedCard = AESCrypt.encrypt(cardNumber, sharedKey)
return api.transfer(amount, encryptedCard)
}
}双重加密:
kotlin
fun doubleEncrypt(data: String): String {
// 第一层:AES 加密
val encrypted1 = AESCrypt.encrypt(data, aesKey)
// 第二层:RSA 加密
val encrypted2 = RSACrypt.encrypt(serverPublicKey, encrypted1)
return encrypted2
}7.3 密钥管理
密钥存储方案:
- Android Keystore(推荐):
kotlin
class KeystoreManager {
private val keyStore = KeyStore.getInstance("AndroidKeystore").apply {
load(null)
}
fun generateKey(alias: String): Key {
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
alias,
KeyPurposePurpose.ENCRYPT or KeyPurposePurpose.DECRYPT
)
.setBlockSize(256)
.setEncryptionMode(Cipher.Transformation.GCM)
.setKeySize(256)
.build()
val keyGenerator = KeyGenerator.getInstance(
KeyGenParameterSpec.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
keyGenerator.init(keyGenParameterSpec)
return keyGenerator.generateKey()
}
fun getKey(alias: String): Key {
return keyStore.getKey(alias, null)
}
}- SharedPreferences(不推荐):
kotlin
// ❌ 不推荐:明文存储
SharedPreferences.Editor.apply {
putString("key", "secret_key")
apply()
}
// ✅ 加密后存储
val encryptedKey = AESCrypt.encrypt("secret_key", masterKey)
SharedPreferences.Editor.apply {
putString("encrypted_key", encryptedKey)
apply()
}- EncryptedSharedPreferences(推荐):
kotlin
val encryptedPrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
encryptedPrefs.edit()
.putString("token", "secret_token")
.apply()7.4 防止日志泄露
敏感数据脱敏:
kotlin
object SecureLogger {
fun log(data: Map<String, Any>) {
val sanitized = data.mapValues { (key, value) ->
when (key.lowercase()) {
"password", "token", "card", "ssn", "id" ->
"***"
else -> value.toString()
}
}
Log.d("SecureAPI", sanitized.toString())
}
}禁用明文日志:
pro
# ProGuard 配置
-assumenosideeffects class android.util.Log {
public static int d(...);
public static int i(...);
public static int v(...);
public static int w(...);
}TrustManager 实现
8.1 自定义 TrustManager
信任特定证书:
kotlin
class CustomTrustManager(
private val customCertificates: List<X509Certificate>
) : X509TrustManager {
private val defaultTrustManager: X509TrustManager
init {
val factory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
factory.init(null)
defaultTrustManager = factory.trustManagers
.firstOrNull { it is X509TrustManager } as X509TrustManager
}
override fun checkServerTrusted(
chain: Array<out X509Certificate>,
authType: String
) {
// 检查是否在自定义证书列表中
val serverCert = chain[0]
if (customCertificates.any { it.serialNumber == serverCert.serialNumber }) {
return // 信任
}
// 否则使用默认验证
defaultTrustManager.checkServerTrusted(chain, authType)
}
override fun checkClientTrusted(
chain: Array<out X509Certificate>,
authType: String
) {
defaultTrustManager.checkClientTrusted(chain, authType)
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return customCertificates.toTypedArray()
}
}8.2 开发环境信任所有证书
⚠️ 仅用于开发:
kotlin
class UnsafeTrustManager : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out X509Certificate>,
authType: String
) {
// 不验证
}
override fun checkServerTrusted(
chain: Array<out X509Certificate>,
authType: String
) {
// 不验证
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
}
fun createUnsafeOkHttpClient(): OkHttpClient {
val trustAllCerts = arrayOf<TrustManager>(UnsafeTrustManager())
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, SecureRandom())
val sslSocketFactory = sslContext.socketFactory
return OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true } // 不验证主机名
.build()
}
// ⚠️ 仅在开发环境使用!
val client = if (BuildConfig.DEBUG) {
createUnsafeOkHttpClient()
} else {
createSecureOkHttpClient()
}8.3 加载自定义证书
从 Assets 加载:
kotlin
fun loadCertificateFromAssets(context: Context, filename: String): X509Certificate {
val certificateFactory = CertificateFactory.getInstance("X.509")
return context.assets.open(filename).use { input ->
certificateFactory.generateCertificate(input) as X509Certificate
}
}
// 使用
val customCert = loadCertificateFromAssets(context, "server.crt")
val trustManager = CustomTrustManager(listOf(customCert))从文件加载:
kotlin
fun loadCertificateFromFile(file: File): X509Certificate {
val certificateFactory = CertificateFactory.getInstance("X.509")
return FileInputStream(file).use { input ->
certificateFactory.generateCertificate(input) as X509Certificate
}
}代码示例
9.1 完整的 SSL Pinning 实现
kotlin
object SecureNetworkClient {
// 1. 加载备用证书
private fun loadBackupCertificates(context: Context): List<X509Certificate> {
return listOf(
loadCertificateFromAssets(context, "cert1.crt"),
loadCertificateFromAssets(context, "cert2.crt")
)
}
// 2. 生成证书哈希
private fun generateCertificateHashes(certificates: List<X509Certificate>): List<String> {
return certificates.map { cert ->
val sha256 = MessageDigest.getInstance("SHA-256")
val hash = sha256.digest(cert.publicKey.encoded)
"sha256/" + Base64.encodeToString(hash, Base64.NO_WRAP)
}
}
// 3. 创建 CertificatePinner
private fun createCertificatePinner(
context: Context,
domains: List<String>
): CertificatePinner {
val certificates = loadBackupCertificates(context)
val hashes = generateCertificateHashes(certificates)
val builder = CertificatePinner.Builder()
for (domain in domains) {
for (hash in hashes) {
builder.add(domain, hash)
}
}
return builder.build()
}
// 4. 创建 OkHttpClient
val client: OkHttpClient by lazy {
val certificatePinner = createCertificatePinner(
Application.application,
listOf("api.example.com", "auth.example.com")
)
OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(SecurityInterceptor())
.build()
}
// 5. 安全检查拦截器
class SecurityInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// 检查 Root
if (isDeviceRooted()) {
throw SecurityException("Device is rooted")
}
// 检查调试
if (isDebugging()) {
throw SecurityException("Debugging detected")
}
return chain.proceed(chain.request())
}
}
}9.2 敏感数据加密示例
kotlin
class SecureDataHandler {
private val keystoreManager = KeystoreManager()
private val serverPublicKey = loadServerPublicKey()
// 加密敏感数据
suspend fun encryptSensitiveData(data: SensitiveData): EncryptedData {
withContext(Dispatchers.IO) {
// 1. 用 RSA 加密密码
val encryptedPassword = RSACrypt.encrypt(
serverPublicKey,
data.password
)
// 2. 用 AES 加密其他数据
val aesKey = keystoreManager.getKey("data_key")
val encryptedInfo = AESCrypt.encrypt(
gson.toJson(data.info),
SecretKeySpec(aesKey.encoded, "AES")
)
return@withContext EncryptedData(
password = encryptedPassword,
info = encryptedInfo
)
}
}
// 解密敏感数据
suspend fun decryptSensitiveData(encrypted: EncryptedData): SensitiveData {
withContext(Dispatchers.IO) {
// 1. 解密密码
val password = RSACrypt.decrypt(
serverPrivateKey,
encrypted.password
)
// 2. 解密信息
val aesKey = keystoreManager.getKey("data_key")
val infoJson = AESCrypt.decrypt(
encrypted.info,
SecretKeySpec(aesKey.encoded, "AES")
)
return@withContext SensitiveData(
password = password,
info = gson.fromJson(infoJson, Info::class.java)
)
}
}
}面试考点
基础题
1. HTTPS 的工作原理?
1. 客户端发起请求
2. 服务器返回证书
3. 客户端验证证书
4. 生成会话密钥
5. 加密通信开始2. 数字证书的作用?
1. 身份认证
2. 加密通信
3. 数据完整性3. 证书链是什么?
终端实体证书 -> 中间证书 -> 根证书
逐级验证签名进阶题
1. SSL Pinning 的原理?
将证书或公钥哈希硬编码到应用中
只接受匹配的证书
防止中间人攻击2. 如何防止中间人攻击?
1. HTTPS
2. SSL Pinning
3. 证书验证
4. 双向认证
5. 代码加固3. AES 和 RSA 的区别?
AES:
- 对称加密
- 速度快
- 适合大数据
RSA:
- 非对称加密
- 速度慢
- 适合密钥交换高级题
1. 如何实现安全的密钥管理?
1. Android Keystore
2. EncryptedSharedPreferences
3. 密钥轮换
4. 不硬编码密钥2. SSL Pinning 的证书轮换策略?
1. 添加备用证书
2. 证书到期前更新
3. 保留旧证书一段时间
4. 远程配置备用证书3. 如何检测和分析证书问题?
1. 证书检查工具
2. 日志分析
3. 网络抓包
4. 证书透明度日志总结
核心要点
证书基础
- X.509 标准
- 证书链验证
- CA 机构
SSL Pinning
- 公钥锁定
- 证书锁定
- 证书轮换
加密技术
- AES 对称加密
- RSA 非对称加密
- 混合加密
安全防护
- 防止中间人攻击
- Root 检测
- 代码加固
最佳实践
kotlin
// 1. 使用 SSL Pinning
client.certificatePinner(pinner)
// 2. 使用 Android Keystore
val key = keystoreManager.generateKey("my_key")
// 3. 混合加密
val encrypted = HybridCrypt.encrypt(data, publicKey)
// 4. 敏感数据加密存储
EncryptedSharedPreferences.create(...)
// 5. 安全日志
SecureLogger.log(sanitizedData)本文约 15000 字,涵盖了证书与加密的核心知识点。