SSL/TLS协议详解 - https为什么比http更安全

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

概述

  • 首先纠正一个错误可能很多初学者都以为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 WebWWW通讯的安全。主要任务是提供私密性信息完整性和身份认证。1994年改版为 SSLv21995年改版为SSLv3
  • TLS(Transport Layer Security安全传输层协议用于在两个通信应用程序之间提供保密性和数据完整性。该标准协议是由IETF 于 1999年颁布整体来说 TLS 非常类似 SSLv3只是对 SSLv3 做了些增加和修改。

TLS握手过程

client server 生成客户端随机数 Client Hello 客户端随机数客户端TLS版本密码套件列表 生成服务端随机数 Server Hello 服务端随机数确认TLS版本号确认密码套件 Server Certificate 发送服务端证书 Server Hello Done 生成预备主密钥PreMaster 使用服务端证书中的公钥加密 预主密钥得到Encrypted PreMaster Client Key Exchange 发送Encrypted PreMaster 使用私钥解密Encrypted PreMaster 得到预备主密钥PreMaster 使用客户端随机数服务端随机数 预备主密钥 计算出主密钥 使用客户端随机数服务端随机数 预备主密钥 计算出主密钥 Change Cipher Spec 密钥协商完成 Finished Change Cipher Spec 密钥协商完成 Finished Application Data 应用数据加密通信 Application Data 应用数据加密通信 client server
  • 握手详解
    • [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
      • 使用密钥块加密通信

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版本并从客户端发送来的加密套件列表中选择一个加密套件再发送一个服务端随机数
      在这里插入图片描述
  • Server Certificate

    • 服务端发送自己的证书
      在这里插入图片描述
  • Server Hello Done

    • 服务端消息发送完毕等待与客户端协商主密钥
      在这里插入图片描述
  • Client Key Exchange

    • 客户端将加密后的预备主密钥发送给服务端
      在这里插入图片描述
  • Change Cipher Spec

    • 通知对方可以使用协商好的主密钥进行加密通信
      在这里插入图片描述
  • Finished

    • 即Encrypted Handshake Message消息。确认握手消息没有被篡改。
  • Application Data

    • 应用数据进行加密发送
      在这里插入图片描述

参考资料

  • 《HTTPS权威指南》
  • 《深入浅出HTTPS从原理到实战》
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6