You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:09:59