当前位置: 代码迷 >> 综合 >> 【openssl https分析和代码实现】
  详细解决方案

【openssl https分析和代码实现】

热度:39   发布时间:2023-11-21 17:24:34.0

【openssl https分析和代码实现】

  • https简述
    • SSL/TLS 区别
  • 简述加密算法
    • 散列函数Hash
    • 对称加密
    • 非对称加密
  • openssl
  • 代码实现
    • 生成公钥私钥和自建CA
      • 自建CA
      • 生成服务端公钥和私钥
      • 生成客户端公钥和私钥
    • c语言进行SSL交互
      • 服务端
      • 客户端
  • SSL握手细节
    • 抓包分析
      • 三次握手
      • client Hello
      • server回复和发送客户端
      • 客户端进行验证并回复
      • 服务端沟通完成,产生新的session Ticket
      • 客户端回复ack,SSL沟通完成

https简述

HTTPS (全称:Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 [1] 。HTTPS 在HTTP 的基础下加入SSL,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。

SSL/TLS 区别

在这里插入图片描述

本质上TLS对对ssl的升级,是一类协议,本质上不做区别。
TLS 是传输层安全性协议(英语:Transport Layer Security,缩写作 TLS)。
SSL 是安全套接字层协议(Secure Sockets Layer,缩写作 SSL)。

简述加密算法

TLS/SSL的功能实现主要依赖于三类基本算法:散列函数 Hash、对称加密和非对称加密,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。
在这里插入图片描述

散列函数Hash

常见的有 MD5、SHA1、SHA256,该类函数特点是函数单向不可逆、对输入非常敏感、输出长度固定,针对数据的任何修改都会改变散列函数的结果,用于防止信息篡改并验证数据的完整性;

在信息传输过程中,散列函数不能单独实现信息防篡改,因为明文传输,中间人可以修改信息之后重新计算信息摘要,因此需要对传输的信息以及信息摘要进行加密;

对称加密

常见的有 AES-CBC、DES、3DES、AES-GCM等,相同的密钥可以用于信息的加密和解密,掌握密钥才能获取信息,能够防止信息窃听,通信方式是1对1;

对称加密的优势是信息传输1对1,需要共享相同的密码,密码的安全是保证信息安全的基础,服务器和 N 个客户端通信,需要维持 N 个密码记录,且缺少修改密码的机制;

非对称加密

即常见的 RSA 算法,还包括 ECC、DH 等算法,算法特点是,密钥成对出现,一般称为公钥(公开)和私钥(保密),公钥加密的信息只能私钥解开,私钥加密的信息只能公钥解开。因此掌握公钥的不同客户端之间不能互相解密信息,只能和掌握私钥的服务器进行加密通信,服务器可以实现1对多的通信,客户端也可以用来验证掌握私钥的服务器身份。

非对称加密的特点是信息传输1对多,服务器只需要维持一个私钥就能够和多个客户端进行加密通信,但服务器发出的信息能够被所有的客户端解密,且该算法的计算复杂,加密速度慢。

结合三类算法的特点,TLS的基本工作方式是,客户端使用非对称加密与服务器进行通信,实现身份验证并协商对称加密使用的密钥,然后对称加密算法采用协商密钥对信息以及信息摘要进行加密通信,不同的节点之间采用的对称密钥不同,从而可以保证信息只能通信双方获取。

openssl

我们先看看官方的说法,就是 man openssl,如图:

DESCRIPTIONOpenSSL is a cryptography toolkit implementing the Secure Sockets Layer (SSL v2/v3) and Transport Layer Security (TLS v1) network protocols andrelated cryptography standards required by them.The openssl program is a command line tool for using the various cryptography functions of OpenSSL's crypto library from the shell.  It can be usedforo  Creation and management of private keys, public keys and parameterso  Public key cryptographic operationso  Creation of X.509 certificates, CSRs and CRLso  Calculation of Message Digestso  Encryption and Decryption with Cipherso  SSL/TLS Client and Server Testso  Handling of S/MIME signed or encrypted mailo  Time Stamp requests, generation and verification

简而言之,就是openssl实现了 SSL v2/v3 和TLS v1的网络协议,并且可以用来进行加解析。同时可以用来加解密和生成证书,生成公钥和私钥的作用。

代码实现

生成公钥私钥和自建CA

自建CA

# CA证书及密钥生成方法一----直接生成CA密钥及其自签名证书
# 如果想以后读取私钥文件ca_rsa_private.pem时不需要输入密码,亦即不对私钥进行加密存储,那么将-passout pass:123456替换成-nodes
openssl req -newkey rsa:2048 -passout pass:123456 -keyout ca_rsa_private.pem -x509 -days 365 -out ca.crt -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=CA/emailAddress=youremail@qq.com"# CA证书及密钥生成方法二----分步生成CA密钥及其自签名证书:
openssl genrsa -aes256 -passout pass:123456 -out ca_rsa_private.pem 2048
openssl req -new -x509 -days 365 -key ca_rsa_private.pem -passin pass:123456 -out ca.crt -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=CA/emailAddress=youremail@qq.com"

生成服务端公钥和私钥

# 服务器证书及密钥生成方法一----直接生成服务器密钥及待签名证书
# 如果想以后读取私钥文件server_rsa_private.pem时不需要输入密码,亦即不对私钥进行加密存储,那么将-passout pass:server替换成-nodes
openssl req -newkey rsa:2048 -passout pass:server -keyout server_rsa_private.pem -out server.csr -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=SERVER/emailAddress=youremail@qq.com"
# 服务器证书及密钥生成方法二----分步生成服务器密钥及待签名证书
openssl genrsa -aes256 -passout pass:server -out server_rsa_private.pem 2048
openssl req -new -key server_rsa_private.pem -passin pass:server -out server.csr -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=SERVER/emailAddress=youremail@qq.com"
# 使用CA证书及密钥对服务器证书进行签名:
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca_rsa_private.pem -passin pass:123456 -CAcreateserial -out server.crt
# 将加密的RSA密钥转成未加密的RSA密钥,避免每次读取都要求输入解密密码
# 密码就是生成私钥文件时设置的passout、读取私钥文件时要输入的passin,比如这里要输入“server”
openssl rsa -in server_rsa_private.pem -out server_rsa_private.pem.unsecure

生成客户端公钥和私钥

因为我们这边是进行双向验证,因此需要生成客户端的公钥和私钥。

# 客户端证书及密钥生成方法一----直接生成客户端密钥及待签名证书
# 如果想以后读取私钥文件client_rsa_private.pem时不需要输入密码,亦即不对私钥进行加密存储,那么将-passout pass:client替换成-nodes
openssl req -newkey rsa:2048 -passout pass:client -keyout client_rsa_private.pem -out client.csr -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=CLIENT/emailAddress=youremail@qq.com"
# 客户端证书及密钥生成方法二----分步生成客户端密钥及待签名证书:openssl genrsa -aes256 -passout pass:client -out client_rsa_private.pem 2048openssl req -new -key client_rsa_private.pem -passin pass:client -out client.csr -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=CLIENT/emailAddress=youremail@qq.com"
# 使用CA证书及密钥对客户端证书进行签名:
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca_rsa_private.pem -passin pass:123456 -CAcreateserial -out client.crt
# 将加密的RSA密钥转成未加密的RSA密钥,避免每次读取都要求输入解密密码
# 密码就是生成私钥文件时设置的passout、读取私钥文件时要输入的passin,比如这里要输入“client”
openssl rsa -in client_rsa_private.pem -out client_rsa_private.pem.unsecure

c语言进行SSL交互

需要注意的是这边在编译的时候需要加上动态库。
gcc server.c -lssl -lcrypto -o server
gcc client.c -lssl -lcrypto -o client

服务端

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>#define MAXBUF 1024
void ShowCerts(SSL * ssl)
{
    X509 *cert;char *line;cert = SSL_get_peer_certificate(ssl);// SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证// 如果验证不通过,那么程序抛出异常中止连接if(SSL_get_verify_result(ssl) == X509_V_OK){
    printf("证书验证通过\n");}if (cert != NULL) {
    printf("数字证书信息:\n");line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);printf("证书: %s\n", line);free(line);line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);printf("颁发者: %s\n", line);free(line);X509_free(cert);} elseprintf("无证书信息!\n");
}int main(int argc, char **argv) {
    int sockfd, new_fd;socklen_t len;struct sockaddr_in my_addr, their_addr;unsigned int myport, lisnum;char buf[MAXBUF + 1];SSL_CTX *ctx;if (argv[1])myport = atoi(argv[1]);elsemyport = 7838;if (argv[2])lisnum = atoi(argv[2]);elselisnum = 2;/* SSL 库初始化 */SSL_library_init();/* 载入所有 SSL 算法 */OpenSSL_add_all_algorithms();/* 载入所有 SSL 错误消息 */SSL_load_error_strings();/* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */ctx = SSL_CTX_new(SSLv23_server_method());/* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */if (ctx == NULL) {
    ERR_print_errors_fp(stdout);exit(1);}// 双向验证// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);// 设置信任根证书if (SSL_CTX_load_verify_locations(ctx, "ca.crt",NULL)<=0){
    ERR_print_errors_fp(stdout);exit(1);}/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */if (SSL_CTX_use_certificate_file(ctx, argv[3], SSL_FILETYPE_PEM) <= 0) {
    ERR_print_errors_fp(stdout);exit(1);}/* 载入用户私钥 */if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0) {
    ERR_print_errors_fp(stdout);exit(1);}/* 检查用户私钥是否正确 */if (!SSL_CTX_check_private_key(ctx)) {
    ERR_print_errors_fp(stdout);exit(1);}/* 开启一个 socket 监听 */if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket");exit(1);} elseprintf("socket created\n");bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = PF_INET;my_addr.sin_port = htons(myport);my_addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))== -1) {
    perror("bind");exit(1);} elseprintf("binded\n");if (listen(sockfd, lisnum) == -1) {
    perror("listen");exit(1);} elseprintf("begin listen\n");while (1) {
    SSL *ssl;len = sizeof(struct sockaddr);/* 等待客户端连上来 */if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len))== -1) {
    perror("accept");exit(errno);} elseprintf("server: got connection from %s, port %d, socket %d\n",inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port),new_fd);/* 基于 ctx 产生一个新的 SSL */ssl = SSL_new(ctx);/* 将连接用户的 socket 加入到 SSL */SSL_set_fd(ssl, new_fd);/* 建立 SSL 连接 */if (SSL_accept(ssl) == -1) {
    perror("accept");close(new_fd);break;}ShowCerts(ssl);/* 开始处理每个新连接上的数据收发 */bzero(buf, MAXBUF + 1);strcpy(buf, "server->client");/* 发消息给客户端 */len = SSL_write(ssl, buf, strlen(buf));if (len <= 0) {
    printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno,strerror(errno));goto finish;} elseprintf("消息'%s'发送成功,共发送了%d个字节!\n", buf, len);bzero(buf, MAXBUF + 1);/* 接收客户端的消息 */len = SSL_read(ssl, buf, MAXBUF);if (len > 0)printf("接收消息成功:'%s',共%d个字节的数据\n", buf, len);elseprintf("消息接收失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno));/* 处理每个新连接上的数据收发结束 */finish:/* 关闭 SSL 连接 */SSL_shutdown(ssl);/* 释放 SSL */SSL_free(ssl);/* 关闭 socket */close(new_fd);}/* 关闭监听的 socket */close(sockfd);/* 释放 CTX */SSL_CTX_free(ctx);return 0;
}

