探索https原理,用openssl自实现https

探索http原理,使用C++基于socket自实现http中使用C++基于socket实现了http,本篇我将在之前实现http的基础上,使用openssl实现一个https。

https是指http和tcp之间多了一层ssl(Secure Sockets Layer 安全套接层)。http协议传输的是明文,就是发送端把数据写入socket,接收端从socket读取数据,数据在传输过程中都保持明文,非常容易被窃听,所以不安全。ssl就是用来实现加密传输的,大概流程就是发送端不再直接向socket写入数据,而是把数据写入ssl层,ssl将数据加密后再使用socket发出。接收端也不再直接从socket读取数据,而是ssl层从socket接收数据,此数据是加密的,ssl需要对数据解密,接收端应用程序从ssl读取解密后的数据。由此,数据在传输途中处于加密状态,从而是安全的。
在这里插入图片描述

ssl的加密过程比较复杂。涉及大量的密码学知识。这里大概介绍一下。

ssl的加密过程使用到了对称加密和非对称加密。

对称加密是指对信息的加密和解密都使用相同的密钥。也就是说,一把钥匙开一把琐。对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高。但是对称加密的缺点也很明显,那就是密钥也需要在网上传输,一旦密钥被破解或截获,后果就可想而知了。

非对称加密算法需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。用得最多的非对称加密算法是RSA算法。用私钥对信息进行加密只有其对应的私钥才能解密,用私钥加密只有对应的公钥才能解密。这就能弥补对称加密的密钥不安全传输问题了,我们只需要把公钥发送出去,别人拿到了公钥也没办法解密信息。因为公钥加密的信息只有私钥才能解密,而私钥是有我们持有,只要保证私钥安全就可以了。非对称加密算法的缺点同样严重,那就是非对称加密算法的加解密速度比对称加密要慢上千倍,啊,这真是一个让人受不了的缺点。

结合对称加密和非对称加密的优缺点,于是人才们想出一个办法,对称加密的问题是密钥不安全,那我们就把这个密钥用非对称加密来加密一下再传输。这样几乎完美解决了这两个加密算法的问题。

说几乎解决是因为实际上还面临一个问题,那就是公钥的交换问题。如果通信的一端收到了对方发来的公钥,如何证明这个公钥是真实的对方发的,而不是中间有人假冒的。确实存在这种可能,比如张三和李四在通信,但中间存在一个老王,他跟张三说自己是李四,跟李四说自己是张三。于是就有了证书的概念,证书就是用来安全交换公钥的。证书类似于身份证,是用来证明身份的。身份证的公信力来源于政府,在加密界也存在一些有公信力的机构,称之为CA,CA是被操作系统信任的,相当于他的权威性写进了基因。我们可以让CA给我们证明一下身份,于是我们可以把公钥、域名和一些基本信息提交到CA。CA也有自己的公钥和私钥,CA用自己的私钥对我们的信息做一个摘要(比如MD5或SHA256等)并加密,这个加密只有CA的公钥才能解。我们提交到CA的信息加上CA的加密信息在一起就组成了证书。CA同时也把自己的公钥和基本信息摘要并加密生成一个证书,称为根证书,根证书被添加到操作系统里。验证证书时就用根证书内的公钥解密一下证书里的信息。其实证书就是证明和某个公钥确实是某人的,而不是冒充的。

为了实现https,所以我们先需要生成自己的证书,并把根证书添加到系统内,并信任这个这个根证书。openssl可以做这件事。

下载openssl

编译

./Configure 
make install

生成CA的私钥

openssl genrsa -out rootCA.key 2048

生成CA的根证书

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem

执行完这个命令会让我们输入一些证书的信息

liaokun@192 http % openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:CN
State or Province Name (full name) []:Beijing
Locality Name (eg, city) []:Beijing
Organization Name (eg, company) []:LK Technology Co. Ltd.
Organizational Unit Name (eg, section) []:LK Technology Co. Ltd.
Common Name (eg, fully qualified host name) []:LK Technology Root CA
Email Address []:

执行完后,得到rootCA.pem文件,这就是根证书,我们把它添加到系统信任。
在这里插入图片描述

接下来,生成https的server端私钥

openssl genrsa -out server.key 2048

生成服务端的csr文件,ca根据csr文件为服务端颁发证书。可以把上面生成证书要填的信息写到-subj选项中,如下

openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=LK/OU=LK/CN=test.com"

ca根据csr文件为服务端颁发证书

openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.pem -days 825 -sha256 -extfile cert/v3.ext
#SSL证书最长期限825天

server.pem就是https使用的服务端证书。我把地址写成了test.com。测试的时候需要改写/etc/hosts将本机地址映射到test.com。

接下来可以写代码了,在之前实现http的基础上,加入openssl。https默认使用的端口是443,所以服务端改为监听443端口。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define port 443 //监听端口,可以在范围内自由设定
using namespace std;



void close_ssl(SSL *ssl,int client_fd)
{
    if(ssl)
    {
        /* 关闭 SSL 连接 */
        SSL_shutdown(ssl);
        /* 释放 SSL */
        SSL_free(ssl);

    }

    if(client_fd>0)
    {
        close(client_fd);
    }
}


int main()
{
    std::string message ="";
    message+="HTTP/1.1 200 OK";
    message+="\r\n";
    message+="Content-Type:text/html\r\n";
    message+="server:Tengine \r\n";
    message+="name:LiaoKun \r\n";
    message+="\r\n";
    message+="<html><head>Hello,World!</head></html>\r\n";
    message+="\r\n";
    SSL_CTX *ctx;

    /* 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_NONE, NULL);
//    SSL_CTX_set_security_level(ctx,0);

    /* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
    if ( SSL_CTX_use_certificate_file(ctx, "/Users/liaokun/CLionProjects/http/cert/server.pem", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 载入用户私钥 */
    if (SSL_CTX_use_PrivateKey_file(ctx, "/Users/liaokun/CLionProjects/http/cert/server.key", 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);
    }

    //1.创建一个socket套接字
    int local_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (local_fd == -1)
    {
        cout << "socket error!" << endl;
        exit(-1);
    }
    cout << "socket ready!" << endl;

    //2.sockaddr_in结构体:可以存储一套网络地址(包括IP与端口),此处存储本机IP地址与本地的一个端口
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(port);  //绑定6666端口
    local_addr.sin_addr.s_addr =  INADDR_ANY ; //绑定本机IP地址

    //3.bind(): 将一个网络地址与一个套接字绑定,此处将本地地址绑定到一个套接字上
    int res = bind(local_fd, (struct sockaddr *)&local_addr, sizeof(local_addr));
    if (res == -1)
    {
        cout << "bind error!" << endl;
        exit(-1);
    }
    cout << "bind ready!" << endl;

    //4.listen()函数:监听试图连接本机的客户端
    //参数二:监听的进程数
    listen(local_fd, 10);
    cout << "等待来自客户端的连接...." << endl;
    SSL *ssl;
    while (true)//循环接收客户端的请求
    {
        //5.创建一个sockaddr_in结构体,用来存储客户机的地址
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        //6.accept()函数:阻塞运行,直到收到某一客户机的连接请求,并返回客户机的描述符
        int client_fd = accept(local_fd, (struct sockaddr *)&client_addr, &len);
        if (client_fd == -1)
        {
            cout << "accept错误\n"<< endl;

            exit(-1);
        }

        /* 基于 ctx 产生一个新的 SSL */
        ssl = SSL_new(ctx);
        /* 将连接用户的 socket 加入到 SSL */
        SSL_set_fd(ssl, client_fd);
        /* 建立 SSL 连接 */
        if (SSL_accept(ssl) == -1) {
            perror("accept");
            printf("SSL 连接失败!\n");
            close_ssl(ssl,client_fd);
            break;
        }
        //7.输出客户机的信息
        char *ip = inet_ntoa(client_addr.sin_addr);
        cout << "客户机: " << ip << " 连接到本服务器成功!" << endl;

        //8.输出客户机请求的信息
        char buff[1024] = {0};
        int size = SSL_read(ssl, buff, sizeof(buff));
        cout << "Request information:\n"
             << buff << endl;
        cout << size << " bytes" << endl;

        //9.使用第6步accept()返回socket描述符,即客户机的描述符,进行通信。
        SSL_write(ssl, message.c_str(), message.length());//返回message

        close_ssl(ssl,client_fd);
    }
    /* 释放 CTX */
    SSL_CTX_free(ctx);

    close(local_fd);
    return 0;
}

在浏览器请求,结果。

在这里插入图片描述

代码:https://github.com/kunliao/http.git

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值