一文学习和实践 当下互联网安全的基石 - TLS 和 SSL

\[N_OSI 模型.TCP 协议.封包\]

\[Java TLS Socket\]

What is SSL/TLS ?

TLS(Transport Layer Security)即传输层安全协议,SSL(Secure Sockets Layer)即安全套接层协议。它们都是用于在网络通信中保障数据安全和隐私的加密协议。

\[TLS\&SSL协议\]

TLS(Transport Layer Security)传输层安全协议

  1. 发展历程
  • 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,每一个新版本都在安全性和性能等方面有所提升。
  1. 工作原理
  • TLS 的工作原理与 SSL 类似,也包括握手阶段。在 TLS 握手过程中,客户端和服务器会协商协议版本、加密算法套件、交换密钥等。TLS 1.3 相比之前的版本,简化了握手过程,减少了握手消息的往返次数,从而提高了连接建立的速度,并且增强了安全性,如加强了密钥交换的安全性。
  • 例如,TLS 1.3 使用了更安全的密钥交换算法,如基于椭圆曲线的 Diffie - Hellman 密钥交换(ECDHE),这种算法能够有效抵抗中间人攻击等安全威胁。
  1. 应用场景
  • TLS 的应用范围非常广泛。它不仅用于 Web 安全,还用于电子邮件安全(如 IMAP、POP3 和 SMTP 协议的安全版本)、虚拟专用网络(VPN)等众多网络通信场景。在现代互联网中,几乎所有需要安全通信的地方都会优先考虑使用 TLS 协议。

SSL (Secure Sockets Layer)安全套接层协议

  1. 发展历程
  • SSL 最初是由网景公司(Netscape)在 1994 年开发的,目的是为了保障网络通信的安全,特别是在 Web 浏览器和服务器之间的通信安全。SSL 1.0 由于存在安全漏洞从未发布,SSL 2.0 在 1995 年发布,但也有安全问题。SSL 3.0 于 1996 年发布,它修复了许多 SSL 2.0 的问题,成为当时比较流行的安全协议。
  1. 工作原理
  • SSL 协议在应用层和传输层之间工作。它通过使用公钥和私钥加密技术来保证通信的保密性。当客户端(如浏览器)与服务器建立连接时,首先会进行 SSL 握手过程。
  • 在握手阶段,客户端向服务器发送客户端支持的加密算法列表等信息。服务器从列表中选择一种加密算法,并将服务器的证书发送给客户端。客户端验证服务器证书的合法性,例如检查证书是否由可信任的证书颁发机构(CA)颁发,证书是否过期等。
  • 如果证书验证通过,客户端和服务器就会协商出一个对称加密密钥。之后的数据传输就使用这个对称密钥进行加密,这样可以保证数据在传输过程中的保密性,因为只有客户端和服务器知道这个对称密钥。
  1. 应用场景
  • SSL 主要应用于 Web 安全。例如,在早期的 https(Hypertext Transfer Protocol Secure)网站访问中,就是通过 SSL 协议来加密浏览器和服务器之间的数据传输。这使得用户在网站上输入的敏感信息,如登录密码、信用卡信息等在传输过程中不会被窃取。

两者关系和区别

  1. 关系
  • TLS 可以看作是 SSL 的升级版。TLS 协议在设计上借鉴了 SSL 协议的很多理念,并且在 SSL 的基础上进行了改进和扩展,以应对不断出现的新的安全威胁和网络通信需求。
  1. 区别
  • 安全性: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 工作流程

  1. Client 发起请求(端口443)
  2. Server 返回公钥证书
  3. Client 验证证书
  4. Client 生成对称密钥,用公钥加密后发给Server
  5. Server使用私钥解密,得到对称密钥
  6. C/S双方使用对称密钥:
    • 加密明文并发送
    • 解密密文得到明文

For TLS 1.2

For TLS 1.3

TLS

证书签名与验证