客户端

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>#define MAXBUF 1024
void ShowCerts(SSL * ssl)
{
    X509 *cert;char *line;cert = SSL_get_peer_certificate(ssl);// SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证// 如果验证不通过,那么程序抛出异常中止连接if(SSL_get_verify_result(ssl) == X509_V_OK){
    printf("证书验证通过\n");}if (cert != NULL) {
    printf("数字证书信息:\n");line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);printf("证书: %s\n", line);free(line);line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);printf("颁发者: %s\n", line);free(line);X509_free(cert);} elseprintf("无证书信息!\n");
}int main(int argc, char **argv)
{
    int sockfd, len;struct sockaddr_in dest;char buffer[MAXBUF + 1];SSL_CTX *ctx;SSL *ssl;if (argc != 5) {
    printf("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个""IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",argv[0], argv[0]);exit(0);}/* SSL 库初始化,参看 ssl-server.c 代码 */SSL_library_init();OpenSSL_add_all_algorithms();SSL_load_error_strings();ctx = SSL_CTX_new(SSLv23_client_method());if (ctx == NULL) {
    ERR_print_errors_fp(stdout);exit(1);}// 双向验证// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);// 设置信任根证书if (SSL_CTX_load_verify_locations(ctx, "ca.crt",NULL)<=0){
    ERR_print_errors_fp(stdout);exit(1);}/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */if (SSL_CTX_use_certificate_file(ctx, argv[3], SSL_FILETYPE_PEM) <= 0) {
    ERR_print_errors_fp(stdout);exit(1);}/* 载入用户私钥 */if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0) {
    ERR_print_errors_fp(stdout);exit(1);}/* 检查用户私钥是否正确 */if (!SSL_CTX_check_private_key(ctx)) {
    ERR_print_errors_fp(stdout);exit(1);}/* 创建一个 socket 用于 tcp 通信 */if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("Socket");exit(errno);}printf("socket created\n");/* 初始化服务器端(对方)的地址和端口信息 */bzero(&dest, sizeof(dest));dest.sin_family = AF_INET;dest.sin_port = htons(atoi(argv[2]));if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
    perror(argv[1]);exit(errno);}printf("address created\n");/* 连接服务器 */if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
    perror("Connect ");exit(errno);}printf("server connected\n");/* 基于 ctx 产生一个新的 SSL */ssl = SSL_new(ctx);SSL_set_fd(ssl, sockfd);/* 建立 SSL 连接 */if (SSL_connect(ssl) == -1)ERR_print_errors_fp(stderr);else {
    printf("Connected with %s encryption\n", SSL_get_cipher(ssl));ShowCerts(ssl);}/* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */bzero(buffer, MAXBUF + 1);/* 接收服务器来的消息 */len = SSL_read(ssl, buffer, MAXBUF);if (len > 0)printf("接收消息成功:'%s',共%d个字节的数据\n",buffer, len);else {
    printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno));goto finish;}bzero(buffer, MAXBUF + 1);strcpy(buffer, "from client->server");/* 发消息给服务器 */len = SSL_write(ssl, buffer, strlen(buffer));if (len < 0)printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",buffer, errno, strerror(errno));elseprintf("消息'%s'发送成功,共发送了%d个字节!\n",buffer, len);finish:/* 关闭连接 */SSL_shutdown(ssl);SSL_free(ssl);close(sockfd);SSL_CTX_free(ctx);return 0;
}

