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 <string>
#include <sys/socket.h>
#include <sys/stat.h> 
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

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;
}

其中welcome.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>WebServer</title>
    </head>
    <body>
    <br/>
    <br/>
    <div align="center"><font size="5"> <strong>hello http</strong></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>

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

<pre>.
├── http
├── http.cpp
└── welcome.html
</pre>

使用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>WebServer</title>
    </head>
    <body>
    <br/>
    <br/>
    <div align="center"><font size="5"> <strong>hello http</strong></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



二、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

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

和http.cpp的区别就是一开始多了初始化,在accept之后用SSL_accept再次接受一次。
https.cpp
使用g++编译的时候要加上-lssl -lcrypto两个库

/*
接受一个tcp请求,简简单单发送发送一个http响应报文
*/
#include <string>
#include <sys/socket.h>
#include <sys/stat.h> 
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#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_VERIFY_FAIL_IF_NO_PEER_CERT,详见https://blog.csdn.net/u013919153/article/details/78616737
    //对于服务器端来说如果使用的是SSL_VERIFY_PEER且服务器端没有考虑对方没交证书的情况,会出现只能访问一次,第二次访问就失败的情况。
    SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 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;
}

如果使用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..

总结

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

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

  • 5
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值