OpenJDK8源码分析SSL握手流程

2024-06-07 21:38

本文主要是介绍OpenJDK8源码分析SSL握手流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

准备

说明
  1. 示例项目地址:https://github.com/shenjy24/jackal-ssl
  2. JDK版本:OpenJDK8。
  3. 系统:MAC
  4. IDE: IDEA
安装OpenJDK
  1. 安装JDK
brew tap AdoptOpenJDK/openjdk
brew install caskroom/cask/brew-cask
brew cask install adoptopenjdk8
  1. IDEA更换JDK,使用openjdk替换sun jdk
Wireshark抓包

使用Wireshark抓包可以先了解到SSL通讯的大体流程,可以参考 Wireshark 抓包理解 HTTPS 请求流程 ,这篇文章详细分析了整个HTTPS通讯流程,包括TCP三次握手、SSL握手环节、TCP四次挥手。

SSL通信流程

SSL通信流程主要有以下四步:

  1. 加载密钥库,初始化SSLContext;
  2. 建立Socket连接;
  3. 进行SSL握手;
  4. 发送加密应用数据。
一. 初始化SSLContext
二. 建立Socket
服务端
  1. 创建ServerSocket

服务端使用SSLServerSocketFactoryImpl的createServerSocket方法来创建ServerSocket,该方法主要进行以下两件事:

(1) 调用native方法socketBind绑定服务端地址和端口,调用native方法socketListen进行监听。

(2) 进行SSL信息初始化,获取支持的加密套件和协议版本。

  1. accept()

(1) 通常我们会在循环中调用accept,accept会事先创建Socket,不过还没初始化;调用native方法socketAccept,该方法会发生阻塞,直到有客户端连接,此时返回初始化完毕的Socket,这里该Socket的实现为SSLSocket。具体代码可以参考ServerSocket的implAccept(Socket)方法。

(2) 当有客户端连接到来时,退出阻塞,返回SSLScoket;接着会初始化ServerHandshaker,初始化主要完成以下操作:

  • 将connectionState标记为cs_HANDSHAKE,表示还未经过握手环节。
  • 设置支持的协议版本列表、加密套件列表、是否需要客户端认证等。代码参考SSLSocketImpl的initHandshaker方法。
客户端
  1. 创建Socket

(1) 初始化Session,支持的协议版本、加密套件等;

(2) 使用native方法socketCreate创建Socket;构造URI socket:127.0.0.1:8080,使用native方法socketConnect发起连接;

(3) 初始化ClientHandshaker,与服务端的类似。

  1. 客户端连接成功之后,向服务器发送数据,最终会调用SSLSocketImpl的writeRecord方法。

(1) 判断connectionState是否为cs_HANDSHAKE,是的则调用kickstartHandshake开启握手环节,即发送 client_hello,握手环节下面再仔细分析。

三. SSL握手
时序图

在这里插入图片描述

流程详解
  1. 客户端第一次发送数据时,需要进行SSL握手,调用Handshaker的kickstart方法开启握手流程,此时客户端发送ClientHello消息,主要参数如下:
  • protocolVersion: 支持的最高协议版本
  • sessionId: 会话ID
  • cipherSuites: 支持的加密套件
  • clnt_random: 客户端随机数
  • compression_methods: 压缩算法(可选)
  • extensions: 拓展字段,会携带支持的签名算法列表。另外如果支持EC算法,还会携带相关参数。
  1. 服务端收到客户端的ClientHello之后,会进行以下操作,主要逻辑在ServerHandshaker中的clientHello方法中。

(1) 服务端发送ServerHello消息,主要参数如下:

  • protocolVersion: 从服务端支持的协议版本中选取的,该版本小于等于客户端发送过来的协议版本;
  • svr_random: 服务端随机数
  • sessionId: 会话ID
  • cipherSuite: 客户端发送过来的加密套件列表中选取的
  • compression_method: 压缩方法
  • extensions: 扩展字段

(2) 服务端发送CertificateMsg消息,主要向客户端发送服务端的证书。

(3) 服务端发送ServerKeyExchange消息,ServerKeyExchange是抽象基类,具体子类要看使用哪种密钥交换算法,这里发送的是DH_ServerKeyExchange。

主要参数如下:

  • protocolVersion: 协议版本
  • dh_p、dh_g、dh_Ys: 前两个是DH算法的参数,后一个是DH算法中的公钥
  • signature: 签名
  • preferableSignatureAlgorithm: 选中的签名算法

(4) 服务端发送CertificateRequest消息,请求客户端证书。实际上大多数系统只要求单向认证,此步骤通常情况下会被忽略。

(5) 服务端发送ServerHelloDone消息。

  1. 客户端接收服务端发过来的SererHello消息,主要进行以下处理:设置协商好的协议版本、服务端随机数、加密套件、以及创建Session。

  2. 客户端接收服务端发过来的CertificateMsg消息,进行证书校验。

  3. 客户端接收服务端发过来的ServerKeyExchange消息,利用发过来的DH参数(dh_p,dh_p)创建DHCrypt,主要生成客户端的DH公钥,这些参数用于后续生成主密钥。

  4. 客户端接收服务端发过来的CertificateRequest消息,客户端会构造CertificateMsg消息(certRequest属性,此时还未完全初始化),但此时还不会立即发送给服务端。

  5. 客户端接收服务端发过来的ServerHelloDone消息,进行以下操作:

