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协议,核心步骤如下:
- ClientHello:客户端发送支持的TLS版本、加密套件、扩展字段等信息。
- ServerHello:服务器选定适配的TLS版本、加密套件,返回给客户端。
- 证书交换:服务器发送证书链,客户端验证证书有效性。
- 密钥交换:依据所选加密套件交换密钥材料,生成会话密钥。
- 握手完成:双方发送Finished消息确认,后续通信使用会话密钥加密。
Java的SSLSocket与SSLEngine封装了上述流程,底层由JDK安全提供者(如SunJSSE)实现。由于标准API未暴露Hello消息实例,需通过日志拦截或字节流解析方式获取相关信息。
内容的提问来源于stack exchange,提问作者goranv33
相关产品推荐
相关产品推荐