颁发证书的过程

  1. 撰写证书元数据 : 包括 签发人(Issuer), 地址, 签发时间, 有效期 等, 还包括证书持有者(Owner)基本信息, 比如 DN(DNS Name, 即证书生效的域名), Owner 公钥 等信息
  2. 使用通用的 Hash 算法(如SHA-256)对证书元数据计算生成数字摘要
  3. 使用 签发人 Issuer (CA)的私钥 对该数字摘要 进行加密, 生成一个加密的数字摘要 , 也就是Issuer的数字签名
  4. 将数字签名 附加到数字证书上, 变成一个签过名的数字证书
    将签过名的数字证书 与 Issuer (CA)的公钥, 一同发给证书使用者(注意, 将公钥主动发给使用者是一个形象的说法, 通常系统或者浏览器都内置 Issuer (CA)的公钥 为可信任的根证书)

所以 CA的私钥是绝对不能被泄露, 它作为互联网被信任的基础

验证证书的过程

  1. 证书使用者获通过某种途径(如浏览器访问)获取到该数字证书, 解压后分别获得证书元数据 和数字签名 ,(Owner)的公钥等
  2. 使用同样的Hash算法计算证书元数据的数字摘要
  3. 使用 签发人 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 , 它可以签发证书, 作为客户端和服务端共同信任者

  1. 创建一个私钥
    生成一个安全的私钥。这个私钥将用于签署根证书以及将来由该根CA签发的所有其他证书。为了确保安全性,使用足够长的密钥长度。
sh 复制代码
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

输入私钥密码

  1. 创建自签名的根证书
    使用刚刚生成的私钥来创建一个自签名的根证书。这一步骤不仅会生成证书本身,还会收集有关颁发者的详细信息,这些信息将嵌入到证书中。
sh 复制代码
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: 可选,可以留空。
  1. 验证其内容
sh 复制代码
openssl x509 -in root_ca.crt -text -noout
  1. 客户端信任

至此

  • root_ca.crt 是CA 自签名的带公钥的证书
  • root_ca.key 是CA的私钥(绝对不能泄露)

为了让客户端信任这个新创建的根证书,需要将CA公钥安装到目标系统的受信任根证书存储中。

例如,在Windows上,你可以双击 root_ca.crt 文件,然后按照提示选择"安装证书",并将安装位置设置为"受信任的根证书颁发机构

CA 签署/颁发服务端证书

  1. 生成目标实体的私钥
sh 复制代码
openssl genpkey -algorithm RSA -out server.key -aes256 -pkeyopt rsa_keygen_bits:2048
  1. 生成目标实体的签发请求 (CSR)
    使用刚刚生成的私钥来创建一个证书签名请求(CSR)。CSR包含了申请者的公钥以及一些识别信息,如组织名称、域名等。
sh 复制代码
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"
  1. 使用CA根证书的私钥 签署CSR
sh 复制代码
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签发的所有下级证书的真实性。它通常被预装在操作系统、浏览器或其他应用程序的信任库中,作为受信任的根证书机构
  1. root_ca.key (根CA证书私钥)
  • 描述:这是与根CA证书配对的私钥,用于签署其他证书。私钥是高度敏感的信息,不应公开或共享。
  • 用途:根CA私钥用于签署中间CA证书、服务器证书、客户端证书等。它是建立信任链的基础。
  • 安全性:私钥应存储在安全的地方,最好是在离线环境中,只有在需要签署新证书时才连接到网络。 它一旦被攻破,所有依赖它的证书都将失去信任。
  1. root_ca.srl (序列号文件)
  • 描述:这是一个包含十六进制正整数的文件,用于记录已签发证书的序列号。每次签署新证书时,OpenSSL会从这个文件中读取当前的最大序列号,并为新证书分配一个递增的序列号。
  1. server.crt (服务器公钥)
  • 描述:这是由CA签署的服务器证书,包含服务器的公钥以及关于证书的一些信息,如版本、序列号、签名算法、颁发者、持有者、有效期等。服务器证书用于加密客户端与服务器之间的通信,确保数据传输的安全性。
  • 用途:服务器证书安装在Web服务器上,用于HTTPS协议下的安全通信。客户端通过验证服务器证书的有效性来确认服务器的身份,并使用证书中的公钥进行加密通信。
  • 格式 :常见的格式包括PEM(以 -----BEGIN CERTIFICATE----- 开头,-----END CERTIFICATE----- 结尾)和DER(二进制格式)。Linux系统通常使用 .crt 文件扩展名,而Windows系统可能使用 .cer
  1. server.csr (证书签名请求)
  • 描述 :这是证书申请者在申请数字证书时生成的文件,包含了公钥和标识名称(Distinguished Name, DN),如国家、省份、城市、组织名称、通用名称(通常是域名)等。CSR是以 -----BEGIN CERTIFICATE REQUEST----- 开头,-----END CERTIFICATE REQUEST----- 结尾的Base64编码格式。
  • 用途:CSR文件提交给CA后,CA会使用其根证书私钥对CSR中的信息进行签名,生成正式的数字证书。CSR文件本身不是证书,而是申请证书的请求。
  • 生成:CSR文件通常由服务器管理员使用OpenSSL等工具生成,同时生成相应的私钥。生成CSR时,必须确保私钥的安全性,因为CSR文件中不包含私钥
  1. server.key (服务器私钥)
  • 描述:这是与服务器证书配对的私钥,用于解密客户端发送的加密信息。私钥是高度敏感的信息,不应公开或共享。
  • 用途:服务器私钥用于解密通过TLS/SSL协议传输的数据。在HTTPS通信中,客户端使用服务器证书中的公钥加密数据,服务器则使用私钥解密这些数据。
  • 格式 :常见的格式包括PEM(以 -----BEGIN RSA PRIVATE KEY----- 或 -----BEGIN PRIVATE KEY----- 开头,-----END RSA PRIVATE KEY----- 或 -----END PRIVATE KEY----- 结尾)和DER(二进制格式)。Linux系统通常使用 .key 文件扩展名

