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协议的双向认证就出现了根证书()、公钥证书(数字证书)、私钥(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
上图中就是1、2的步骤也被称为ssl握⼿,由OpenSSL库实现,开发者⽆需关注代码实现,懂得原理就⾏。
2.4、握⼿阶段的详细过程
握⼿涉及四次通信,且握⼿阶段都是明⽂,详细来看
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)/ $(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");
return1;// 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);马克思诞辰200周年
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,"",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,"", 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/ciph ", 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));
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 连接 */
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

本文发布于:2024-09-22 17:30:39,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/230962.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:证书   服务器   认证   客户端   加密   公钥   双向
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议