一文学习和实践 当今互联网安全的基石 - TLS/SSL
[[N_OSI 模型.TCP 协议.封包]]
[[Java TLS Socket]]
[[TLS&SSL协议]]
What is SSL/TLS ?
TLS(Transport Layer Security)即传输层安全协议,SSL(Secure Sockets Layer)即安全套接层协议。它们都是用于在网络通信中保障数据安全和隐私的加密协议。
TLS(Transport Layer Security)传输层安全协议
- 发展历程
- TLS 是 SSL 协议的继任者。由于 SSL 协议存在一些安全漏洞,并且随着网络安全需求的不断提高,IETF(Internet Engineering Task Force)对 SSL 3.0 进行了改进,推出了 TLS 1.0 协议,于 1999 年发布。TLS 协议在后续又不断更新版本,如 TLS 1.1、TLS 1.2 和 TLS 1.3,每一个新版本都在安全性和性能等方面有所提升。
- 工作原理
- TLS 的工作原理与 SSL 类似,也包括握手阶段。在 TLS 握手过程中,客户端和服务器会协商协议版本、加密算法套件、交换密钥等。TLS 1.3 相比之前的版本,简化了握手过程,减少了握手消息的往返次数,从而提高了连接建立的速度,并且增强了安全性,如加强了密钥交换的安全性。
- 例如,TLS 1.3 使用了更安全的密钥交换算法,如基于椭圆曲线的 Diffie - Hellman 密钥交换(ECDHE),这种算法能够有效抵抗中间人攻击等安全威胁。
- 应用场景
- TLS 的应用范围非常广泛。它不仅用于 Web 安全,还用于电子邮件安全(如 IMAP、POP3 和 SMTP 协议的安全版本)、虚拟专用网络(VPN)等众多网络通信场景。在现代互联网中,几乎所有需要安全通信的地方都会优先考虑使用 TLS 协议。
SSL (Secure Sockets Layer)安全套接层协议
- 发展历程
- SSL 最初是由网景公司(Netscape)在 1994 年开发的,目的是为了保障网络通信的安全,特别是在 Web 浏览器和服务器之间的通信安全。SSL 1.0 由于存在安全漏洞从未发布,SSL 2.0 在 1995 年发布,但也有安全问题。SSL 3.0 于 1996 年发布,它修复了许多 SSL 2.0 的问题,成为当时比较流行的安全协议。
- 工作原理
- SSL 协议在应用层和传输层之间工作。它通过使用公钥和私钥加密技术来保证通信的保密性。当客户端(如浏览器)与服务器建立连接时,首先会进行 SSL 握手过程。
- 在握手阶段,客户端向服务器发送客户端支持的加密算法列表等信息。服务器从列表中选择一种加密算法,并将服务器的证书发送给客户端。客户端验证服务器证书的合法性,例如检查证书是否由可信任的证书颁发机构(CA)颁发,证书是否过期等。
- 如果证书验证通过,客户端和服务器就会协商出一个对称加密密钥。之后的数据传输就使用这个对称密钥进行加密,这样可以保证数据在传输过程中的保密性,因为只有客户端和服务器知道这个对称密钥。
- 应用场景
- SSL 主要应用于 Web 安全。例如,在早期的 https(Hypertext Transfer Protocol Secure)网站访问中,就是通过 SSL 协议来加密浏览器和服务器之间的数据传输。这使得用户在网站上输入的敏感信息,如登录密码、信用卡信息等在传输过程中不会被窃取。
两者关系和区别
- 关系
- TLS 可以看作是 SSL 的升级版。TLS 协议在设计上借鉴了 SSL 协议的很多理念,并且在 SSL 的基础上进行了改进和扩展,以应对不断出现的新的安全威胁和网络通信需求。
- 区别
- 安全性:TLS 在安全性能上比 SSL 更优越。例如,SSL 3.0 存在 POODLE(Padding Oracle On Downgraded Legacy Encryption)攻击漏洞,而 TLS 协议通过不断更新版本来修复类似的安全漏洞。TLS 1.3 更是在密钥交换等关键环节采用了更先进的加密技术,提供了更强的安全性。
- 兼容性:SSL 是比较早期的协议,在一些旧的系统和软件中有应用。但随着网络安全标准的提高,现代的应用和系统更倾向于使用 TLS 协议。TLS 在兼容性方面也在不断扩展,能够更好地与各种新的网络技术和应用场景相结合。
目前常用的是 TLS 1.2 和 TLS 1.3; SSL不多见了
HTTPS 工作流程
- Client 发起请求(端口443)
- Server 返回公钥证书
- Client 验证证书
- Client 生成对称密钥,用公钥加密后发给Server
- Server使用私钥解密,得到对称密钥
- C/S双方使用对称密钥:
- 加密明文并发送
- 解密密文得到明文
For TLS 1.2


