news 2026/6/9 22:56:05

SSL Socket 通信与本地 Mock Server 实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSL Socket 通信与本地 Mock Server 实践指南

SSL Socket 通信与本地 Mock Server 实践指南

一、背景概念

1.1 什么是 Socket 通信

Socket(套接字)是网络通信的基础抽象。两个程序通过 Socket 建立连接后,可以像读写文件一样发送和接收数据。

与 HTTP 的区别:

对比项HTTP原生 Socket
协议层次应用层协议(基于 TCP)传输层直接通信(TCP)
数据格式有固定的请求/响应格式(Header + Body)无固定格式,双方自行约定报文结构
连接方式短连接为主(请求-响应后断开)可以长连接或短连接
工具支持Postman、curl 等现成工具需要自己编写客户端/服务端程序
典型场景Web API、微服务银行/金融系统、硬件通信、私有协议

1.2 什么是 SSL/TLS

SSL(Secure Sockets Layer)和 TLS(Transport Layer Security)是加密通信协议,用来保证数据在网络传输中不被窃听和篡改。TLS 是 SSL 的升级版本,当前主流使用 TLS 1.2 或 TLS 1.3。

通信过程简化理解:

客户端 服务端 | | |-- 1. 发起连接(ClientHello) ------->| |<-- 2. 返回证书(ServerHello) ------| |-- 3. 验证证书是否可信 ------------> | |-- 4. 协商加密密钥 ---------------> | |<==== 5. 加密通道建立 ============> | |-- 6. 发送业务数据(加密的)-------> | |<-- 7. 接收响应数据(加密的)------| | |

关键概念:

  • 证书(Certificate):服务端的"身份证",包含公钥信息,由 CA(证书颁发机构)签发
  • 信任库(TrustStore):客户端存放"我信任的证书"的容器。客户端收到服务端证书后,会检查它是否在信任库中
  • 密钥库(KeyStore):服务端存放"自己的私钥和证书"的容器,用来证明自己的身份

1.3 什么是 JKS

JKS(Java KeyStore)是 Java 特有的密钥库文件格式,用来存储:

  • 私钥 + 对应的证书链(服务端用)
  • 信任的第三方证书(客户端用)

一个.jks文件就像一个加密的"保险箱",里面可以存多把"钥匙"(按 alias 别名区分),打开保险箱需要密码。

1.4 通信模型总结

┌─────────────┐ ┌─────────────┐ │ 客户端 │ │ 服务端 │ │ │ │ │ │ TrustStore │ ←验证证书是否可信→ │ KeyStore │ │ (ylh.jks) │ │(server.jks) │ │ │ │ │ │ SSLSocket │ ====== TLS 1.2 ===== │SSLServerSocket│ │ │ │ │ │ 发送报文 │ ------TCP数据流----→ │ 接收报文 │ │ 接收响应 │ ←----TCP数据流------ │ 发送响应 │ └─────────────┘ └─────────────┘

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、报文协议格式

2.1 自定义协议结构

由于是私有协议,双方约定了固定的报文格式:

[外层前缀 18字节][内层报文头 10+141=151字节][XML业务数据][MD5校验和 32字节] \_____________________169字节____________________/

客户端收到响应后的解析逻辑:

Stringresponse=rawData.substring(169,rawData.length()-32);// 跳过前169字节报文头,去掉最后32字节校验和,中间就是XML

2.2 报文各部分说明

外层前缀(18字节):

位置长度内容说明
1-33“BADAO”固定标识
4-85“1001”系统编码
9-1810“0000000xxx”后续内容总长度(补零到10位)

内层长度(10字节):

位置长度内容说明
19-2810“0000000xxx”内层报文体总长度(补零到10位)

内层报文头(141字节):

位置长度字段名示例值
1-33业务类型“TEST”
41报文版本“1”
5-95发起系统编码“1001”
10-145目标系统编码“1002”
15-184交易类型“001”
19-202操作类型“02”
21-222报文编码“01”
23-242通讯协议“01”
25-262数据协议“01”
27-4620交易流水号“12345678901234567890”
47-5610XML报文长度“0000000350”
57-7014交易时间“20260608120000”
71-722请求类型"01"请求/"02"响应
73-8816交易签名MD5签名截取
89-913附件个数“000”
92-14150预留位全"0"

XML业务数据(变长):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><scf><header><txId>交易流水号</txId><txTime>交易时间</txTime><rtCode>状态码</rtCode><message>错误信息</message></header><body><!-- 业务数据 --></body></scf>

MD5校验和(32字节):

