c++使用OpenSSL基于socket实现tcp双向认证ssl(使用TSL协议)代码实现

这篇具有很好参考价值的文章主要介绍了c++使用OpenSSL基于socket实现tcp双向认证ssl(使用TSL协议)代码实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

相信各位对OpenSSL库已经不陌生了,目前笔者使用这个库实现了RSA、AES加解密和tcp的双向认证功能,下面来看tcp的双向认证。

1、什么是双向认证

简单说双向认证就是:客户端认证服务端是否合法,服务端认证客户端是否合法
可以借助于HTTPS来说明,http网络传输协议是超文本的明文协议,也就是说经过网卡传输的字节序列都是明文,那么HTTPS上的s就是双向认证的操作(ssl),实际上就是在http的逻辑上再套一层ssl握手,进程想要发送的字节序列数据经过http传输时再加上一层ssl来让c和s两端先相互认证是绝对正确的对端,然后ssl使用AES产生一个加密密钥,进行数据加密,传输给对端。而tcp的双向认证也是一样,只是tcp协议网络接口是socket套接字,因此需借助其配合开发,

在代码实现时有俩种:ssl、tsl,我们可以简单的将其看做tsl是ssl的版本升级,下面列举历史版本:
1994年,NetScape公司设计了SSL协议(Secure Sockets Layer)的1.0版,但是未发布。
1995年,NetScape公司发布SSL 2.0版,很快发现有严重漏洞。
1996年,SSL 3.0版问世,得到大规模应用。
1999年,互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS 1.0版。
2006年和2008年,TLS进行了两次升级,分别为TLS 1.1版和TLS 1.2版。最新的变动是2011年TLS 1.2的修订版。
目前,应用最广泛的是TLS 1.0,接下来是SSL 3.0。但是,主流浏览器都已经实现了TLS 1.2的支持。TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。因此如果当项目计划使用ssl或者tsl做双向认证,无需疑惑,两者根本就是一回事,只不过是版本的差异而已,而且这个版本的选择,OpenSSL会自动在cs两端协商,开发者无需写死,且不能写死,防止cs两端因版本号无法进行ssl握手(ssl握手就是指双向认证的过程,参照tcp的三次握手四次挥手)

不论是Java、qt、Android移动端、c语言,本质都是一样的,也不论是https协议、tcp协议,ssl双向认证的逻辑都一样:都是在正常的明文传输基础上套一层ssl,用来实现安全传输,这个安全传输我理解有俩层含义:
1、数据的安全,在网络节点路由器上传输加密后数据序列;
2、访问的对端合法,站在自己的角度上确保对端是期望访问的合法进程节点,例如银行APP,在访问服务器时,必须确保访问的服务器是真的银行,不然我们把账号密码短信验证码都发给黑客的某个进程服务器,它收到后以我们的身份去访问银行,把我们的钱全卷走
注:因此,在安全要求高的产品上双向认证和数据加密传输很有必要的

2、双向认证流程

2.1、大体上了解了双向认证的概念,开始介绍基本的认证逻辑

ssl的基本逻辑是使用公钥加密、私钥解密(也就是加密是用RSA非对称算法)。也就是说,客户端先发起对服务器的访问,索要服务端的公钥证书,然后使用公钥证书加密一个随机数,发给服务端,如果服务端能够解密出来,那么说明对端服务器没有被黑客劫持,因为私钥是不会在网络中公开传播的,安全的前提就是私钥是绝对安全的,不会被其他人获取到

2.2、插入证书相关知识点

这里出现了公钥证书(数字证书)、私钥等等,插入一些知识点:在非对称的RSA加密算法中,公钥加密,只能由私钥解密,反之也成立,怎么保证公钥不被篡改,OpenSSL库的做法就是把公钥放到证书中,因此出现了公钥证书(因私钥不传播,因此无须制作成证书)。

我们还应该思考一个问题,对于某个client端来说,如果是黑客劫持的中间服务器给c端发了它自己的公钥证书,c端用一个随机数根据劫持服务器的公钥证书去加密传输给它,黑客的劫持服务器能不能解密,必然是可以的,公钥证书和私钥是配套的,这样就会导致client端认为这个服务器是合法的,开始给它传输大量数据,这是致命的bug,于是就出现了根证书