For TLS 1.3


TLS

证书签名与验证
颁发证书的过程
- 撰写证书元数据: 包括
签发人(Issuer), 地址, 签发时间, 有效期 等, 还包括证书持有者(Owner)基本信息, 比如 DN(DNS Name, 即证书生效的域名), Owner 公钥 等信息 - 使用通用的 Hash 算法(如SHA-256)对证书元数据计算生成数字摘要
- 使用 签发人 Issuer (CA)的私钥 对该数字摘要进行加密, 生成一个加密的数字摘要, 也就是Issuer的数字签名
- 将数字签名附加到数字证书上, 变成一个签过名的数字证书
将签过名的数字证书与 Issuer (CA)的公钥, 一同发给证书使用者(注意, 将公钥主动发给使用者是一个形象的说法, 通常系统或者浏览器都内置 Issuer (CA)的公钥 为可信任的根证书)
所以 CA的私钥是绝对不能被泄露, 它作为互联网被信任的基础
验证证书的过程
- 证书使用者获通过某种途径(如浏览器访问)获取到该数字证书, 解压后分别获得证书元数据 和数字签名,(Owner)的公钥等
- 使用同样的Hash算法计算证书元数据的数字摘要
- 使用 签发人 Issuer (CA)的公钥 对数字签名进行解密, 得到 解密后的数字摘要
从第1步获得的数字摘要值, 再对比 2 和 3 两个步骤得到的数字摘要值; 如果相同, 则说明这个数字证书确实是被 签发人Issuer 验证过合法证书, 证书中的信息
最主要的是确定 Owner 的公钥是可信的, 此后通信 信任使用该公钥解密对方私钥加密的数据
in short 握手时服务端需要提供自己的公钥 和被 ca签发过的数字摘要; 关键在与客户端需要一个可信任 Issuer(CA)
证书链
在 chrome 证书信息-> 证书路径, 可以看到证书的签发链.
例, 百度的网站:
GlobalSign Root CA -> GlobalSign Organization Validation CA -> baidu.com