对"内层报文头 + XML"部分做 MD5 哈希,得到 32 位十六进制大写字符串,附在最后用于校验数据完整性。


三、keytool 证书工具详解

keytool是 JDK 自带的密钥和证书管理工具,用来创建和管理 JKS 文件。

3.1 常用命令

生成密钥对(创建 KeyStore):

keytool-genkeypair\-aliasmykey\# 别名,标识这对密钥-keyalgRSA\# 算法类型(RSA 最常用)-keysize2048\# 密钥长度(2048位足够安全)-keystoreserver.jks\# 输出的 JKS 文件名-storepass123456\# JKS 文件的密码-dname"CN=localhost"\# 证书主体信息(CN=域名)-validity365# 证书有效期(天)

导出证书:

keytool-exportcert\-aliasmykey\# 要导出的别名-keystoreserver.jks\# 从哪个 JKS 导出-storepass123456\# JKS 密码-fileserver.cer# 导出的证书文件

导入证书到信任库:

keytool-importcert\-aliasmykey\# 导入后在信任库中的别名-keystoreclient.jks\# 客户端的信任库文件-storepass654321\# 信任库密码-fileserver.cer\# 要导入的证书文件-noprompt# 不询问直接导入

查看 JKS 中有哪些证书:

keytool-list\-keystoreserver.jks\-storepass123456

3.2 dname 参数说明

-dname指定证书的主体信息(Distinguished Name),格式为:

CN=Common Name, OU=Organization Unit, O=Organization, L=Locality, ST=State, C=Country

本地测试时只需要CN=localhostCN=127.0.0.1


四、完整独立示例(与业务无关)

以下是一个最简化的 SSL Socket 通信示例,包含服务端和客户端,演示整个 SSL 通信 + 自定义协议解析的过程。

4.1 项目结构

ssl-socket-demo/ ├── generate-certs.bat # Windows 证书生成脚本 ├── server.jks # 服务端密钥库(运行脚本后生成) ├── client.jks # 客户端信任库(运行脚本后生成) ├── SslServer.java # SSL 服务端 └── SslClient.java # SSL 客户端

4.2 证书生成脚本(generate-certs.bat)

@echo off echo === 生成 SSL 证书 === REM 1. 生成服务端密钥库 keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -keystore server.jks -storepass changeit -dname "CN=localhost, OU=Demo, O=Demo, L=City, ST=State, C=CN" -validity 365 REM 2. 导出服务端证书 keytool -exportcert -alias server -keystore server.jks -storepass changeit -file server.cer REM 3. 将服务端证书导入客户端信任库 keytool -importcert -alias server -keystore client.jks -storepass changeit -file server.cer -noprompt REM 4. 清理证书文件 del server.cer echo === 完成 === echo 生成了 server.jks(服务端用)和 client.jks(客户端用) pause

4.3 SSL 服务端(SslServer.java)

