当今互联网安全的基石 - TLS/SSL
LSTransport Layer Security传输层安全协议发展历程TLS 是 SSL 协议的继任者。由于 SSL 协议存在一些安全漏洞并且随着网络安全需求的不断提高IETFInternet 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 安全。例如在早期的 httpsHypertext Transfer Protocol Secure网站访问中就是通过 SSL 协议来加密浏览器和服务器之间的数据传输。这使得用户在网站上输入的敏感信息如登录密码、信用卡信息等在传输过程中不会被窃取。两者关系和区别关系TLS 可以看作是 SSL 的升级版。TLS 协议在设计上借鉴了 SSL 协议的很多理念并且在 SSL 的基础上进行了改进和扩展以应对不断出现的新的安全威胁和网络通信需求。区别安全性TLS 在安全性能上比 SSL 更优越。例如SSL 3.0 存在 POODLEPadding Oracle On Downgraded Legacy Encryption攻击漏洞而 TLS 协议通过不断更新版本来修复类似的安全漏洞。TLS 1.3 更是在密钥交换等关键环节采用了更先进的加密技术提供了更强的安全性。兼容性SSL 是比较早期的协议在一些旧的系统和软件中有应用。但随着网络安全标准的提高现代的应用和系统更倾向于使用 TLS 协议。TLS 在兼容性方面也在不断扩展能够更好地与各种新的网络技术和应用场景相结合。目前常用的是 TLS 1.2 和 TLS 1.3; SSL不多见了HTTPS 工作流程Client 发起请求端口443Server 返回公钥证书Client 验证证书Client 生成对称密钥用公钥加密后发给ServerServer使用私钥解密得到对称密钥C/S双方使用对称密钥加密明文并发送解密密文得到明文For TLS 1.2For TLS 1.3TLS证书签名与验证颁发证书的过程撰写证书元数据: 包括签发人(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)证书的应用之一 —— TCPSSL通信实例及协议分析(中)每一条消息都会包含有ContentType,Version,HandshakeType等信息;ContentType 指示SSL通信处于哪个阶段, 是握手(Handshake), 开始加密传输(ChangeCipherSpec)还是正常通信(Application)等, 见下表HexDecType0x1420ChangeCipherSpec0x1521Alert0x1622Handshake0x1723ApplicationHandshake Type是在handshanke阶段中的具体哪一步, 见下表CodeDescription0HelloRequest1ClientHello2ServerHello11Certificate12ServerKeyExchange13CertificateRequest14ServerHelloDone15CertificateVerify16ClientKeyExchange20Finished关于双向认证所有通信中涉及两个端点即浏览器和它所连接的网站即客户端和服务器。 在单向SSL身份认证过程中仅验证一个端点服务器的身份。双向认证是指在SSL握手过程中将同时验证客户端和服务器的身份所以双向认证SSL证书至少包括两个或两个以上的证书一个是服务器证书另一个或多个是客户端证书即个人认证证书。主流的证书格式主流的SSL证书格式:DER、CER文件是二进制格式只保存证书不保存私钥。PEM一般是文本格式可保存证书可保存私钥。Privacy Enhanced Mail一般为文本格式以 -----BEGIN... 开头以 -----END... 结尾。中间的内容是 BASE64 编码。这种格式可以保存证书和私钥有时我们也把PEM 格式的私钥的后缀改为 .key 以区别证书与私钥CRT可以是二进制格式可以是文本格式与 DER 格式相同不保存私钥。PFX P12二进制格式同时包含证书和私钥一般有密码保护。JKS二进制格式同时包含证书和私钥一般有密码保护。各服务器平台使用的证书格式:NginxpemApachepemIISpfx/pkcs12TomcatjksNetty 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.crtreq是用来处理证书请求的命令。-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 /CCN/STState/LCity/OOrganization/CNyour.server.domain使用CA根证书的私钥签署CSRopenssl 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系统可能使用.cer5.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 ChannelInitializerNioSocketChannel() { 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 SimpleChannelInboundHandlerString() { 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 并不支持 JKSJava KeyStore格式。相反Android 支持其他类型的密钥库如 BKSBouncy 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 签名客户端的 CSRopenssl 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 ChannelInitializerNioSocketChannel() { 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 SimpleChannelInboundHandlerString() { 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();
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2473140.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!