每个角色验证需要

  1. **客户端需要
  • root_ca.crt:根 CA 的公钥证书。客户端将使用此证书来验证服务器的证书是否由可信的 CA 签发。
  1. **服务端端需要
  • server.key:服务器的私钥。服务器将使用此私钥来解密客户端发送的加密数据,并签署响应。
  • server.crt:服务器的公钥证书,由根 CA 签发。服务器将使用此证书向客户端证明其身份。
  • root_ca.crt(可选, 双向验证必选):如果你的服务器证书是由中间 CA 签发的,你可以将根 CA 证书或中间 CA 证书链一起加载到服务器的信任库中,以便客户端可以验证整个证书链。

在Netty 使用

证书的加载

  1. 公钥加载 .pem, .crt, .cer : 通常以 -----BEGIN CERTIFICATE----- 开头,以 -----END CERTIFICATE----- 结尾。它既可以包含公钥证书,也可以包含私钥。Java本身支持 pem

  2. 私钥加载 .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")

java 复制代码
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 ?
java 复制代码
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

客户端单向了验证服务端

服务端

java 复制代码
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();
}

客户端

java 复制代码
 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。

java 复制代码
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;
}

抓包看看

!\[Pasted image 20241219145126.png]

双向验证的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

服务端

java 复制代码
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();
    }

客户端

java 复制代码
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();
    }

最后, 抓包看看

相关推荐
leikooo8 小时前
用餐厅和盘子理解栈和堆
java·数据结构·链表·rust
Ivanqhz9 小时前
激活函数在 Transformer 中的作用及各种变体简述
java·linux·数据库·人工智能·深度学习
沙漠之主9 小时前
C++编程教学设计资料:从入门到实战的完整课程方案
java·前端·c++
FPGA小徐10 小时前
PA1 基础设施(2)深度解析:NEMU 中 ITRACE、MTRACE、FTRACE、IRINGBUF、ETRACE、DIFFTEST 全解析
java·服务器·网络
YOLO数据集集合10 小时前
EvoAgent:面向PR研发治理的自进化Multi-Agent Harness系统
java·开发语言·目标检测·agent·自进化
知行EDI10 小时前
知行之桥 MaBang 端口使用指南——Create Order 订单创建篇
java·服务器·数据库
笑鸿的学习笔记10 小时前
C++笔记之大块顺序写
java·c++·笔记
打工仔折腾 AI11 小时前
从 Demo 到生产级 Agent:8 个关键设计机制与 Python 实现拆解
java·jvm·人工智能·后端·python·langchain·ai agent 实战
殷色玫瑰12 小时前
C/C++ 内存管理详解:从内存分布到 new/delete 底层原理
java·linux·c语言·c++
青山木13 小时前
Hot 100 --- 编辑距离
java·数据结构·算法·leetcode·动态规划