这个路径可以抽象为三个级别:
end-user: 即 baidu.com, 该证书包含百度的公钥, 访问者就是使用该公钥将数据加密后再传输给百度, 即在 HTTPS 中使用的证书
intermediates: 即上文提到的 签发人 Issuer, 用来认证公钥持有者身份的证书, 负责确认 HTTPS 使用的 end-user 证书确实是来源于百度; 这类 intermediates 证书可以有很多级, 也就是说 签发人 Issuer 可能会有有很多级
root: 可以理解为最高级别的签发人 Issuer, 负责认证 intermediates 身份的合法性
in short 其实就是一个可信任的链条, 最终的目的就是为了保证 end-user 证书是可信的, 该证书的公钥也就是可信的.
参考: 关于证书链的一点认知
TLS 握手步骤
(0) Client 与 Server 之间建立 (TCP 三次握手) 连接
(1) Client 向 Server 发送 "client hello" 消息, 里面包含了安全相关的信息, 例如SSL/TLS 版本号, Client 支持的加密套件 (CipherSuite); "client hello" 消息还包含了一个随机数client random, 用于通信密钥的计算;
(可选)SSL/TLS 协议还允许 "client hello" 消息包含 Client 所支持的压缩算法
(2) Server 回复一条 "server hello" 消息, 里面包含了加密套件(Server 从 "client hello" 消息的 CipherSuites 列表中选择其中一个),session id 和 另一个随机数server random; Server 还会在消息中附带自己的数字证书;
(可选)如果 Server 需要 Client 的数字证书进行客户端认证, 会向 Client 发送 "client certificate request" 请求消息, 里面包含了Server 所支持的证书类型和认可的证书颁发机构 CA (双向认证, 同认证客户端同理)
(3) Client 收到 "server hello",验证 Server 端的数字证书, 并得到证书中Server 端的公钥
(关键这里客户端有可信任的证书签发者Issuer, 验证服务端的证书)
(4) Client 向 Server 发送第三个随机数 pre-master secret; 与之前不同,这次的随机数使用了 Server 的公钥加密 (非对称加密); 现在双方同时拥有这三个随机数client random,server random,premaster secret, 可以用来计算生成共同的通信密钥 master secret 用于加密后面传输的业务数据;
(5 - 可选) 如果收到 Server 端发来的 "client certificate request" 请求消息, Client 会向 Server 发送一个使用 Client 自己的私钥加密过的随机数 (暂时记作 secret-A), 附带 Client 的数字证书; 或者发送一个 "no digital certificate alert" 无证书警告, 这种情况下基本可以认为 SSL/TLS 握手失败;
(6 - 可选) Server 验证 Client 发送过来的数字证书, 并得到证书中公钥对 Client 进行身份认证 (通过公钥解密上面那个 secret-A);
(7) Client 向 Server 发送 "finished" 消息, 使用第 4 步中计算出来的密钥进行加密传输 (对称加密), 这表示 Client 端握手阶段已经完成;
(8) Server 也向 Client 发送 "finished" 消息, 使用第 4 步中计算出来的密钥进行加密传输 (对称加密), 这表示 Server 端握手阶段完成;
(9) SSL/TLS 握手阶段完成, 接下来双方通信的消息都会使用协商出来的密钥进行加密 (对称加密)
Content Type: Handshake (22)