(1) 通过判断certRequest是否为空来判断是否需要发送客户端证书,如果需要客户端证书,则从KeyManager中获取客户端证书,构造Certificate握手消息发送给服务端。

(2) 客户端发送ClientKeyExchange握手消息,ClientKeyExchange是抽象基类,具体子类要看使用哪种密钥交换算法,这里发送的是DHClientKeyExchange,主要是客户端的DH公钥。

(3) 使用客户端DH参数和服务端DH公钥生成preMasterSecret,接着用preMasterSecret和prf函数生成masterSecret,session会存储masterSecret;用masterSecret和prf函数生成connectionKeys,connectionKeys包含clntWriteKey,svrWriteKey,clntWriteIV,svrWriteIV,clntMacSecret和svrMacSecret,这些参数用于后续生成通信的readCipher, readAuthenticator, writeCipher, writeAuthenticator。

(4) 客户端发送CertificateVerify握手消息;

(5) 客户端发送contentType为ct_change_cipher_spec的消息,握手消息的contentType为ct_handshake,利用clntWriteKey, clntWriteIV生成客户端的writeCipher,用于客户端读取数据时解密;利用clntMacSecret生成客户端的writeAuthenticator,用于对客户端数据进行签名。

(6) 客户端发送Finished握手消息;

  1. 服务端接收客户端发的Certificate消息,进行证书校验。

  2. 服务端接收客户端发的ClientKeyExchange消息,这里收到DHClientKeyExchange消息对象,进行以下操作:

(1) 根据服务端DH参数和客户端DH公钥计算出preMasterSecret;

(2) 利用preMasterSecret和prf函数生成masterSecret,session会存储masterSecret;

(3) 利用masterSecret和prf函数生成connectionKeys,connectionKeys包含clntWriteKey,svrWriteKey,clntWriteIV,svrWriteIV,clntMacSecret和svrMacSecret,这些参数用于后续生成通信的readCipher, readAuthenticator, writeCipher, writeAuthenticator。

  1. 服务端收到客户端发的CertificateVerify消息,主要对客户端证书进行验签。

  2. 服务端收到客户端发的change_cipher_spec消息,利用clntWriteKey, clntWriteIV生成服务端的readCipher,readCipher用于服务端读取数据时解密;利用clntMacSecret生成服务端的readAuthenticator,readAuthenticator用于对客户端数据进行验签。

  3. 服务端收到客户端发的Finished握手消息,进行以下操作:

(1) 对verifyData参数进行验证,用于判断双方生成的masterSecret是否一致。

(2) 发送contentType为ct_change_cipher_spec的消息,利用svrWriteKey和svrWriteIV生成服务端的writeCipher,用于服务端发送数据时进行加密;利用svrMAcSecret生成服务端的writeAuthenticator,用于对服务端的数据进行签名。

(3) 发送Finished握手消息。

  1. 客户端收到服务端发的change_cipher_spec消息,利用svrWriteKey, svrWriteIV生成客户端的readCipher,用于客户端读取数据时解密;利用svrMacSecret生成客户端的readAuthenticator,用于对服务端数据进行验签。

  2. 客户端收到服务端发的Finished消息,对verifyData参数进行验证,用于判断双方生成的masterSecret是否一致。

四. 发送应用数据

客户端发送数据时使用writeAuthenticator进行签名,再使用writeCipher对数据进行加密,然后发送给服务端;服务端收到数据后使用readCipher对数据进行解密,再使用readAuthenticator进行验签。服务端发送数据给客户端也是同理。

这篇关于OpenJDK8源码分析SSL握手流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040374

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1

nodejs打包作为公共包使用的完整流程

《nodejs打包作为公共包使用的完整流程》在Node.js项目中,打包和部署是发布应用的关键步骤,:本文主要介绍nodejs打包作为公共包使用的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言一、前置准备二、创建与编码三、一键构建四、本地“白嫖”测试(可选)五、发布公共包六、常见踩坑提醒

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

Ubuntu向多台主机批量传输文件的流程步骤

《Ubuntu向多台主机批量传输文件的流程步骤》:本文主要介绍在Ubuntu中批量传输文件到多台主机的方法,需确保主机互通、用户名密码统一及端口开放,通过安装sshpass工具,准备包含目标主机信... 目录Ubuntu 向多台主机批量传输文件1.安装 sshpass2.准备主机列表文件3.创建一个批处理脚

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

一个Java的main方法在JVM中的执行流程示例详解

《一个Java的main方法在JVM中的执行流程示例详解》main方法是Java程序的入口点,程序从这里开始执行,:本文主要介绍一个Java的main方法在JVM中执行流程的相关资料,文中通过代码... 目录第一阶段:加载 (Loading)第二阶段:链接 (Linking)第三阶段:初始化 (Initia