importjavax.net.ssl.*;importjava.io.*;importjava.nio.charset.StandardCharsets;importjava.security.KeyStore;importjava.security.MessageDigest;/** * SSL Socket 服务端示例. * 监听端口,接收客户端请求,返回自定义协议格式的响应. */publicclassSslServer{privatestaticfinalintPORT=9999;privatestaticfinalStringKEYSTORE_PATH="server.jks";privatestaticfinalStringKEYSTORE_PASS="changeit";publicstaticvoidmain(String[]args)throwsException{// 1. 加载服务端密钥库KeyStoreks=KeyStore.getInstance("JKS");ks.load(newFileInputStream(KEYSTORE_PATH),KEYSTORE_PASS.toCharArray());// 2. 初始化 KeyManager(管理服务端自己的证书和私钥)KeyManagerFactorykmf=KeyManagerFactory.getInstance("SunX509");kmf.init(ks,KEYSTORE_PASS.toCharArray());// 3. 创建 SSL 上下文SSLContextsslContext=SSLContext.getInstance("TLSv1.2");sslContext.init(kmf.getKeyManagers(),null,null);// 4. 创建 SSL Server SocketSSLServerSocketFactoryfactory=sslContext.getServerSocketFactory();SSLServerSocketserverSocket=(SSLServerSocket)factory.createServerSocket(PORT);serverSocket.setEnabledProtocols(newString[]{"TLSv1.2"});System.out.println("SSL 服务端启动,监听端口: "+PORT);// 5. 循环接收客户端连接while(true){java.net.Socketclient=serverSocket.accept();System.out.println("客户端已连接: "+client.getInetAddress());InputStreaminput=client.getInputStream();OutputStreamoutput=client.getOutputStream();// 读取请求byte[]buf=newbyte[4096];intlen=input.read(buf);Stringrequest=newString(buf,0,len,StandardCharsets.UTF_8);System.out.println("收到请求: "+request);// 构造响应(自定义协议格式)StringxmlBody="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+"<response>"+"<code>200</code>"+"<message>Hello from SSL Server</message>"+"<data>This is the response data</data>"+"</response>";StringfullResponse=buildProtocolMessage(xmlBody);System.out.println("发送响应,总长度: "+fullResponse.length());output.write(fullResponse.getBytes(StandardCharsets.UTF_8));output.flush();client.close();}}/** * 按自定义协议封装报文. * 格式: [前缀18字节][内层长度10字节][报文头141字节][XML][MD5校验32字节] * 总报文头 = 18 + 10 + 141 = 169字节 */privatestaticStringbuildProtocolMessage(Stringxml)throwsException{// 141字节报文头(简化版,用固定值填充)StringBuilderheader=newStringBuilder();header.append("BIZ");// 业务类型 3位header.append("1");// 版本 1位header.append("SRVID");// 发起系统 5位header.append("CLTID");// 目标系统 5位header.append("0001");// 交易类型 4位header.append("02");// 操作类型 2位header.append("01");// 报文编码 2位header.append("01");// 通讯协议 2位header.append("01");// 数据协议 2位header.append("00000000000000000001");// 流水号 20位header.append(String.format("%010d",xml.getBytes(StandardCharsets.UTF_8).length));// XML长度 10位header.append("20260609120000");// 时间 14位header.append("02");// 响应标识 2位header.append("ABCDEF0123456789");// 签名 16位header.append("000");// 附件数 3位// 预留50位header.append("00000000000000000000000000000000000000000000000000");// 以上合计 141 字符// 内层 = 报文头 + XMLStringinnerContent=header.toString()+xml;// MD5校验和(32字符)StringcheckSum=md5(innerContent.getBytes(StandardCharsets.UTF_8));StringinnerWithChecksum=innerContent+checkSum;// 内层长度(10位)StringinnerLengthStr=String.format("%010d",innerWithChecksum.getBytes(StandardCharsets.UTF_8).length);// 外层 = "ENA" + 系统编码5位 + 外层长度10位 + 内层长度 + 内层内容StringinnerMsg=innerLengthStr+innerWithChecksum;StringouterLengthStr=String.format("%010d",innerMsg.getBytes(StandardCharsets.UTF_8).length);return"ENA"+"SRVID"+outerLengthStr+innerMsg;}privatestaticStringmd5(byte[]data)throwsException{MessageDigestmd=MessageDigest.getInstance("MD5");byte[]digest=md.digest(data);StringBuildersb=newStringBuilder();for(byteb:digest){sb.append(String.format("%02X",0xFF&b));}returnsb.toString();}}

4.4 SSL 客户端(SslClient.java)

importjavax.net.ssl.*;importjava.io.*;importjava.nio.charset.StandardCharsets;importjava.security.KeyStore;/** * SSL Socket 客户端示例. * 连接服务端,发送请求,接收并解析自定义协议格式的响应. */publicclassSslClient{privatestaticfinalStringSERVER_HOST="127.0.0.1";privatestaticfinalintSERVER_PORT=9999;privatestaticfinalStringTRUSTSTORE_PATH="client.jks";privatestaticfinalStringTRUSTSTORE_PASS="changeit";publicstaticvoidmain(String[]args)throwsException{// 1. 加载客户端信任库(包含服务端证书)KeyStorets=KeyStore.getInstance("JKS");ts.load(newFileInputStream(TRUSTSTORE_PATH),TRUSTSTORE_PASS.toCharArray());// 2. 初始化 TrustManager(验证服务端证书)TrustManagerFactorytmf=TrustManagerFactory.getInstance("SunX509");tmf.init(ts);// 3. 创建 SSL 上下文SSLContextsslContext=SSLContext.getInstance("TLSv1.2");sslContext.init(null,tmf.getTrustManagers(),null);// 4. 建立 SSL 连接SSLSocketFactoryfactory=sslContext.getSocketFactory();SSLSocketsocket=(SSLSocket)factory.createSocket(SERVER_HOST,SERVER_PORT);System.out.println("已连接到服务端: "+SERVER_HOST+":"+SERVER_PORT);// 5. 发送请求OutputStreamoutput=socket.getOutputStream();Stringrequest="Hello, this is a test request from client";output.write(request.getBytes(StandardCharsets.UTF_8));output.flush();System.out.println("已发送请求: "+request);// 6. 接收响应InputStreaminput=socket.getInputStream();byte[]buf=newbyte[4096];StringBuildersb=newStringBuilder();intlen;while((len=input.read(buf))>0){sb.append(newString(buf,0,len,StandardCharsets.UTF_8));if(len<buf.length){break;}}StringrawResponse=sb.toString();System.out.println("收到响应,总长度: "+rawResponse.length());// 7. 按协议解析响应// 跳过前169字节报文头,去掉最后32字节校验和,中间是XMLStringxml=rawResponse.substring(169,rawResponse.length()-32);System.out.println("解析出 XML:\n"+xml);socket.close();}}

4.5 运行步骤

# 1. 生成证书generate-certs.bat# 2. 编译javac SslServer.java javac SslClient.java# 3. 启动服务端(新开一个终端窗口)javaSslServer# 4. 运行客户端(另一个终端窗口)javaSslClient

4.6 预期输出

服务端:

SSL 服务端启动,监听端口: 9999 客户端已连接: /127.0.0.1 收到请求: Hello, this is a test request from client 发送响应,总长度: 452

客户端:

已连接到服务端: 127.0.0.1:9999 已发送请求: Hello, this is a test request from client 收到响应,总长度: 452 解析出 XML: <?xml version="1.0" encoding="UTF-8"?><response><code>200</code><message>Hello from SSL Server</message><data>This is the response data</data></response>

五、为什么不能用 Postman 等 HTTP 工具测试

原因说明
协议不同Postman 基于 HTTP 协议,而 Socket 通信是裸 TCP 数据流
报文格式不同HTTP 有固定的GET /path HTTP/1.1格式,Socket 私有协议是自定义的二进制/文本格式
连接方式不同HTTP 有握手过程(协议协商),Socket 直接建立 TCP + TLS 连接后收发数据
端口行为不同HTTP 服务端期望收到 HTTP 格式数据,收到私有格式会报错

所以测试这类接口只能:

  1. 写代码模拟(本文方案)
  2. 使用专业的 TCP 调试工具(如 Hercules、PacketSender)发送原始字节
  3. 抓包工具(Wireshark)观察数据

六、关键概念速查表

概念一句话解释
Socket程序之间网络通信的端点,类似"电话两端"
SSL/TLS在 Socket 上加一层加密,防窃听防篡改
JKSJava 的密钥/证书存储文件格式
KeyStore存自己的私钥+证书,服务端用来证明身份
TrustStore存信任的证书,客户端用来验证服务端身份
keytoolJDK 自带的证书管理命令行工具
Certificate数字证书,包含公钥和身份信息
MD5 checksum报文完整性校验,防止数据被篡改
报文头固定长度的元数据区域,描述报文类型和长度等信息
JAXBJava 的 XML 与对象互转框架

七、总结

整个通信链路可以概括为:

1. 客户端用 TrustStore 验证服务端证书 → 建立加密通道 2. 客户端按约定格式(报文头+XML+校验和)组装请求 → 通过加密通道发送 3. 服务端收到请求 → 处理业务 → 按同样格式组装响应 → 发回 4. 客户端收到响应 → 跳过报文头、去掉校验和 → 取出XML → 反序列化为Java对象

本地 Mock 的核心思路就是:自己启动一个 SSL Server,按照协议格式返回固定数据,让客户端以为在和真实服务端通信。

八、测试效果

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 22:54:01

嵌入式开发实战:Kinetis K20外设时序与引脚复用配置详解

1. 项目概述与核心价值在嵌入式系统开发的实战中&#xff0c;我们常常会陷入一种困境&#xff1a;硬件电路连接无误&#xff0c;软件驱动配置看起来也正确&#xff0c;但外设就是无法正常工作&#xff0c;或者数据传输时好时坏&#xff0c;稳定性堪忧。很多时候&#xff0c;问题…

作者头像 李华
网站建设 2026/6/9 22:49:00

内网渗透-横向移动-哈希传递(PTH)+密钥传递(PTK)+票据传递(PTT) 的横向

Pass the Hash 哈希传递攻击(PTH)横向移动 哈希传递 大多数渗透测试人员都听说过哈希传递(Pass The Hash)攻击。该方法通过找到与账户相关 的密码散列值(通常是 NTLM Hash)来进行攻击。在域环境中,用户登录计算机时使用的大都是域账号,大量计算机在安装时会使用相同的本 地管…

作者头像 李华