证书的应用之一 —— TCP&SSL通信实例及协议分析(中)
每一条消息都会包含有ContentType,Version,HandshakeType等信息;
ContentType 指示SSL通信处于哪个阶段, 是握手(Handshake), 开始加密传输(ChangeCipherSpec)还是正常通信(Application)等, 见下表
| Hex | Dec | Type |
|---|---|---|
| 0x14 | 20 | ChangeCipherSpec |
| 0x15 | 21 | Alert |
| 0x16 | 22 | Handshake |
| 0x17 | 23 | Application |
Handshake Type是在handshanke阶段中的具体哪一步, 见下表
| Code | Description |
|---|---|
| 0 | HelloRequest |
| 1 | ClientHello |
| 2 | ServerHello |
| 11 | Certificate |
| 12 | ServerKeyExchange |
| 13 | CertificateRequest |
| 14 | ServerHelloDone |
| 15 | CertificateVerify |
| 16 | ClientKeyExchange |
| 20 | Finished |
关于双向认证
所有通信中,涉及两个端点,即浏览器和它所连接的网站(即客户端和服务器)。 在单向SSL身份认证过程中,仅验证一个端点(服务器)的身份。
双向认证是指在SSL握手过程中将同时验证客户端和服务器的身份,所以双向认证SSL证书至少包括两个或两个以上的证书,一个是服务器证书,另一个或多个是客户端证书(即个人认证证书)。
主流的证书格式
主流的SSL证书格式:
DER、CER,文件是二进制格式,只保存证书,不保存私钥。
PEM,一般是文本格式,可保存证书,可保存私钥。
Privacy Enhanced Mail,一般为文本格式,以 -----BEGIN... 开头,以 -----END... 结尾。中间的内容是 BASE64 编码。这种格式可以保存证书和私钥,有时我们也把PEM 格式的私钥的后缀改为 .key 以区别证书与私钥
CRT,可以是二进制格式,可以是文本格式,与 DER 格式相同,不保存私钥。
PFX P12,二进制格式,同时包含证书和私钥,一般有密码保护。
JKS,二进制格式,同时包含证书和私钥,一般有密码保护。
各服务器平台使用的证书格式:
| Nginx | pem |
|---|---|
| Apache | pem |
| IIS | pfx/pkcs12 |
| Tomcat | jks |
Netty For TLS 实例
证书生成
CA 根证书生成
或者称为 Issuer , 它可以签发证书, 作为客户端和服务端共同信任者
- 创建一个私钥
生成一个安全的私钥。这个私钥将用于签署根证书以及将来由该根CA签发的所有其他证书。为了确保安全性,使用足够长的密钥长度。
openssl genpkey -algorithm RSA -out root_ca.key -aes256 -pkeyopt rsa_keygen_bits:4096
# 不加密
# openssl genpkey -algorithm RSA -out root_ca.key -pkeyopt rsa_keygen_bits:2048
输入私钥密码
- 创建自签名的根证书
使用刚刚生成的私钥来创建一个自签名的根证书。这一步骤不仅会生成证书本身,还会收集有关颁发者的详细信息,这些信息将嵌入到证书中。
openssl req -x509 -new -nodes -key root_ca.key -sha256 -days 36500 -out root_ca.crt
req是用来处理证书请求的命令。-x509表示要创建一个X.509格式的自签名证书。-new表示这是一个新的证书请求。-nodes表示不加密私钥(仅适用于这次命令,因为私钥已经加密)。-key root_ca.key指定了用于签名的私钥。-sha256指定了哈希算法。-days 3650指定了证书的有效期为10年。-out root_ca.crt指定了输出文件名为root_ca.crt。
在运行该命令时,你会被要求填写一系列问题,这些问题的答案将作为证书的主题字段(Subject)的一部分。例如:
- Country Name (2 letter code): 输入国家代码,如
CN。 - State or Province Name (full name): 输入省份或州的全名。
- Locality Name (eg, city): 输入城市名。
- Organization Name (eg, company): 输入组织名称。
- Organizational Unit Name (eg, section): 输入部门名称。
- Common Name (e.g. server FQDN or YOUR name): (注意这个) 输入通用名称,通常是组织的域名或描述性名称,对于根证书而言,可以是类似于
Root CA的字符串。 - Email Address: 可选,可以留空。
- 验证其内容
openssl x509 -in root_ca.crt -text -noout
- 客户端信任
至此
root_ca.crt是CA 自签名的带公钥的证书root_ca.key是CA的私钥(绝对不能泄露)
为了让客户端信任这个新创建的根证书,需要将CA公钥安装到目标系统的受信任根证书存储中。
例如,在Windows上,你可以双击 root_ca.crt 文件,然后按照提示选择“安装证书”,并将安装位置设置为“受信任的根证书颁发机构
CA 签署/颁发服务端证书
- 生成目标实体的私钥
openssl genpkey -algorithm RSA -out server.key -aes256 -pkeyopt rsa_keygen_bits:2048
- 生成目标实体的签发请求 (CSR)
使用刚刚生成的私钥来创建一个证书签名请求(CSR)。CSR包含了申请者的公钥以及一些识别信息,如组织名称、域名等。
openssl req -new -key server.key -out server.csr
# openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/C=CN/ST=State/L=City/O=Organization/CN=your.server.domain"
- 使用CA根证书的私钥 签署CSR
openssl x509 -req -in server.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial -out server.crt -days 7300 -sha256
# openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 -sha256
各文件说明
至此有6个文件分别是:
1. root_ca.crt (根CA证书公钥) - 自签名 公开 双方信任
- 描述:这是由CA自身创建并签署的自签名证书,作为信任链的起点。根证书通常包含CA的公钥、名称和其他元数据,并且是由CA私钥签名的
- 用途:根证书用于验证由该CA签发的所有下级证书的真实性。它通常被预装在操作系统、浏览器或其他应用程序的信任库中,作为受信任的根证书机构
2. root_ca.key (根CA证书私钥)
- 描述:这是与根CA证书配对的私钥,用于签署其他证书。私钥是高度敏感的信息,不应公开或共享。
- 用途:根CA私钥用于签署中间CA证书、服务器证书、客户端证书等。它是建立信任链的基础。
- 安全性:私钥应存储在安全的地方,最好是在离线环境中,只有在需要签署新证书时才连接到网络。 它一旦被攻破,所有依赖它的证书都将失去信任。
3. root_ca.srl (序列号文件)
- 描述:这是一个包含十六进制正整数的文件,用于记录已签发证书的序列号。每次签署新证书时,OpenSSL会从这个文件中读取当前的最大序列号,并为新证书分配一个递增的序列号。
4. server.crt (服务器公钥)
- 描述:这是由CA签署的服务器证书,包含服务器的公钥以及关于证书的一些信息,如版本、序列号、签名算法、颁发者、持有者、有效期等。服务器证书用于加密客户端与服务器之间的通信,确保数据传输的安全性。
- 用途:服务器证书安装在Web服务器上,用于HTTPS协议下的安全通信。客户端通过验证服务器证书的有效性来确认服务器的身份,并使用证书中的公钥进行加密通信。
- 格式:常见的格式包括PEM(以
-----BEGIN CERTIFICATE-----开头,-----END CERTIFICATE-----结尾)和DER(二进制格式)。Linux系统通常使用.crt文件扩展名,而Windows系统可能使用.cer
5. server.csr (证书签名请求)
- 描述:这是证书申请者在申请数字证书时生成的文件,包含了公钥和标识名称(Distinguished Name, DN),如国家、省份、城市、组织名称、通用名称(通常是域名)等。CSR是以
-----BEGIN CERTIFICATE REQUEST-----开头,-----END CERTIFICATE REQUEST-----结尾的Base64编码格式。 - 用途:CSR文件提交给CA后,CA会使用其根证书私钥对CSR中的信息进行签名,生成正式的数字证书。CSR文件本身不是证书,而是申请证书的请求。
- 生成:CSR文件通常由服务器管理员使用OpenSSL等工具生成,同时生成相应的私钥。生成CSR时,必须确保私钥的安全性,因为CSR文件中不包含私钥
6. server.key (服务器私钥)
- 描述:这是与服务器证书配对的私钥,用于解密客户端发送的加密信息。私钥是高度敏感的信息,不应公开或共享。
- 用途:服务器私钥用于解密通过TLS/SSL协议传输的数据。在HTTPS通信中,客户端使用服务器证书中的公钥加密数据,服务器则使用私钥解密这些数据。
- 格式:常见的格式包括PEM(以
-----BEGIN RSA PRIVATE KEY-----或-----BEGIN PRIVATE KEY-----开头,-----END RSA PRIVATE KEY-----或-----END PRIVATE KEY-----结尾)和DER(二进制格式)。Linux系统通常使用.key文件扩展名
每个角色验证需要
- **客户端需要
root_ca.crt:根 CA 的公钥证书。客户端将使用此证书来验证服务器的证书是否由可信的 CA 签发。
- **服务端端需要
server.key:服务器的私钥。服务器将使用此私钥来解密客户端发送的加密数据,并签署响应。server.crt:服务器的公钥证书,由根 CA 签发。服务器将使用此证书向客户端证明其身份。root_ca.crt(可选, 双向验证必选):如果你的服务器证书是由中间 CA 签发的,你可以将根 CA 证书或中间 CA 证书链一起加载到服务器的信任库中,以便客户端可以验证整个证书链。
在Netty 使用
证书的加载
-
公钥加载
.pem, .crt, .cer: 通常以-----BEGIN CERTIFICATE-----开头,以-----END CERTIFICATE-----结尾。它既可以包含公钥证书,也可以包含私钥。Java本身支持 pem -
私钥加载
.key: openssl 默认给私钥加密了 , 将其转为不加密的, 可使用 bouncycastle加载之
# **转换为 PKCS#8 格式 (PEM 编码)**
openssl pkcs8 -topk8 -inform PEM -in server.key -outform PEM -nocrypt -out server_pkcs8.key
# **转换为 PKCS#8 格式 (DER 编码)** 二进制格式
openssl pkcs8 -topk8 -inform PEM -in server.key -outform DER -nocrypt -out server_pkcs8.der
- 使用 bouncycastle 加载 pkcs8 私钥
implementation("cn.hutool:hutool-all:5.8.16")
implementation("org.bouncycastle:bcprov-jdk15on:1.70")
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
public static void main(String[] args) throws Exception {
final String ca_cet = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\root_ca.crt";
final String server_crt = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\server.crt";
final String server_key = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\server.key";
final String server_pkcs8_key = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\server_pkcs8.key";
PublicKey root_caKey = PemUtil.readPemPublicKey(FileUtil.getInputStream(ca_cet));
System.out.println("root_caKey ==> "+root_caKey );
Certificate certificate = SecureUtil.readX509Certificate(FileUtil.getInputStream(server_crt));
System.out.println("X509C ==> "+root_caKey );
PublicKey server_crtKey = PemUtil.readPemPublicKey(FileUtil.getInputStream(server_crt));
System.out.println("server_crtKey ==> "+server_crtKey );
PrivateKey privateKey = PemUtil.readPemPrivateKey(FileUtil.getInputStream(server_pkcs8_key));
System.out.println("server_pkcs8 ==> "+privateKey );
}
- 使用 bouncycastle 如何 加载默认 openssl 加密的
server.key?
public static PrivateKey loadEncryptedPrivateKeyFromPem(String pemFile, char[] password) throws Exception {
try (Reader reader = new FileReader(pemFile);
PEMParser pemParser = new PEMParser(reader)) {
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
if (object instanceof PEMEncryptedKeyPair) {
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) object;
InputDecryptorProvider decryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password);
KeyPair kp = converter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptorProvider));
return kp.getPrivate();
} else {
throw new IllegalArgumentException("Unsupported encrypted key type: " + object.getClass().getName());
}
}
}
单向验证的TLS
客户端单向了验证服务端
服务端
public static void main(String[] args) throws Exception {
NioEventLoopGroup boosGroup = new NioEventLoopGroup();
final ServerBootstrap serverBootstrap = new ServerBootstrap();
int port = 19977;
// 1. 加载 PEM 私钥, 2. 加载 证书
final String server_crt = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\server.crt";
final String server_pkcs8_key = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\server_pkcs8.key";
Certificate certificate = SecureUtil.readX509Certificate(FileUtil.getInputStream(server_crt));
PrivateKey privateKey = PemUtil.readPemPrivateKey(FileUtil.getInputStream(server_pkcs8_key));
// 3. 创建 KeyStore 并将私钥和证书添加到其中
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
keyStore.setKeyEntry("alias", privateKey, "password".toCharArray(), new Certificate[]{certificate});
// 4. 创建 KeyManagerFactory 并初始化
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());
// 5. 构建 SslContext
final SslContext sslContext = SslContextBuilder.forServer(kmf)
.build();
serverBootstrap
.group(boosGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
// 添加 SSL/TLS 处理器
ch.pipeline().addLast(sslContext.newHandler(ch.alloc()));
ch.pipeline().addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new ServerHandler());
}
});
ChannelFuture f = serverBootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
} else {
System.err.println("端口[" + port + "]绑定失败!");
}
});
//下面会进行阻塞, 等待服务器连接关闭之后 main 方法退出, 程序结束;
f.channel().closeFuture().sync();
}
客户端
public static void main(String[] args) throws Exception {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
String host = "192.168.20.130";
int port = 19977;
// 1. 加载 CA 证书
final String ca_cet = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\root_ca.crt";
Certificate certificate = SecureUtil.readX509Certificate(FileUtil.getInputStream(ca_cet));
// 2. 创建 KeyStore 并将 CA 证书添加到信任库中
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", certificate);
// 3. 创建 TrustManagerFactory 并初始化
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// 4. 构建 SslContext
SslContext build = SslContextBuilder.forClient()
.trustManager(tmf)
.build();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler( new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("客户端接收: "+msg);
}
})
.connect(host, port).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
future.channel().pipeline()
.addFirst(new StringEncoder())
.addFirst(new StringDecoder())
.addFirst( build.newHandler(future.channel().alloc() ));
final String helloToServer = "珠玉买歌笑,糟糠养贤才。方知黄鹄举,千里独徘徊。"+System.currentTimeMillis();
future.channel().writeAndFlush(helloToServer);
}
});
// group.shutdownGracefully();
}
Android 上使用 java.security.KeyStore 时,可能会遇到 KeyStoreException: JKS not found 的错误。这是因为 Android 的安全框架与标准 Java 不同,Android 并不支持 JKS(Java KeyStore)格式。相反,Android 支持其他类型的密钥库,如 BKS(Bouncy Castle KeyStore)和 PKCS12。
private SslContext getSslContext() throws Exception {
Certificate caCert = CertificateUtils.getCertificate();
KeyStore trustStore = KeyStore.getInstance("BKS"); // 使用 BKS 或 PKCS12
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SslContext build = SslContextBuilder.forClient()
.trustManager(tmf)
.build();
return build;
}
抓包看看

