Android中基于nv-websocket-client库实现WebSocket SSL证书固定方法咨询
用nv-websocket-client实现Android WebSocket的SSL证书固定
嘿,刚好我对这个库的证书固定有实际实践经验,给你一步步拆解怎么实现:
1. 先拿到目标服务器证书的SHA-256指纹
证书固定的核心是验证服务器证书的哈希值,所以第一步得先获取你要连接的WebSocket服务器证书的SHA-256指纹。你可以用openssl命令行工具来获取:
openssl s_client -connect your-websocket-server:443 < /dev/null | openssl x509 -noout -fingerprint -sha256
输出会是类似 SHA256 Fingerprint=AA:BB:CC:... 的格式,把里面的冒号全部去掉,得到一串纯大写(或小写)的字符串,比如AABBCCDDEEFF112233445566778899AABBCCDDEEFF,把这个字符串存好备用。
2. 写一个自定义的TrustManager做证书验证
接下来我们需要自定义一个X509TrustManager,它的作用就是只让指纹匹配的证书通过验证。代码示例如下:
import javax.net.ssl.X509TrustManager; import java.security.MessageDigest; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class PinningTrustManager implements X509TrustManager { // 把这里替换成你刚才拿到的SHA-256指纹 private static final String ALLOWED_CERT_FINGERPRINT = "AABBCCDDEEFF112233445566778899AABBCCDDEEFF"; @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // 客户端证书验证,如果你不需要客户端证书认证,这里可以留空或者直接抛出异常 } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { if (chain == null || chain.length == 0) { throw new CertificateException("服务器未提供有效证书"); } // 获取服务器返回的第一个证书(通常就是服务器本身的证书) X509Certificate serverCert = chain[0]; String serverCertFingerprint = calculateSha256Fingerprint(serverCert); // 对比指纹是否匹配,不匹配就抛出异常阻止连接 if (!ALLOWED_CERT_FINGERPRINT.equalsIgnoreCase(serverCertFingerprint)) { throw new CertificateException("服务器证书指纹不匹配,可能存在中间人攻击风险"); } } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } // 计算证书的SHA-256指纹 private String calculateSha256Fingerprint(X509Certificate cert) throws CertificateException { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hashBytes = digest.digest(cert.getEncoded()); StringBuilder fingerprintBuilder = new StringBuilder(); for (byte b : hashBytes) { fingerprintBuilder.append(String.format("%02X", b)); } return fingerprintBuilder.toString(); } catch (Exception e) { throw new CertificateException("计算证书指纹失败", e); } } }
3. 配置WebSocketFactory使用自定义TrustManager
最后一步就是把这个自定义的TrustManager配置到WebSocketFactory里,让它在建立SSL连接时用我们的规则验证证书:
import com.neovisionaries.ws.client.WebSocket; import com.neovisionaries.ws.client.WebSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; public class WebSocketHelper { public static WebSocket createPinnedWebSocket() throws Exception { // 创建SSLContext,传入我们自定义的TrustManager SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{new PinningTrustManager()}, null); // 初始化WebSocketFactory WebSocketFactory factory = new WebSocketFactory(); factory.setSSLContext(sslContext); // 这里的主机名验证:如果你的证书里的CN字段和服务器地址一致,建议保持开启; // 如果你确定证书固定已经足够安全(比如证书是你自己签发的),可以设置为false,但不推荐随便关 factory.setVerifyHostname(true); // 创建并返回WebSocket实例,替换成你的实际WebSocket地址 WebSocket webSocket = factory.createSocket("wss://your-websocket-server/your-path"); // 这里可以添加你的WebSocket监听器,处理连接、消息等事件 // webSocket.addListener(new YourWebSocketMessageListener()); return webSocket; } }
几个要注意的点
- 如果服务器有备用证书(比如证书过期前提前部署新证书),你可以把
ALLOWED_CERT_FINGERPRINT改成一个字符串列表,验证时检查当前证书指纹是否在列表里。 - 后续服务器证书更新时,你必须同步更新代码里的指纹,然后重新发布应用,否则用户的应用会无法连接。
- 除非特殊情况,不要关闭
setVerifyHostname(true),否则即使证书指纹匹配,也可能被攻击者用同指纹但主机名不符的证书欺骗。
内容的提问来源于stack exchange,提问作者user3863488
相关产品推荐
相关产品推荐

