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

Java 17+如何从TLS握手获取ServerHello与ClientHello

在Java 17中获取TLS握手的ClientHello与ServerHello

方式一:启用TLS调试日志(快速查看握手细节)

若仅需查看握手过程的详细内容(无需获取Java对象),添加以下JVM启动参数即可:

-Djavax.net.debug=ssl:handshake

该参数会输出完整的TLS握手流程,包括ClientHello、ServerHello的所有字段(如协议版本、加密套件、扩展字段等),输出内容将打印至控制台或指定日志文件。

方式二:自定义拦截逻辑获取握手消息(获取对象/字节流)

Java标准API并未直接暴露ClientHello和ServerHello的实例对象,此类结构属于TLS协议内部实现细节。若需获取相关内容,可通过拦截TLS握手字节流自行解析,或借助第三方库(如BouncyCastle)简化解析工作。

步骤1:包装SSLSocket拦截字节流

创建自定义SSLSocket包装类,在握手阶段拦截底层字节流,提取握手消息:

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class InterceptedSSLSocket extends SSLSocket {
    private final SSLSocket delegate;
    private final InputStream interceptedIn;
    private final OutputStream interceptedOut;

    public InterceptedSSLSocket(SSLSocket delegate) throws IOException {
        this.delegate = delegate;
        this.interceptedIn = new TLSHandshakeInterceptorInputStream(delegate.getInputStream());
        this.interceptedOut = new TLSHandshakeInterceptorOutputStream(delegate.getOutputStream());
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return interceptedIn;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        return interceptedOut;
    }

    @Override
    public void startHandshake() throws IOException {
        delegate.startHandshake();
    }

    // 按需重写其他SSLSocket方法,全部委托给delegate实例

    private static class TLSHandshakeInterceptorInputStream extends InputStream {
        private final InputStream delegate;

        public TLSHandshakeInterceptorInputStream(InputStream delegate) {
            this.delegate = delegate;
        }

        @Override
        public int read() throws IOException {
            int b = delegate.read();
            // 参考TLS协议规范解析字节流:记录层头部为5字节(内容类型、版本、长度),握手消息类型字段中ClientHello=1、ServerHello=2
            // 可积累字节,读取到完整握手消息后解析为对应结构
            return b;
        }

        // 重写read(byte[])等方法,确保字节被完整拦截
    }

    private static class TLSHandshakeInterceptorOutputStream extends OutputStream {
        private final OutputStream delegate;

        public TLSHandshakeInterceptorOutputStream(OutputStream delegate) {
            this.delegate = delegate;
        }

        @Override
        public void write(int b) throws IOException {
            delegate.write(b);
            // 捕获输出字节,解析ClientHello消息
        }
    }
}

class InterceptedSSLSocketFactory extends SSLSocketFactory {
    private final SSLSocketFactory delegate;

    public InterceptedSSLSocketFactory(SSLSocketFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return new InterceptedSSLSocket((SSLSocket) delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return new InterceptedSSLSocket((SSLSocket) delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return new InterceptedSSLSocket((SSLSocket) delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return new InterceptedSSLSocket((SSLSocket) delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return new InterceptedSSLSocket((SSLSocket) delegate.createSocket(address, port, localAddress, localPort));
    }
}

步骤2:使用BouncyCastle解析握手消息

手动解析TLS字节流较为繁琐,可借助BouncyCastle库简化工作。添加依赖后,通过TlsClient接口拦截握手消息:

import org.bouncycastle.tls.*;
import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TlsHandshakeInterceptor extends DefaultTlsClient {
    public TlsHandshakeInterceptor(BcTlsCrypto crypto) {
        super(crypto);
    }

    @Override
    public void notifyServerHello(ServerHello serverHello) throws IOException {
        super.notifyServerHello(serverHello);
        // 获取ServerHello对象,访问其字段:协议版本、加密套件等
        System.out.println("Received ServerHello: CipherSuite=" + serverHello.getCipherSuite());
    }

    @Override
    public ClientHello generateClientHello() throws IOException {
        ClientHello clientHello = super.generateClientHello();
        // 获取或修改ClientHello对象
        System.out.println("Generated ClientHello: Supported CipherSuites=" + clientHello.getCipherSuites());
        return clientHello;
    }
}

// 使用示例
public class TlsClientDemo {
    public static void main(String[] args) throws IOException {
        BcTlsCrypto crypto = new BcTlsCrypto();
        // 替换为实际的输入输出流(如Socket的流)
        TlsClientProtocol protocol = new TlsClientProtocol(System.in, System.out);
        protocol.connect(new TlsHandshakeInterceptor(crypto));
    }
}

Java TLS握手后台流程概述

Java的TLS握手基于标准TLS协议,核心步骤如下:

  1. ClientHello:客户端发送支持的TLS版本、加密套件、扩展字段等信息。
  2. ServerHello:服务器选定适配的TLS版本、加密套件,返回给客户端。
  3. 证书交换:服务器发送证书链,客户端验证证书有效性。
  4. 密钥交换:依据所选加密套件交换密钥材料,生成会话密钥。
  5. 握手完成:双方发送Finished消息确认,后续通信使用会话密钥加密。

Java的SSLSocket与SSLEngine封装了上述流程,底层由JDK安全提供者(如SunJSSE)实现。由于标准API未暴露Hello消息实例,需通过日志拦截或字节流解析方式获取相关信息。

内容的提问来源于stack exchange,提问作者goranv33

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 00:54:56