SSL握手细节

首先看一下流程图
在这里插入图片描述
第一步:Visitor给出协议版本号、一个客户端随机数(Client random),以及客户端支持的加密方法。

第二步:Server确认双方使用的加密方法,以及一个服务器生成的随机数(Server random)。

第三步:Server发送数字证书给Visitor。

第四步:Visitor确认数字证书有效(查看证书状态且查询证书吊销列表),并使用信任的CA的公钥解密数字证书获得Server的公钥,然后生成一个新的46字节随机数(称为预备主密钥Pre-master secret),并使用Server的公钥加密预备主密钥发给Server。

第五步:Server使用自己的私钥,解密Visitor发来的预备主密钥。

第六步:Visitor和Server双方都具有了(客户端随机数+服务端随机数+预备主密钥),它们两者都根据约定的加密方法,使用这三个随机数生成对称密钥——主密钥(也称为对话密钥session key),用来加密接下来的整个对话过程。

第七步:在双方验证完session key的有效性之后,SSL握手机制就算结束了。之后所有的数据只需要使用“对话密钥”加密即可,不再需要多余的加密机制。

需要说明的是,session key不是真正的对称加密密钥,而是由session key进行hash算法得到一段hash值,从这个hash值中推断出对称加密过程中所需的key(即对称加密所需的明文密码部分)、salt(在RFC文档中称为MAC secret)和IV向量。以后客户端每次传输数据,都需要用key + salt +IV向量来完成对称加密,而服务端只需一个key和协商好的加密算法即可解密。同理服务端向客户端传输数据时也是一样的。

简化版本的图
在这里插入图片描述

抓包分析

三次握手

在这里插入图片描述

这里是正常的tcp。三次握手。

client Hello

在这里插入图片描述

需要注意这个包和客户端回服务端是一个包里面完成的。

server回复和发送客户端

在这里插入图片描述
服务端返回协商的信息结果,包括选择使用的协议版本 version,选择的加密套件 cipher suite,选择的压缩算法 compression method、随机数 random_S 等,其中随机数用于后续的密钥协商;

certificates, 服务器端配置对应的证书链,用于身份验证与密钥交换;

客户端进行验证并回复

在这里插入图片描述
客户端回复,并返回客户端的请求.如随机数,客户端的证书

服务端沟通完成,产生新的session Ticket

在这里插入图片描述
change_cipher_spec,客户端通知服务器后续的通信都采用协商的通信密钥和加密算法进行加密通信、

客户端回复ack,SSL沟通完成

在这里插入图片描述

参考相关资讯:
加密、签名和SSL握手机制细节
HTTPS加密协议详解(四):TLS/SSL握手过程
Openssl实现双向认证教程(附服务端客户端代码)

  相关解决方案