双向验证的TLS
其实同服务端一样的逻辑 1.生成客户端私钥, 2.生成客户端CRS, 3.用CA根证书签发客户端的公钥
客户端证书准备
生成客户端私钥
openssl genpkey -algorithm RSA -out client.key -aes256 -pkeyopt rsa_keygen_bits:2048
转换为pkcs#8 格式
openssl pkcs8 -topk8 -inform PEM -in client.key -outform PEM -nocrypt -out client_pkcs8.key
创建客户端的证书签名请求 (CSR)
openssl req -new -key client.key -out client.csr
使用根 CA 签名客户端的 CSR
openssl x509 -req -in client.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial -out client.crt -days 7300 -sha256
服务端
public static void main(String[] args) throws Exception {
NioEventLoopGroup boosGroup = new NioEventLoopGroup();
final ServerBootstrap serverBootstrap = new ServerBootstrap();
int port = 19977;
// 1. 加载 PEM 私钥, 2. 加载 证书
final String server_crt = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\server.crt";
final String server_pkcs8_key = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\server_pkcs8.key";
Certificate certificate = SecureUtil.readX509Certificate(FileUtil.getInputStream(server_crt));
PrivateKey privateKey = PemUtil.readPemPrivateKey(FileUtil.getInputStream(server_pkcs8_key));
// 3. 创建 KeyStore 并将私钥和证书添加到其中
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
keyStore.setKeyEntry("alias", privateKey, "password".toCharArray(), new Certificate[]{certificate});
// 4. 创建 KeyManagerFactory 并初始化
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());
//双向认证 CA根证书
final String root_ca = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\root_ca.crt";
X509Certificate caCertificate = (X509Certificate) SecureUtil.readX509Certificate(FileUtil.getInputStream(root_ca));
// 5. 构建 SslContext
final SslContext sslContext = SslContextBuilder.forServer(kmf)
.trustManager(caCertificate)
.clientAuth(io.netty.handler.ssl.ClientAuth.REQUIRE)//双向认证 > 配置要求客户端也认证
.build();
serverBootstrap
.group(boosGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
// 添加 SSL/TLS 处理器
ch.pipeline().addLast(sslContext.newHandler(ch.alloc()));
ch.pipeline().addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new ServerHandler());
}
});
ChannelFuture f = serverBootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
} else {
System.err.println("端口[" + port + "]绑定失败!");
}
});
//下面会进行阻塞, 等待服务器连接关闭之后 main 方法退出, 程序结束;
f.channel().closeFuture().sync();
}
客户端
public static void main(String[] args) throws Exception {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
String host = "192.168.20.130";
int port = 19977;
// 1. 加载 CA 证书
final String ca_cet = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\root_ca.crt";
Certificate certificate = SecureUtil.readX509Certificate(FileUtil.getInputStream(ca_cet));
// 2. 创建 KeyStore 并将 CA 证书添加到信任库中
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", certificate);
// 3. 创建 TrustManagerFactory 并初始化
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// 双向认证 > 客户端证书
final String client_crt = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\client.crt";
final String client_pkcs8_key = "E:\\content-for-work\\2024-12XXXXAPP\\tls_test\\client_pkcs8.key";
// 4. 构建 SslContext
SslContext build = SslContextBuilder.forClient()
.keyManager(FileUtil.file(client_crt), FileUtil.file(client_pkcs8_key))
.trustManager(tmf)
.build();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler( new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("客户端接收: "+msg);
}
})
.connect(host, port).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
future.channel().pipeline()
.addFirst(new StringEncoder())
.addFirst(new StringDecoder())
.addFirst( build.newHandler(future.channel().alloc() ));
final String helloToServer = "珠玉买歌笑,糟糠养贤才。方知黄鹄举,千里独徘徊。"+System.currentTimeMillis();
future.channel().writeAndFlush(helloToServer);
}
});
// group.shutdownGracefully();
}
最后, 抓包看看


浙公网安备 33010602011771号