• 20.7 OpenSSL 套接字SSL加密传输


    OpenSSL 中的 SSL 加密是通过 SSL/TLS 协议来实现的。SSL/TLS 是一种安全通信协议,可以保障通信双方之间的通信安全性和数据完整性。在 SSL/TLS 协议中,加密算法是其中最核心的组成部分之一,SSL可以使用各类加密算法进行密钥协商,一般来说会使用RSA等加密算法,使用TLS加密针对服务端来说则需要同时载入公钥与私钥文件,当传输被建立后客户端会自行下载公钥并与服务端完成握手,读者可将这个流程理解为上一章中RSA的分发密钥环节,只是SSL将这个过程简化了,当使用时无需关注传输密钥对的问题。

    与RSA实现加密传输一致,使用SSL实现加密传输读者同样需要自行生成对应的密钥对,密钥对的生成可以使用如下命令实现;

    • 生成私钥: openssl genrsa -out privkey.pem 2048
    • 生成公钥: openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095

    执行如上两条命令,读者可得到两个文件首先生成2048位的privkey.pem也就是私钥,接着利用私钥文件生成cacert.pem证书文件,该文件的有效期为1095天也就是三年,当然此处由于是测试可以使用自定义生成,如果在实际环境中还是需要购买正规签名来使用的。

    服务端实现代码与原生套接字通信保持高度一致,在连接方式上同样采用了标准API实现,唯一的不同在于当accept函数接收到用于请求时,我们需要通过SSL_new产生一个SSL对象,当需要发送数据时使用SSL_write,而当需要接收数据时则使用SSL_read函数,通过使用这两个函数即可保证中间的传输流程是安全的,其他流程与标准套接字编程保持一致,如下是服务端完整代码实现。

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    extern "C"
    {
    #include 
    }
    
    #pragma comment(lib, "WS2_32.lib")
    #pragma comment(lib,"libssl.lib")
    #pragma comment(lib,"libcrypto.lib")
    
    #define MAXBUF 1024
    
    int main(int argc, char** argv)
    {
      SOCKET sockfd, new_fd;
      struct sockaddr_in socket_ptr, their_addr;
    
      char buf[MAXBUF + 1] = {0};
    
      SSL_CTX* ctx;
    
      // SSL库初始化
      SSL_library_init();
    
      // 载入所有SSL算法
      OpenSSL_add_all_algorithms();
    
      // 载入所有SSL错误消息
      SSL_load_error_strings();
    
      // 以SSLV2和V3标准兼容方式产生一个SSL_CTX即SSLContentText
      ctx = SSL_CTX_new(SSLv23_server_method());
      if (ctx == NULL)
      {
        std::cout << "[-] 产生CTX上下文对象错误" << std::endl;
        return 0;
      }
      else
      {
        std::cout << "[+] 产生CTX上下文对象" << std::endl;
      }
    
      // 载入用户的数字证书,此证书用来发送给客户端,证书里包含有公钥
      if (SSL_CTX_use_certificate_file(ctx, "d://cacert.pem", SSL_FILETYPE_PEM) <= 0)
      {
        std::cout << "[-] 载入公钥失败" << std::endl;
        return 0;
      }
      else
      {
        std::cout << "[+] 已载入公钥" << std::endl;
      }
    
      // 载入用户私钥
      if (SSL_CTX_use_PrivateKey_file(ctx, "d://privkey.pem", SSL_FILETYPE_PEM) <= 0)
      {
        std::cout << "[-] 载入私钥失败" << std::endl;
        return 0;
      }
      else
      {
        std::cout << "[+] 已载入私钥" << std::endl;
      }
    
      // 检查用户私钥是否正确
      if (!SSL_CTX_check_private_key(ctx))
      {
        std::cout << "[-] 用户私钥错误" << std::endl;
        return 0;
      }
    
      // 开启Socket监听
      WSADATA wsaData;
      WSAStartup(MAKEWORD(2, 2), &wsaData);
      if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
      {
        WSACleanup();
        return 0;
      }
    
      // 创建套接字
      if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
      {
        return 0;
      }
    
      socket_ptr.sin_family = AF_INET;
      socket_ptr.sin_addr.s_addr = htonl(INADDR_ANY);
      socket_ptr.sin_port = htons(9999);
    
      // 绑定套接字
      if (bind(sockfd, (struct sockaddr*)&socket_ptr, sizeof(struct sockaddr)) == -1)
      {
        return 0;
      }
      if (listen(sockfd, 10) == -1)
      {
        return 0;
      }
    
      while (1)
      {
        SSL* ssl;
        int len = sizeof(struct sockaddr);
    
        // 等待客户端连接
        if ((new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &len)) != -1)
        {
          printf("客户端地址: %s --> 端口: %d --> 套接字: %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)
        {
          closesocket(new_fd);
          break;
        }
    
        // 开始处理每个新连接上的数据收发
        memset(buf, 0, MAXBUF);
        strcpy(buf, "[服务端消息] hello lyshark");
    
        // 发消息给客户端
        len = SSL_write(ssl, buf, strlen(buf));
        if (len <= 0)
        {
          goto finish;
          return 0;
        }
    
        memset(buf, 0, MAXBUF);
    
        // 接收客户端的消息
        len = SSL_read(ssl, buf, MAXBUF);
        if (len > 0)
        {
          printf("[接收到客户端消息] => %s \n", buf);
        }
    
        // 关闭套接字连接
      finish:
        SSL_shutdown(ssl);
        SSL_free(ssl);
        closesocket(new_fd);
      }
    
      closesocket(sockfd);
      WSACleanup();
      SSL_CTX_free(ctx);
    
      system("pause");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170

    客户端实现代码同样与原生套接字编程保持一致,如下是完整代码,读者可以发现当使用connect连接到服务端后,依然调用了SSL_connect函数,此处的函数功能是在服务端下载证书信息,并完成证书通信验证,当验证实现后,则读者就可以向原生套接字那样去操作数据包的流向了。

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    extern "C"
    {
    #include 
    }
    
    #pragma comment(lib, "WS2_32.lib")
    #pragma comment(lib,"libssl.lib")
    #pragma comment(lib,"libcrypto.lib")
    
    #define MAXBUF 1024
    
    void ShowCerts(SSL* ssl)
    {
      X509* cert;
      char* line;
    
      cert = SSL_get_peer_certificate(ssl);
      if (cert != NULL)
      {
        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);
      }
      else
      {
        printf("[-] 无证书信息 \n");
      }
    }
    
    int main(int argc, char** argv)
    {
      int sockfd, len;
      struct sockaddr_in dest;
      char buffer[MAXBUF + 1] = { 0 };
    
      SSL_CTX* ctx;
      SSL* ssl;
    
      // SSL库初始化
      SSL_library_init();
      OpenSSL_add_all_algorithms();
      SSL_load_error_strings();
    
      // 建立CTX上下文
      ctx = SSL_CTX_new(SSLv23_client_method());
      if (ctx == NULL)
      {
        WSACleanup();
        return 0;
      }
    
      // 创建Socket
      WSADATA wsaData;
      WSAStartup(MAKEWORD(2, 2), &wsaData);
      if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
      {
        WSACleanup();
        return 0;
      }
    
      if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      {
        WSACleanup();
        return 0;
      }
    
      // 初始化服务器端(对方)的地址和端口信息
      dest.sin_family = AF_INET;
      dest.sin_addr.s_addr = inet_addr("127.0.0.1");
      dest.sin_port = htons(9999);
    
      // 连接服务器
      if (connect(sockfd, (struct sockaddr*)&dest, sizeof(dest)) != 0)
      {
        WSACleanup();
        return 0;
      }
    
      // 基于ctx产生一个新的SSL
      ssl = SSL_new(ctx);
      SSL_set_fd(ssl, sockfd);
    
      // 建立 SSL 连接
      if (SSL_connect(ssl) != -1)
      {
        printf("[+] SSL连接类型: %s \n", SSL_get_cipher(ssl));
        ShowCerts(ssl);
      }
    
      //接收服务器来的消息 最多接收MAXBUF字节
      len = SSL_read(ssl, buffer, MAXBUF);
      if (len > 0)
      {
        printf("接收消息: %s --> 共 %d 字节 \n", buffer, len);
      }
      else
      {
        goto finish;
      }
    
      memset(buffer, 0, MAXBUF);
      strcpy(buffer, "[客户端消息] hello Shark");
    
      // 发消息给服务器
      len = SSL_write(ssl, buffer, strlen(buffer));
      if (len > 0)
      {
        printf("[+] 发送成功 \n");
      }
    
    finish:
      // 关闭连接
      SSL_shutdown(ssl);
      SSL_free(ssl);
      closesocket(sockfd);
      SSL_CTX_free(ctx);
    
      system("pause");
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

    至此读者可以分别编译服务端与客户端程序,并首先运行服务端侦听套接字,接着运行客户端,此时即可看到如下图所示的通信流程,至此两者的通信数据包将被加密传输,从而保证了数据的安全性

  • 相关阅读:
    paddle学习赛——钢铁目标检测(yolov5、ppyoloe+,Faster-RCNN)
    多系统对接的适配与包装模式应用
    Java在编译到执行过程的编码问题
    4、K8s控制器-Replicaset
    Linux驱动开发(十四)---USB驱动开发学习(键盘+鼠标)
    leetcode 460. LFU 缓存
    一文详解:springboot多数据源配置--传统的xml方式(附带源码)
    【二】2D测量 Metrology——get_metrology_object_num_instances()算子
    23模式--建造者模式
    算法刷题入门线性表|单调栈
  • 原文地址:https://blog.csdn.net/lyshark_csdn/article/details/134229375