网络通信安全:HTTPS配置、证书固定与中间人攻击防护

各位同学,今天我们来聊聊网络通信安全。说实话,这是Android组件安全里最容易出问题的一环。我见过太多App,组件本身写得挺安全,结果网络传输层被人一锅端了。你想想看,数据在传输过程中被截获,那前面做的所有防护都白费了。

为什么网络通信安全这么重要?

咱们先理清一个概念。Activity劫持、Provider数据泄露,这些攻击的前提是什么?是攻击者能接触到你的数据。但如果网络通信不安全,攻击者根本不需要接触你的设备——他直接在网络中间就把数据拿走了。

我在项目中遇到过这么一件事:一个金融类App,所有业务逻辑都做了签名校验,结果上线第一天就被爆出用户信息泄露。查到最后发现,问题出在HTTP请求上——他们用的还是明文HTTP,连HTTPS都没开。攻击者在公共WiFi上做了个ARP欺骗,所有用户的登录密码、交易记录全被截获了。

说白了,网络通信安全就是你的最后一道防线。这道防线破了,前面做得再好也没用。

HTTPS配置:基础但容易踩坑

HTTPS配置看起来简单,但坑不少。我刚开始做Android开发时也犯过低级错误。咱们一步步来看。

1. 强制使用HTTPS

首先,你的App必须强制使用HTTPS,不能回退到HTTP。Android 9(API 28)之后,默认就禁止了明文HTTP流量。但如果你还在支持低版本,就得手动配置。

AndroidManifest.xml 中配置网络安全策略:

<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

对应的 res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>
⚠️ 注意: 这里有个坑。如果你只配了 cleartextTrafficPermitted="false",但没配 trust-anchors,那系统会使用默认的信任锚。默认信任锚包括系统证书和用户证书。这意味着如果用户在手机上安装了恶意CA证书,你的HTTPS连接照样能被中间人攻击。

2. 证书验证的常见问题

HTTPS连接建立时,客户端会验证服务端证书的合法性。验证过程包括:

  • 证书链验证:从服务端证书到根证书,每一级都要验证签名
  • 域名验证:证书中的CN或SAN必须匹配请求的域名
  • 有效期验证:证书必须在有效期内
  • 吊销状态验证:检查证书是否被吊销(可选但推荐)

我记得有一次排查线上问题,发现某个用户一直连不上服务器。查了半天,原来是那个用户的系统时间被改到了2020年,而服务端证书是2021年签发的。证书有效期验证直接失败了。

证书固定(Pinning):给连接加把锁

HTTPS虽然安全,但有个问题:系统信任的CA太多了。任何一家CA被攻破,或者用户安装了恶意CA,你的HTTPS连接就不再安全。

证书固定(Certificate Pinning)就是解决这个问题的。说白了,就是让你的App只信任特定的证书或公钥,而不是信任所有系统CA。

1. 证书固定的两种方式

方式 说明 优点 缺点
证书固定 固定完整的服务端证书 实现简单 证书更换时必须更新App
公钥固定 固定证书中的公钥 证书更换但公钥不变时无需更新App 需要额外计算公钥指纹

我个人更推荐公钥固定。为什么呢?因为证书可以换,但公钥对一般不会变。你想想看,如果哪天你的证书过期了,你换张新证书,但公钥没变,那App就不用更新。如果是证书固定,你就得强制用户更新App,这体验太差了。

2. OkHttp中的证书固定实现

咱们用OkHttp来演示。这是Android开发中最常用的网络库。

// 计算公钥指纹
// 可以用 openssl 命令:openssl x509 -in cert.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

String hostname = "api.example.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .add(hostname, "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
    .build();

OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build();
💡 技巧: 建议至少固定两个公钥。一个是当前使用的,另一个是备用公钥。这样当你要更换证书时,可以先部署使用备用公钥的新证书,等所有用户都更新后,再移除旧公钥。这样可以避免服务中断。

3. 证书固定的避坑指南

我曾经在项目中遇到过这么个问题:证书固定写死了,结果测试环境用的自签名证书,线上环境用的正式证书。测试的时候一切正常,一上线就全崩了。为什么?因为测试环境的证书指纹和线上不一样啊。

所以,我建议的做法是:

  • 在BuildConfig中区分环境,不同环境使用不同的证书固定配置
  • 或者使用动态配置,从服务端下发证书指纹(但要保证下发通道的安全)
  • 一定要有备用方案,防止证书固定导致App完全无法访问

防止中间人攻击:全方位防护

中间人攻击(MITM)是网络通信中最常见的攻击方式。攻击者伪装成服务端,与客户端建立连接,同时与真正的服务端建立连接,从而窃取或篡改通信数据。

1. 常见的MITM攻击场景

  • 公共WiFi劫持:攻击者在公共WiFi上做ARP欺骗或DNS劫持
  • 恶意代理:用户手机被安装了恶意代理App
  • CA证书欺骗:用户安装了恶意CA证书
  • SSLStrip攻击:将HTTPS降级为HTTP

2. 防护措施总结

咱们把防护措施整理一下:

防护措施 防护目标 实现方式
强制HTTPS 防止SSLStrip攻击 网络安全策略配置
证书固定 防止CA欺骗 OkHttp CertificatePinner
HSTS 防止HTTPS降级 服务端配置+客户端预加载
证书透明度 检测恶意证书 Certificate Transparency
双向认证 防止服务端伪造 客户端证书+服务端证书

3. 双向认证:最严格的防护

如果你们做的是金融、政务这类高安全要求的App,我建议上双向认证。双向认证不仅服务端要出示证书,客户端也要出示证书。这样即使攻击者伪造了服务端,他没有客户端的证书,也无法建立连接。

// 加载客户端证书
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(getResources().openRawResource(R.raw.client), "password".toCharArray());

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "password".toCharArray());

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);

OkHttpClient client = new OkHttpClient.Builder()
    .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManager)
    .build();
⚠️ 警告: 客户端证书一定要保护好。如果客户端证书被提取出来,攻击者就可以冒充你的App。建议将证书放在Native层,或者使用Android的KeyStore系统存储私钥。

知识体系总览

咱们这一章的内容比较多,我画了张图帮你梳理一下整体结构:

网络通信安全知识体系 网络通信安全 HTTPS配置 证书固定(Pinning) MITM防护 强制HTTPS 网络安全策略 证书验证 证书固定 公钥固定 OkHttp实现 HSTS 证书透明度 双向认证 目标:防止组件数据在网络传输中被窃取 三层防护:基础配置 → 证书固定 → 高级防护 层层递进,构建完整的网络通信安全体系

总结

网络通信安全这块,说白了就是三件事:配好HTTPS、做好证书固定、防住中间人攻击。每一层都有坑,但每一层都能防住大部分攻击。

我个人习惯是:能用双向认证就用双向认证,实在不行也要上证书固定。HTTPS配置是基础,但千万别以为配了HTTPS就万事大吉了。你想想看,如果用户手机里有个恶意CA,你的HTTPS跟HTTP有什么区别?

最后提醒一句:安全是一个持续的过程,不是配一次就完事了。证书会过期、攻击手法会更新、你的App也会迭代。定期检查你的网络通信配置,确保它们还在正常工作。

核心要点:
  • 强制HTTPS,禁止明文HTTP流量
  • 使用证书固定(推荐公钥固定),不信任系统CA
  • 高安全场景使用双向认证
  • 定期检查和更新证书固定配置

公众号:蓝海资料掘金营,微信deep3321