上述的问题实际上根本无法解决,因此就出现一个权威的认证机构,由它来生成颁发证书,只有记录在根证书里的公钥证书才被看做是合法的,国际权威就是CA机构,需要花钱,国内的华为、阿里都有这种业务,便宜也更方便一些。
综上所述,ssl协议的双向认证就出现了根证书(chain.crt)、公钥证书(数字证书certificate.crt)、私钥(privateKey.key)这三种,

这三种证书是有俩种后缀名称,也就是文件后缀类型:
1、pem
2、crt
也有两种编码格式:
1、ascll码,也就是字符串
2、asn1、也就是二进制字节

这两种后缀和两种编码格式没有固定的对应关系,需要生成证书的时候才能看出来(谁给证书,谁一定知道证书的编码格式,找他问),OpenSSL库可以生成自己测试的证书,搜一些博客有操作指南,是什么编码格式是需要确定的,因为代码里读取证书的时候需要把编码格式传入进去。一般来说Unix OS多数用字符串编码格式,window OS好像是der二进制编码用的多。

2.3、继续ssl认证逻辑

上面说到公钥加密、私钥解密,根据三个证书相关,现在可以保证两端的正确性,但是RSA非对称加密算法非常耗时,出现第二个问题:公钥加密计算量太大,如何减少耗用的时间

解决方法:每一次对话(session),客户端和服务器端都生成一个"对话密钥"(session key),用它来加密信息。由于"对话密钥"是对称加密,所以运算速度非常快,而服务器公钥只用于加密"对话密钥"本身,这样就减少了加密运算的消耗时间。

由此可知,SSL的主体流程是这样的:

(1) 客户端向服务器端索要并验证公钥。
(2) 双方协商生成"对话密钥"。
(3) 双方采用"对话密钥"进行加密通信。

其中1、2叫ssl握手,3是建立安全通道后开始加密的数据传输。用一个手绘图来说明1、2
c++使用OpenSSL基于socket实现tcp双向认证ssl(使用TSL协议)代码实现
上图中就是1、2的步骤也被称为ssl握手,由OpenSSL库实现,开发者无需关注代码实现,懂得原理就行。

2.4、握手阶段的详细过程

c++使用OpenSSL基于socket实现tcp双向认证ssl(使用TSL协议)代码实现
握手涉及四次通信,且握手阶段都是明文,详细来看

2.4.1、c端发出请求(clientHello)

首先,客户端先向服务器发出加密通信的请求,这被叫做ClientHello请求。在这一步,客户端主要向服务器提供以下信息。

(1) 支持的协议版本,比如TLS 1.0版。
(2) 一个客户端生成的随机数,稍后用于生成"对话密钥"。
(3) 支持的加密方法,比如RSA公钥加密。
(4) 支持的压缩方法。

2.4.2、服务器回应(SeverHello)

服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容。

(1) 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
(2) 一个服务器生成的随机数,稍后用于生成"对话密钥"。
(3) 确认使用的加密方法,比如RSA公钥加密。
(4) 服务器证书。
除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供"客户端证书"。这是可以代码配置实现的

2.4.3、客户端回应

客户端收到服务器的回应后,首先验证服务器证书,如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。
如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。

(1) 一个随机数。该随机数用服务器公钥加密,防止被窃听。
(2) 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
(3) 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。

上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称"pre-master key"。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把"会话密钥"。至于为什么一定要用三个随机数,来生成"会话密钥",dog250解释得很好:

1、“不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。
2、对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,三个随机数通过一个密钥导出器最终导出一个对称密钥。
3、pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,那么pre master secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,因此必须引入新的随机因素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,一个伪随机可能完全不随机,可是是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。”

此外,如果前一步,服务器要求客户端证书,客户端会在这一步发送证书及相关信息。

2.4.4、服务器的最后回应

服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的"会话密钥"。然后,向客户端最后发送下面信息

