• c++ openssl实现https


    本文是在学习https的时候,参照网上诸多资料,在实际操作过程中还是遇到不少问题,力求详细的阐述在linux环境下,c++使用openssl对http报文加密成为https的过程,并使用tcpdump抓取tcp报文展示http和https在传输过程中的差别。

    本文适合对c++ openssl一点了解都没有,服务器端编程,只需要把ssl添加上tcp连接上。

    参照文章:
    C++通过openssl搭建https服务器
    OpenSSL生成CA自签名根证书和颁发证书
    tcpdump抓取TCP/IP数据包分析
    openssl基本原理 + 生成证书 + 使用实例

    总述:https相当于在http连接上添加上一层ssl验证。
    如图所示,与没有ssl验证相比,在服务器端,listen之前设置ssl证书,在accept之后使用ssl_accept进行ssl的连接,相当于套了一层壳。

    在这里插入图片描述

    一、没有ssl的tcp连接

    http.cpp

    /*
    接受一个tcp请求,简简单单发送发送一个http响应报文
    */
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    int main(int argc, char* argv[]){
        if(argc < 3){
            printf("need filename ip-address port\n");
            return 1;
        }
        //ip地址
        char *ip = argv[1];
        //端口
        int port = atoi(argv[2]);
        //创建socket,ipv4.tcp
        int listenfd = socket(PF_INET, SOCK_STREAM, 0);
        if(listenfd == -1){
            printf("Create socket error %d", errno);
            return 1;
        }
        //命名socket
        //创建ipv4地址
        struct sockaddr_in m_addr;
        bzero(&m_addr, sizeof(m_addr));
        m_addr.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &m_addr.sin_addr);
        m_addr.sin_port = htons(port);
        //绑定
        int ret = bind(listenfd, (struct sockaddr*)&m_addr, sizeof(m_addr));
        if(ret == -1){
            printf("Socket bind error %d", ret);
            return 1;
        }
        //监听
        ret = listen(listenfd, 100);
        if(ret == -1){
            printf("Listen error %d", ret);
            return 1;
        }
        while(1){
            struct sockaddr_in addr;
            socklen_t addrlen = sizeof(addr);
            int new_con = accept(listenfd, (sockaddr *)&addr, &addrlen);
            if(new_con == -1){
                printf("accept error, errno = %d",errno);
                continue;
            } else {
                printf("accept %d success\n", new_con);
            }
            string html_file = "welcome.html";
            int fd = open(html_file.c_str(), O_RDONLY);
            struct stat file_stat;
            stat(html_file.c_str(), &file_stat);
            void *html_ = mmap(nullptr, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    
            string buf_w = "HTTP/1.1 200 OK\r\n"
                            "Content-Type: text/html; charset=UTF-8\r\n"
                            "Connection: close\r\n"
                            "Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n"
                            "Content-Length: " + to_string(file_stat.st_size) + "\r\n"
                            "\r\n";
            buf_w += (char *)html_;
    
            printf("send %d bytes\n", send(new_con, (void*)buf_w.c_str(), buf_w.size(), 0));
            munmap(html_, file_stat.st_size);
            
    
            sleep(2);
            close(new_con);
        }
        
      
        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

    其中welcome.html

    DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>WebServertitle>
        head>
        <body>
        <br/>
        <br/>
        <div align="center"><font size="5"> <strong>hello httpstrong>font>div>
    	<br/>
    		<br/>
    		<form action="5" method="post">
     			<div align="center"><button type="submit">点赞button>div>
                    form>
    		<br/>
                    <form action="6" method="post">
                            <div align="center"><button type="submit" >收藏button>div>
                    form>
    		<br/>
    		<form action="7" method="post">
     			<div align="center"><button type="submit">关注button>div>
                    form>
    		
            div>
        body>
    html>
    
    
    • 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

    注意,html文件要和http.cpp文件在同一文件夹下才能被准确打开。
    文件结构:

    <pre>.
    ├── http
    ├── http.cpp
    └── welcome.html
    pre>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用tcpdump抓包得到的消息

    编译完成后,在http所在的位置命令行键入$ ./http 127.0.0.1 1234开始等待连接。
    同时另开一终端,输入$sudo tcpdump -A -i lo port 1234开始监听本地回环lo的1234端口的信息。
    打开postman发送get报文
    在这里插入图片描述
    或者打开浏览器,输入127.0.0.1:1234/
    在这里插入图片描述
    此时tcpdump抓取到的信息,可以看到是明文传输的,这也是问什么需要https的原因。

    [zsz@localhost https]$ sudo tcpdump -A  -i lo port 1234
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
    22:11:03.930244 IP localhost.50744 > localhost.search-agent: Flags [S], seq 741845069, win 43690, options [mss 65495,sackOK,TS val 1659589 ecr 0,nop,wscale 7], length 0
    E..<..@.@.B1.........8..,7.M.........0.........
    ..R.........
    22:11:03.930333 IP localhost.search-agent > localhost.50744: Flags [S.], seq 2115968623, ack 741845070, win 43690, options [mss 65495,sackOK,TS val 1659590 ecr 1659589,nop,wscale 7], length 0
    E..<..@.@.<............8~..o,7.N.....0.........
    ..R...R.....
    22:11:03.930368 IP localhost.50744 > localhost.search-agent: Flags [.], ack 1, win 342, options [nop,nop,TS val 1659590 ecr 1659590], length 0
    E..4..@.@.B8.........8..,7.N~..p...V.(.....
    ..R...R.
    22:11:03.930742 IP localhost.search-agent > localhost.50744: Flags [P.], seq 1:841, ack 1, win 342, options [nop,nop,TS val 1659590 ecr 1659590], length 840
    E..|L.@.@..............8~..p,7.N...V.q.....
    ..R...R.HTTP/1.1 200 OK
    Content-Type: text/html; charset=UTF-8
    Connection: close
    Date: Fri, 23 Nov 2018 02:01:05 GMT
    Content-Length: 704
    
    DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>WebServertitle>
        head>
        <body>
        <br/>
        <br/>
        <div align="center"><font size="5"> <strong>hello httpstrong>font>div>
    	<br/>
    		<br/>
    		<form action="5" method="post">
     			<div align="center"><button type="submit">......button>div>
                    form>
    		<br/>
                    <form action="6" method="post">
                            <div align="center"><button type="submit" >......button>div>
                    form>
    		<br/>
    		<form action="7" method="post">
     			<div align="center"><button type="submit">......button>div>
                    form>
    		
            div>
        body>
    html>
    
    22:11:03.930768 IP localhost.50744 > localhost.search-agent: Flags [.], ack 841, win 355, options [nop,nop,TS val 1659590 ecr 1659590], length 0
    E..4..@.@.B7.........8..,7.N~.!....c.(.....
    ..R...R.
    22:11:03.931909 IP localhost.50744 > localhost.search-agent: Flags [P.], seq 1:202, ack 841, win 355, options [nop,nop,TS val 1659592 ecr 1659590], length 201
    E.....@.@.Am.........8..,7.N~.!....c.......
    ..R...R.GET / HTTP/1.1
    User-Agent: PostmanRuntime/7.29.2
    Accept: */*
    Postman-Token: 9d205642-3df0-49c7-87a5-74e73f2d731d
    Host: 127.0.0.1:1234
    Accept-Encoding: gzip, deflate, br
    Connection: keep-alive
    
    
    
    
    • 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

    二、https

    https需要证书,可以使用openssl进行自签发证书。
    详细可以自行搜索
    下边三行分别是生成私钥,证书申请文件,生成ca证书.
    注意生成私钥是要键入密码的,证书申请和生成要填很多信息,看说明填入。

    openssl genrsa -des3 -out privkey.pem 2048 
    openssl req -new -key privkey.pem -out cert.csr 
    openssl x509 -req -in cert.csr -out cert.pem -signkey privkey.pem -days 3650
    
    • 1
    • 2
    • 3

    最后会生成privkey.pem和cert.pem,分别用来ssl验证私钥和证书。

    和http.cpp的区别就是一开始多了初始化,在accept之后用SSL_accept再次接受一次。
    https.cpp

    /*
    接受一个tcp请求,简简单单发送发送一个http响应报文
    */
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    #include "assert.h"
    using namespace std;
    
    SSL_CTX *ctx = NULL;
    bool InitSSL(const char* cacert, const char* key, const char* passwd){
        // 初始化
        SSLeay_add_ssl_algorithms();
        OpenSSL_add_all_algorithms();
        SSL_load_error_strings();
        ERR_load_BIO_strings();
    
        // 我们使用SSL V3,V2
        assert((ctx = SSL_CTX_new(SSLv23_method())) != NULL);
    
        // 要求校验对方证书
        SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
    
        // 加载CA的证书
        assert(SSL_CTX_load_verify_locations(ctx, cacert, NULL));
        // 加载自己的证书
        assert(SSL_CTX_use_certificate_chain_file(ctx, cacert) > 0);
        //assert(SSL_CTX_use_certificate_file(ctx, "cacert.pem", SSL_FILETYPE_PEM) > 0);
     // 加载自己的私钥 
        SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)passwd);
        assert(SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) > 0);
     
        // 判定私钥是否正确  
        assert(SSL_CTX_check_private_key(ctx));
    
    
        return true;
    }
    
    
    int main(int argc, char* argv[]){
        string cacert = "cert.pem";
        string key = "privkey.pem";
        string passwd = "123456";
        if(!InitSSL(cacert.c_str(), key.c_str(), passwd.c_str())){
            printf("init ssl error\n");
            return 0;
        }
    
        if(argc < 3){
            printf("need filename ip-address port\n");
            return 1;
        }
        //ip地址
        char *ip = argv[1];
        //端口
        int port = atoi(argv[2]);
        //创建socket,ipv4.tcp
        int listenfd = socket(PF_INET, SOCK_STREAM, 0);
        if(listenfd == -1){
            printf("Create socket error %d", errno);
            return 1;
        }
        //命名socket
        //创建ipv4地址
        struct sockaddr_in m_addr;
        bzero(&m_addr, sizeof(m_addr));
        m_addr.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &m_addr.sin_addr);
        m_addr.sin_port = htons(port);
        //绑定
        int ret = bind(listenfd, (struct sockaddr*)&m_addr, sizeof(m_addr));
        if(ret == -1){
            printf("Socket bind error %d", ret);
            return 1;
        }
        //监听
        ret = listen(listenfd, 100);
        if(ret == -1){
            printf("Listen error %d", ret);
            return 1;
        }
        while(1){
            struct sockaddr_in addr;
            socklen_t addrlen = sizeof(addr);
            int new_con = accept(listenfd, (sockaddr *)&addr, &addrlen);
            if(new_con == -1){
                printf("accept error, errno = %d",errno);
                continue;
            } else {
                printf("accept %d success\n", new_con);
            }
    /ssl
            SSL *ssl = SSL_new(ctx);
            if(ssl == NULL)
            {
                printf("ssl new wrong\n");
                return 0;
            }
            SSL_set_accept_state(ssl);
            //关联sockfd和ssl
            SSL_set_fd(ssl, new_con);
            
            int ret = SSL_accept(ssl);
            if(ret != 1){
                printf("%s\n", SSL_state_string_long(ssl));
                printf("ret = %d, ssl get error %d\n", ret, SSL_get_error(ssl, ret));
            }
    
    //
            string html_file = "welcome.html";
            int fd = open(html_file.c_str(), O_RDONLY);
            struct stat file_stat;
            stat(html_file.c_str(), &file_stat);
            void *html_ = mmap(nullptr, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    
            string buf_w = "HTTP/1.1 200 OK\r\n"
                            "Content-Type: text/html; charset=UTF-8\r\n"
                            "Connection: close\r\n"
                            "Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n"
                            "Content-Length: " + to_string(file_stat.st_size) + "\r\n"
                            "\r\n";
            buf_w += (char *)html_;
            //把send换成SSL_write
            //printf("send %d bytes\n", send(new_con, (void*)buf_w.c_str(), buf_w.size(), 0));
            printf("send %d bytes\n", SSL_write(ssl, (void*)buf_w.c_str(), buf_w.size()));
            munmap(html_, file_stat.st_size);
            
            //关闭
            SSL_shutdown(ssl);
            SSL_free(ssl);
            close(new_con);
        }
        
        SSL_CTX_free(ctx);
        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

    如果使用postman,要把证书验证关掉,因为这个证书是我们自己发布的,不是官方,
    在这里插入图片描述
    在ip前边加上https前缀
    在这里插入图片描述
    如果使用浏览器,会提示不安全连接,直接同意就行了,要注意https前缀
    在这里插入图片描述

    使用tcpdump抓包

    和前边一样使用tcpdump抓包,可以看到都是乱码

    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
    23:00:32.693162 IP localhost.51284 > localhost.search-agent: Flags [S], seq 956600350, win 43690, options [mss 65495,sackOK,TS val 4628353 ecr 0,nop,wscale 7], length 0
    E..<..@.@.-..........T..9............0.........
    .F..........
    23:00:32.693283 IP localhost.search-agent > localhost.51284: Flags [S.], seq 2464157102, ack 956600351, win 43690, options [mss 65495,sackOK,TS val 4628353 ecr 4628353,nop,wscale 7], length 0
    E..<..@.@.<............T....9........0.........
    .F...F......
    23:00:32.693385 IP localhost.51284 > localhost.search-agent: Flags [.], ack 1, win 342, options [nop,nop,TS val 4628353 ecr 4628353], length 0
    E..4..@.@.-".........T..9..........V.(.....
    .F...F..
    23:00:32.700124 IP localhost.51284 > localhost.search-agent: Flags [P.], seq 1:518, ack 1, win 342, options [nop,nop,TS val 4628360 ecr 4628353], length 517
    E..9..@.@.+..........T..9..........V.......
    .F...F.............G5.E...YS.h...T........$.Dd.r.;. .......Sid.,]
    L..#EH.X...... .&'.$.......,.
    .+...	.0.../.......5.../.
    ..............
    .......................#........#.lJ'.y..[.......	.N..!../.........!..U......\
    .z..y.L[...F.0......B."....aB.N.Lt............QA.O..JJ@.4%.....I}/.f..9...c..{.'j......	.
    .j..^..s...+..;.p.......h2.http/1.1..........3.k.i... ...MtF>rzx.C.zU..+.8.fv..>mgb.zA...A.D.I....l6......)!E...tX[...D;i.s#.q.s..W...3..y.....0.4....IN$>Z.+....................................-........@................
    23:00:32.700227 IP localhost.search-agent > localhost.51284: Flags [.], ack 518, win 350, options [nop,nop,TS val 4628360 ecr 4628360], length 0
    E..4..@.@..............T....9..$...^.(.....
    .F...F..
    23:00:32.700410 IP localhost.search-agent > localhost.51284: Flags [F.], seq 1, ack 518, win 350, options [nop,nop,TS val 4628360 ecr 4628360], length 0
    E..4./@.@..............T....9..$...^.(.....
    .F...F..
    23:00:32.700799 IP localhost.51284 > localhost.search-agent: Flags [F.], seq 518, ack 2, win 342, options [nop,nop,TS val 4628360 ecr 4628360], length 0
    E..4..@.@.- .........T..9..$.......V.(.....
    .F...F..
    23:00:32.700815 IP localhost.search-agent > localhost.51284: Flags [.], ack 519, win 350, options [nop,nop,TS val 4628360 ecr 4628360], length 0
    E..4.0@.@..............T....9..%...^.(.....
    .F...F..
    
    
    • 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

    总结

    在学习如何使用https的时候,一开始概念很清楚,很多文章也直接明了的说了是在http和tcp中间加上一层ssl验证,但是对于证书如何获取,以及在代码中如何体现却很难找到一篇完整论述的文章,大部分文章都是基于读者有一定基础来写,加上c++实现的https服务器基本是体量很大,很难在庞大的代码中找到自己想要的,也就是关于ssl如何建立的部分。
    因此本文的主要目的不是说让读者建立一个完整的https的概念,或者掌握大部分openssl的api,而是说给出一个可以跑的通的代码,一个非常简洁的代码,一套跟着做就能实现的使用openssl+https的完整流程。

    这篇文章也是留个自己的一个教程,之后建立https服务器的时候,也可以根据这篇文章结合其他文章迅速回忆起相关知识,直接建立服务。

  • 相关阅读:
    java毕业设计体育场馆预定网站演示录像源码+lw文档+mybatis+系统+mysql数据库+调试
    【数据挖掘】聚类分析
    java计算机毕业设计商店管理系统源码+系统+mysql数据库+lw文档+部署
    标志位的设置与判断
    [python和CSP的姻缘]202109-2 非零段划分
    模拟栈(模板)
    jbase代码生成器(成型篇)
    newspringboot
    AtCoder Beginner Contest 127 F - Absolute Minima(对顶堆求动态中位数)
    02-CSS3基本样式
  • 原文地址:https://blog.csdn.net/qq_42370809/article/details/126352996