概述
- 首先纠正一个错误,可能很多初学者都以为HTTPS跟HTTP一样,都属于应用层协议。但其实HTTPS并不是一个单独的协议。HTTPS是安全版本的HTTP,简单理解 HTTPS = HTTP + SSL/TLS,即HTTPS就是使用SSL/TLS协议对HTTP报文进行了加密处理。
- 我们发送HTTP请求时,不管直接从网页,还是使用抓包工具,可以很方便的拿到报文。但是发送HTTPS请求时,报文是经过加密处理的。因此说HTTPS比HTTP更加安全。
- 接下来就介绍下SSL/TLS协议的握手过程,看看如何通过SSL/TLS协议对应用数据进行加密。
SSL/TLS简介
- SSL (Secure Sockets Layer)安全套接层,是由Netscape公司于1990年开发,用于保障Word Wide Web(WWW)通讯的安全。主要任务是提供私密性,信息完整性和身份认证。1994年改版为 SSLv2,1995年改版为SSLv3
- TLS(Transport Layer Security)安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性。该标准协议是由IETF 于 1999年颁布,整体来说 TLS 非常类似 SSLv3,只是对 SSLv3 做了些增加和修改。
TLS握手过程
- TLS握手涉及到加密算法和数字证书的知识。不了解的可参考我的另外两篇文章。
- 加密算法简介
- 数字证书简介
- 握手图示
- 握手详解
- [1] Client Hello
- 客户端发送TLS版本,支持的加密套件列表给服务端,并生成客户端随机数发送给服务端。
- Version : 表示客户端支持的SSL/TLS协议版本
- Random : 客户端随机数
- Session ID : 和会话恢复有关
- Cipher Suites : 客户端支持的密码套件列表
- Compression Methods : 客户端支持的压缩方法
- Extension : 扩展项
- [2] Server Hello
- 服务端确认TLS版本,选择使用的加密套件,生成服务端随机数发送给客户端。
- Version : 服务端根据客户端传递的版本号,选择一个双方都支持的版本
- Random : 服务端随机数
- Session ID : 和会话恢复有关
- Cipher Suite : 根据客户端传递过来的密码套件列表,选择一个双方都支持的密码套件
- Compression Method : 压缩算法
- Extension : 扩展项
- [3] Server Certificate
- 该消息是可选的。根据协商出来的密码套件,服务端选择是否发送证书消息。
- [4] Server Key Exchange
- 该消息是有条件才发送的。如果证书包含的信息不足以进行密钥交换,那么必须发送该消息。
- [5] Server Hello Done
- 表示服务端发送了足够的消息,接下来等待和客户端协商出预备主密钥。
- [6] Client Key Exchange
- 接收到服务端的Server Hello Done消息后,客户端发送此消息。该消息的主要作用就是协商出预备主密钥。这里只介绍使用RSA密码套件的流程。
- 客户端生成一个48位的预备主密钥,然后用服务端证书中的公钥加密并发送给服务端。最终发送的消息就是Encrypted PreMaster。
- [7] 计算主密钥和密钥块
- 客户端根据预备主密钥,客户端随机数,服务端随机数计算出主密钥。服务端使用服务端证书中的私钥解密客户端发送过来的Encrypted PreMaster,得到预备主密钥,根据相同的方法计算出主密钥。主密钥的长度固定是48字节。
- master_secret = PRF(pre_master_secret, “master secret”, ClientHello.random + ServerHello.random)
- 计算出主密钥后,还需要根据主密钥计算出密钥块。密钥块主要有六个
- Client MAC Key
- Server MAC Key
- Client Key
- Server Key
- Client IV
- Server IV
- MAC Key主要用于数据的完整性校验,Key用于加密数据。IV作为加密算法的初始化向量。
- [8] Change Cipher Spec
- 通知对方,可以用协商好的密钥进行通信了。
- [9] Finished
- 确认所有握手消息没有被篡改。
- [10] Application Data
- 使用密钥块加密通信
- [1] Client Hello
openssl实现tls通信
- openssl提供了SSL/TLS协议库,下面通过代码实现下。
- linux下执行这两个命令安装openssl。
- sudo apt-get install openssl
- sudo apt-get install libssl-dev
证书生成
-
编码实现前,需要用到三个证书。CA证书,服务端证书,客户端证书。可以直接使用openssl命令行工具来生成。
-
CA证书
- 生成RSA私钥
- openssl genrsa -out ca.key 1024
- 生成CA证书
- openssl req -new -x509 -key ca.key -out ca.crt -days 365
-
服务端证书
- 生成服务端密钥
- openssl genrsa -out server.key 1024
- 生成证书请求文件
- openssl req -new -key server.key -out server.csr
- 使用CA证书签发服务端证书
- openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -days 365
-
客户端证书
- 生成客户端密钥
- openssl genrsa -out client.key 1024
- 生成证书请求文件
- openssl req -new -key client.key -out client.csr
- 使用CA证书签发客户端证书
- openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt -days 365
代码
- 服务端代码
-
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/rsa.h> #include <openssl/crypto.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/rand.h> #define CERTF "server.crt" /*服务端的证书(需经CA签名)*/ #define KEYF "server.key" /*服务端的私钥(建议加密存储)*/ #define CACERT "ca.crt" /*CA 的证书*/ #define PORT 10088 /*准备绑定的端口*/ int main (){ SSL_load_error_strings(); /*为打印调试信息作准备*/ OpenSSL_add_ssl_algorithms(); /*初始化*/ SSL_CTX* ctx = SSL_CTX_new(TLSv1_server_method()); if(ctx == NULL) { printf("SSL_CTX_new failed.\n"); return -1; } SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); /*验证与否*/ SSL_CTX_load_verify_locations(ctx,CACERT,NULL); /*若验证,则放置CA证书*/ if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } if (!SSL_CTX_check_private_key(ctx)) { printf("Private key does not match the certificate public key\n"); return -1; } SSL_CTX_set_cipher_list(ctx,"RC4-MD5"); /*开始正常的TCP socket过程.*/ printf("Begin TCP socket...\n"); int listenSock = socket(AF_INET, SOCK_STREAM, 0); if(listenSock == -1){ perror("socket"); return -1; } struct sockaddr_in sa_serv; memset (&sa_serv, 0, sizeof(sa_serv)); sa_serv.sin_family = AF_INET; sa_serv.sin_addr.s_addr = INADDR_ANY; sa_serv.sin_port = htons(PORT); if(bind(listenSock, (struct sockaddr*) &sa_serv, sizeof (sa_serv)) == -1){ perror("bind"); return -1; } /*接受TCP链接*/ if(listen (listenSock, 5) == -1){ perror("listen"); return -1; } struct sockaddr_in sa_cli; socklen_t client_len = sizeof(sa_cli); int connfd = accept (listenSock, (struct sockaddr*) &sa_cli, &client_len); if(connfd == -1){ perror("accept"); close (listenSock); return -1; } printf ("[%s:%d] connected...\n", inet_ntoa(sa_cli.sin_addr), sa_cli.sin_port); /*TCP连接已建立,进行服务端的SSL过程. */ printf("Begin server side SSL\n"); SSL* ssl = SSL_new (ctx); if(ssl == NULL){ printf("SSL_new failed.\n"); return -1; } SSL_set_fd (ssl, connfd); int sslSock = SSL_accept (ssl); if(sslSock == -1){ ERR_print_errors_fp(stderr); return -1; } printf("SSL_accept finished\n"); /*打印所有加密算法的信息(可选)*/ printf ("SSL connection using %s\n", SSL_get_cipher(ssl)); /*得到客户端的证书并打印些信息(可选) */ X509* client_cert = SSL_get_peer_certificate (ssl); if (client_cert != NULL) { printf ("Client certificate:\n"); char* subStr = X509_NAME_oneline(X509_get_subject_name (client_cert), 0, 0); if(subStr == NULL){ printf("X509_NAME_oneline subject failed.\n"); return -1; } printf ("subject: %s\n", subStr); //Free (subStr); char* issStr = X509_NAME_oneline(X509_get_issuer_name (client_cert), 0, 0); if(issStr == NULL){ printf("X509_NAME_oneline subject failed.\n"); return -1; } printf ("issuer: %s\n", issStr); //Free (issStr); X509_free (client_cert);/*如不再需要,需将证书释放 */ }else{ printf ("Client does not have certificate\n"); } char buf[4096] = {0}; /* 数据交换开始,用SSL_write,SSL_read代替write,read */ int readSize = SSL_read(ssl, buf, sizeof(buf) - 1); if(readSize == -1){ ERR_print_errors_fp(stderr); return -1; } printf ("SSL_read buf[%d] = {%s}\n", readSize, buf); if(SSL_write (ssl, "Welcome to Connect to Server!", strlen("Welcome to Connect to Server!")) == -1){ ERR_print_errors_fp(stderr); return -1; } shutdown (connfd, 2); SSL_free (ssl); SSL_CTX_free (ctx); return 0; }
-
- 客户端代码
-
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/rsa.h> #include <openssl/crypto.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/rand.h> /*所有需要的参数信息都在此处以#define的形式提供*/ #define CERTF "client.crt" /*客户端的证书(需经CA签名)*/ #define KEYF "client.key" /*客户端的私钥(建议加密存储)*/ #define CACERT "ca.crt" /*CA 的证书*/ #define PORT 10088 /*服务端的端口*/ // #define SERVER_ADDR "182.92.205.179" /*服务段的IP地址*/ #define SERVER_ADDR "127.0.0.1" /*服务段的IP地址*/ int main () { /*初始化*/ OpenSSL_add_ssl_algorithms(); //载入所有SSL错误消息 SSL_load_error_strings(); /*采用什么协议(SSLv2/SSLv3/TLSv1)在此指定*/ /*申请SSL会话环境*/ SSL_CTX *ctx = SSL_CTX_new(TLSv1_client_method()); if(ctx == NULL){ printf("SSL_CTX_new failed!\n"); return -1; } /*验证与否,是否要验证对方*/ SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); /*若验证对方,则放置CA证书*/ SSL_CTX_load_verify_locations(ctx,CACERT,NULL); /*加载自己的证书*/ if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } /*加载自己的私钥,以用于签名*/ if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } /*调用了以上两个函数后,检验一下自己的证书与私钥是否配对*/ if (!SSL_CTX_check_private_key(ctx)) { printf("Private key does not match the certificate public key\n"); return -1; } /*以下是正常的TCP socket建立过程*/ printf("Begin tcp socket...\n"); int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == -1){ perror("socket"); return -1; } struct sockaddr_in sa; memset (&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr(SERVER_ADDR); /* Server IP */ sa.sin_port = htons(PORT); /* Server Port number */ if(connect(sock, (struct sockaddr*) &sa, sizeof(sa)) == -1){ perror("connect"); return -1; } /* TCP 链接已建立.开始 SSL 握手过程 */ printf("Begin SSL negotiation \n"); /*申请一个SSL套接字*/ SSL* ssl = SSL_new (ctx); if(ssl == NULL){ printf("SSL_new failed.\n"); return -1; } /*绑定读写套接字*/ SSL_set_fd(ssl, sock); if(SSL_connect(ssl) == -1){ ERR_print_errors_fp(stderr); return -1; } /*打印所有加密算法的信息(可选)*/ printf ("SSL connection using %s\n", SSL_get_cipher(ssl)); /*得到服务端的证书并打印些信息(可选) */ X509* server_cert = SSL_get_peer_certificate (ssl); if(server_cert == NULL){ printf("SSL_get_peer_certificate failed.\n"); return -1; } printf ("Server certificate:\n"); char* subStr = X509_NAME_oneline(X509_get_subject_name (server_cert),0,0); if(subStr == NULL){ printf("X509_NAME_oneline subject failed.\n"); return -1; } printf("subject: %s\n", subStr); // Free(subStr); char* issStr = X509_NAME_oneline (X509_get_issuer_name(server_cert),0,0); if(issStr == NULL){ printf("X509_NAME_oneline issuer failed.\n"); return -1; } printf ("issuer: %s\n", issStr); // Free (issStr); X509_free(server_cert); /*如不再需要,需将证书释放 */ /* 数据交换开始,用SSL_write,SSL_read代替write,read */ printf("Begin SSL data exchange\n"); if(SSL_write(ssl, "Hello, I am client!", strlen("Hello, I am client!")) == -1){ ERR_print_errors_fp(stderr); return -1; } char buf[4096] = {0}; int readSize = SSL_read(ssl, buf, sizeof(buf) - 1); if(readSize == -1){ ERR_print_errors_fp(stderr); return -1; } printf ("SSL_read buf[%d] = {%s}\n", readSize, buf); SSL_shutdown (ssl); /* send SSL/TLS close_notify */ shutdown (sock, 2); SSL_free (ssl); SSL_CTX_free (ctx); return 0; }
-
- Makefile
-
all: server client server: server.cpp g++ -o server server.cpp -lssl -lcrypto client: client.cpp g++ -o client client.cpp -lssl -lcrypto clean: rm server client
-
使用wireShark抓包分析
-
接下来就使用wireShark抓包工具来分析下SSL/TLS协议的握手过程
-
通过上图可以看出,在SSL/TLS握手前后,还是要进行TCP三次握手和四次挥手。这个不再介绍TCP的握手过程,不清楚的可查看我这篇文章 : 抓包分析TCP协议
-
接下来详细看下具体的SSL/TLS握手内容
-
Client Hello
- TCP三次握手完成,客户端发送一个Client Hello消息,开始进行SSL/TLS握手
- 客户端发送支持的TLS版本,支持的密码套件列表和一个客户端随机数。
-
Server Hello
- 服务端确认TLS版本,并从客户端发送来的加密套件列表中选择一个加密套件,再发送一个服务端随机数
- 服务端确认TLS版本,并从客户端发送来的加密套件列表中选择一个加密套件,再发送一个服务端随机数
-
Server Certificate
- 服务端发送自己的证书
- 服务端发送自己的证书
-
Server Hello Done
- 服务端消息发送完毕,等待与客户端协商主密钥
- 服务端消息发送完毕,等待与客户端协商主密钥
-
Client Key Exchange
- 客户端将加密后的预备主密钥发送给服务端
- 客户端将加密后的预备主密钥发送给服务端
-
Change Cipher Spec
- 通知对方,可以使用协商好的主密钥进行加密通信
- 通知对方,可以使用协商好的主密钥进行加密通信
-
Finished
- 即Encrypted Handshake Message消息。确认握手消息没有被篡改。
-
Application Data
- 应用数据进行加密发送
- 应用数据进行加密发送
参考资料
- 《HTTPS权威指南》
- 《深入浅出HTTPS从原理到实战》