(1)编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送
(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。

这样,ssl握手就完成,但是需要注意,上述的流程是HTTPS,OpenSSL的tcp socket流程和它稍有不同,就是tcp的证书验证是在建立ssl_connect后开始验证对端发过来的数字证书,而不是握手过程中验证,理解本质的话都是一样的,ssl_connect完成后,假如证书不符合规定,会返回0终止此次ssl握手连接。

3、c++的代码实现

Java中可以使用SSLSockets来实现,qt中也有Qsslsocket,他们都是语言集成了OpenSSL库的双向认证与加密api工具,而纯c++没有,则开发者需要在程序中集成OpenSSL库,下面来看实现:

3.1、Android.mk中链接OpenSSL库

LOCAL_SHARED_LIBRARIES := \    
    libcrypto\
    libssl
//项目中可以将某个文件拷贝到image镜像中,TARGET_OUT指的是根目录下的system/目录
$(shell mkdir -p $(TARGET_OUT)/etc/crt)
$(shell cp $(LOCAL_PATH)/crt/chain.crt $(TARGET_OUT)/etc/crt)

3.2、实现双向认证

#include <openssl/ssl.h>
#include <openssl/err.h>
//验证证书的函数回调,返回1的时候,无论对端证书是否正确都会放行
static int internalCertificateVerificationCallback(int preverify_ok, X509_STORE_CTX* x509_ctx)
{
    //preverify_ok contains 1 if the pre-verification succeeded, 0 otherwise.
	ALOGE("111wwwww:start vefity");

    return 1; // This accepts every certificate
}

bool ShowCerts(SSL * ssl)
{
    X509 *cert;
    char *line;

    cert = SSL_get_peer_certificate(ssl);
    // SSL_get_verify_result()是重点,SSL_CTX_set_verify()只是配置启不启用并没有执行认证,调用该函数才会真证进行证书认证
    // 如果验证不通过,那么程序抛出异常中止连接
    if(SSL_get_verify_result(ssl) == X509_V_OK){
        ALOGI("证书验证通过\n");
    }
    if (cert != NULL) {
        ALOGI("数字证书信息:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        ALOGI("证书: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        ALOGI("颁发者: %s\n", line);
        free(line);
        X509_free(cert);
		return TRUE;
    } else{
        ALOGI("无证书信息!\n");
		return FALSE;
    }
}

void createSocket(){

    SSL_CTX *ctx;
    SSL *ssl;
   /* SSL 库初始化 */
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    ctx = SSL_CTX_new(TSL_client_method());
    if (ctx == NULL) {
        ERR_print_errors_fp(stdout);
        ALOGE("SSL_CTX_new kill myself");
        kill(getpid(), SIGKILL);
    }
    /* 加载三个证书 */
    // 双向验证, internalCertificateVerificationCallback
	// SSL_VERIFY_PEER---要求对证书进行认证,没有证书也会放行
	// SSL_VERIFY_FAIL_IF_NO_PEER_CERT---要求客户端需要提供证书,但验证发现单独使用没有证书也会放行
	//SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
	// 设置信任根证书
	if (SSL_CTX_load_verify_locations(ctx, "chain.crt",NULL)<=0){
		ERR_print_errors_fp(stdout);
        ALOGE("SSL_CTX_load_verify_locations kill myself,%s",ERR_error_string( ERR_get_error(), NULL ));
        kill(getpid(), SIGKILL);
	}
	/* 载入用户的数字证书, 此证书用来发送给服务端端。 证书里包含有公钥 */
	if (SSL_CTX_use_certificate_file(ctx, "certificate.crt", SSL_FILETYPE_PEM) <= 0) {
		ERR_print_errors_fp(stderr);
        ALOGE("SSL_CTX_crt load error kill myself,%s,code = %d",ERR_error_string( ERR_get_error(), NULL ),SSL_CTX_use_certificate_file(ctx, "/data/cipherdata/crt.crt", SSL_FILETYPE_PEM));
		kill(getpid(), SIGKILL);
	}
	/* 载入用户私钥 */
	if (SSL_CTX_use_PrivateKey_file(ctx, "key.key", SSL_FILETYPE_PEM) <= 0) {
		ERR_print_errors_fp(stdout);
        ALOGE("SSL_CTX_key load error kill myself,error = %s",ERR_error_string( ERR_get_error(), NULL ));
		kill(getpid(), SIGKILL);
	}
		/* 检查用户私钥是否正确 */
	if (!SSL_CTX_check_private_key(ctx)) {
		ERR_print_errors_fp(stdout);
        ALOGE("SSL_CTX_key verity error kill myself,error = %s",ERR_error_string( ERR_get_error(), NULL ));
		kill(getpid(), SIGKILL);
	}
    //**这里创建一个socket,从这里开始是伪代码!!!就是创建一个正常连接成功的socket
    mSocketFd= (int)socket(AF_INET, SOCK_STREAM, 0);
    //connect,假设这里成功
    connect(mSocketFd,address,sizeof(address))
     /* 建立 SSL 连接 */
     ssl = SSL_new(ctx);
     if(ssl){
         SSL_set_fd(ssl, m_socket_id);
     }
     //SSL_shutdown(ssl);
     //SSL_free(ssl);
     //SSL_CTX_free(ctx);
      if (SSL_connect(ssl) == -1){
            ERR_print_errors_fp(stderr);
            ALOGE("SSL_connect error :%s",ERR_error_string( ERR_get_error(), NULL ));
            
			 closeSocket();
             return false;
       }else {
              ShowCerts(ssl);//返回true就是成功
             }
}
//当连接成功以后就可以使用SSL_write、SSL_read来收发数据,这里有个技巧,可能读者觉得这俩函数不太熟悉,不知道怎么使用,但是socket的send、recv函数应该是一直在使用的,直接把socket的send、recv替换成这俩ssl的读写函数就行,总归一句话,tcp的ssl双向认证,是基于socket开发的,先实现正常明文socket开发,然后把上述代码套进去,OpenSSL的双向认证就完成了

4、重点和难点和遇到的问题

4.1、因ssl协议版本不匹配导致的connect失败

上述代码中,ctx = SSL_CTX_new(TSL_client_method());这个形参的类型是有挺多协议类型的:

1、SSLv2_client_method()
2、SSLv3_client_method()
3、SSLv23_client_method()//包含v2和v3两种
4、TSL_client_method()
5、TSLv1_client_method()

从笔者开发测试来看,OpenSSL库是可以向下兼容的,也就是说在ssl握手过程中,会优先选择高版本的协议,这里是使用TSL_client_method(),尽量不要写死某个协议(例如TSLv1_client_method),否则一旦对端的协议版本不匹配,ssl_connect就会失败,而这种问题极难看出来原因,解决虽然很容易但是一时半刻想不到这里,会浪费很多时间。笔者在开发测试的过程中就遇到服务器写死了TSLv1_client_method这个类型,导致一直无法连接。

4.2、因socket是非阻塞模式导致的ssl_connect失败

在使用OpenSSL库实现双向认证的前提下,TCP socket套接字如果是非阻塞模式下进行ssl握手,就会一直连接失败,目前笔者这里是用阻塞式,暂时未找到原因!

5、总结

上述两个问题,解决起来其实很容易,问题是定位非常麻烦,c++的程序不像java抛异常给你提示的那么详细,而且实现代码又都在三方库里,看源码很不方便,那么上述俩问题是怎么定位出来的呢?网络相关的程序出问题,使用万能手段网络抓包工具wireshark,先把这个工具安装到电脑上,它可以自己抓包保存成文件,然后在工具中打开文档查看,当然我们要知道,任何抓包工具只能抓当前所在的网卡网络,你不能让电脑上的wireshark去抓手机网卡上的网络数据包,勿慌,adb shell命令可以抓网络包:

adb shell
tcpdump i any-w /data/log/xxx.cap

执行此命令即开始抓包,ctrl+c停止命令执行,数据包文件就保存到相应目录,导出来用wireshark查看,同一时刻肯定有很多网络请求经过网卡,根据自己进程访问的是哪个服务器IP来过滤数据,定位到具体的网络请求以后,查看你这一次的ssl握手执行到哪一步,对应去解决问题,wireshark网络数据抓包不只是用在查看ssl握手这里,只要有网络请求,必然能抓包,以后调试服务器接口等等都可以使用它,甚至是追踪网络请求的具体数据字节流,因此称之为万能手段文章来源地址https://www.toymoban.com/news/detail-461792.html

到了这里,关于c++使用OpenSSL基于socket实现tcp双向认证ssl(使用TSL协议)代码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于python socket实现TCP/UDP通信

    两个应用程序如果需要进行通讯最基本的一个前提就是能够唯一的标示一个进程,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。能够唯一标示网络中的进程后

    2024年02月16日
    浏览(51)
  • socket的基本使用,基于TCP/UDP

    OSI参考模型 Open System Interconnect 开放式系统 每层网络的由来 物理层:010101比特流,设备之间原始数据的传输,数模转换(发送端)和模数转换(接收端) -》传输过程可能出现错码和误码? 数据链路层:将原始比特流转换成逻辑传输符号,提供纠错编码,格式变为帧 -》出现

    2024年02月03日
    浏览(39)
  • JAVA使用RestTemplate类实现SSL双向/单向认证(国际)

    以管理员身份打开Windows PowerShel,通过cd(与linux系统类似)命令进入到JDK的bin目录:如C:Program FilesJavajdk1.8.0_221jrebin,找到目录下有keytool.exe就是正确进入目录了 参数说明: genkey 表示要创建一个新的密钥 alias 表示 keystore 的别名、 keyalg 表示使用的加密算法是 RSA ,一种非

    2024年02月15日
    浏览(40)
  • python tcp socket中实现SSL/TLS认证

    官话说SSL是安全套接层(secure sockets layer),TLS是SSL的继任者,叫传输层安全(transport layer security)。 说白点,就是在明文的上层和TCP层之间加上一层加密,这样就保证上层信息传输的安全。如HTTP协议是明文传输,加上SSL层之后,就有了雅称 HTTPS 。它存在的唯一目的就是 保证上层

    2024年01月23日
    浏览(49)
  • C++开发基础之网络编程WinSock库使用详解TCP/UDP Socket开发

    Winsock是Windows操作系统提供的用于网络编程的API库。它是Windows Sockets的简称,也就是套接字库。Winsock可以让开发人员使用TCP/IP协议族中的各种协议,如TCP、UDP等,在Windows平台下进行网络编程。 Winsock提供了一组函数和数据结构,这些函数和数据结构可以让开发人员创建和管理

    2024年01月23日
    浏览(51)
  • 使用paho.mqtt.embedded-c和openssl实现MQTT的单向认证功能

        由于项目有需求在一个现有的产品上增加MQTT通信的功能,且出于安全考虑,MQTT要走TLS,采用单向认证的方式。     由于是在现有的产品上新增功能,那么为了减少总的成本,故选择只动应用软件的来实现需求。     MQTT的功能直接选择PahoMqtt这个第三方库来实现,因为以

    2024年02月02日
    浏览(75)
  • Android Socket使用TCP协议实现手机投屏

    本节主要通过实战来了解Socket在TCP/IP协议中充当的是一个什么角色,有什么作用。通过Socket使用TCP协议实现局域网内手机A充当服务端,手机B充当客户端,手机B连接手机A,手机A获取屏幕数据转化为Bitmap,通过Socket传递个手机B显示。 实现效果: Socket 是应用层与TCP/IP协议族通

    2024年02月13日
    浏览(47)
  • 使用 python socket 实现UDP/TCP网络通信

    目录 目录 1.socket简介 2.创建socket 2.1创建UDPSocket 2.2创建TCPSocket 3.使用UDPSocket发送数据并接收 4.使用UDPSocket发送广播 5.UDPSocket聊天器 (多线程实现消息的收发功能) 6.使用TCPSocket建立客户端 7.使用TCPSocket建立服务端        socket(简称:套接字),是支持TCP和UDP(网络传输方式

    2023年04月10日
    浏览(64)
  • 使用Socket实现TCP版的回显服务器

    Socket(Java套接字)是Java编程语言提供的一组类和接口,用于实现网络通信。它基于Socket编程接口,提供了一种简单而强大的方式来实现网络应用程序。 Socket类库提供了丰富的方法和功能,用于处理网络通信的各个方面。它支持TCP和UDP协议,可以实现可靠的、面向连接的通信

    2024年02月14日
    浏览(47)
  • Python使用 Twisted 实现 TCP/UDP Socket 编程

    更多文章: 技数未来 环境准备: - 安装Python:确保你已经安装了Python解释器。 - 安装Twisted:可以通过pip命令来安装Twisted库,运行`pip install twisted`即可。 依赖的类库: - twisted.internet.protocol:包含了实现TCP/UDP Socket编程所需的协议类。 - twisted.internet.reactor:用于处理事件循环

    2024年02月16日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包