文章参考
深入探索 OpenSSL:概念、原理、开发步骤、使用方法、使用场景及代码示例
c++使用OpenSSL基于socket实现tcp双向认证ssl(使用TSL协议)代码实现
SSL握手通信详解及linux下c/c++ SSL Socket代码举例(另附SSL双向认证客户端代码)
SSL/CA 证书及其相关证书文件(pem、crt、cer、key、csr)
TCP实现OpenSSL原理(TCP3次握手
+OPENSLL四次握手
)
SSL全称安全套接字协议层,为了通信安全,使用RSA非对称加密交换密钥,密钥交换完成后使用对称密钥进行通信。(为什么将非对称加密切换称对称加密是为了提供通信效率。非对称加密效率低)
TCP openssl双向认证:即在TCP三次握手的基础上增加一次openssl的4次握手。
实现OPENSSL四次握手
- 服务端,在已建立的TCP链接基础上调用
SSL_accept
- 客户端,在已建立的TCP链接基础上调用
SSL_connect
TCP SSL 服务端步骤
- 初始化ssl并加载证书和密钥
- TCP服务收到客户端连接之后调用
SSL_accept
进行四次握手 - SSL握手建立完成之后调用
SSL_read
、SSL_write
收发数据 - 使用完成之后关闭并释放SSL资源
SSL_shutdown
、SSL_free
#include <iostream>
#include <tchar.h>
#include <winsock.h>
#include "openssl/ssl.h"
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libcrypto_static.lib")
#pragma comment(lib,"libssl_static.lib")
using namespace std;
int main()
{
WSADATA wsdata;
int errcode = WSAStartup(MAKEWORD(2, 2), &wsdata);
if (errcode != 0)
{
std::cout << "WSAStartup failed,errcode:%d" << errcode<<std::endl;
return 0;
}
//初始化ssl
SSL_library_init();
//加载ssl算法库
OpenSSL_add_all_algorithms();
//加载ssl 错误信息
SSL_load_error_strings();
//以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Context
SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_server_method());
if (ssl_ctx == nullptr)
{
std::cout << "SSL_CTX_new failed"<<std::endl;
return 0;
}
//加载数字证书
if (SSL_CTX_use_certificate_chain_file(ssl_ctx, "ca.crt") <= 0)
{
printf("SSL_CTX_use_certificate_chain_file failed\r\n");
return 0;
}
//加载私钥
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, "ca.key", SSL_FILETYPE_PEM) <= 0)
{
printf("SSL_CTX_use_PrivateKey_file failed\r\n");
return 0;
}
// 检查用户私钥是否正确
if (!SSL_CTX_check_private_key(ssl_ctx))
{
printf("SSL_CTX_check_private_key failed\r\n");
return 0;
}
SSL_CTX_set_timeout(ssl_ctx, 100);
//创建socket->bind->list->accept
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET)
{
printf("socket create failed\r\n");
return 0;
}
//setsocketopt resuse port
unsigned short port = 32100;
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == bind(listen_sock, (sockaddr*)&sin, sizeof(sin)))
{
printf("socket bind failed\r\n");
return 0;
}
if (SOCKET_ERROR == listen(listen_sock, 5))
{
printf("socket listen failed\r\n");
return 0;
}
printf("tcp ssl server:%d\r\n", port);
while (true)
{
char szbuf[1024] = "";
sockaddr_in peer_addr;
int naddr_len = sizeof(peer_addr);
printf("wait client...\r\n");
SOCKET soct_peer = accept(listen_sock, (sockaddr*)&peer_addr, &naddr_len);
printf("accept client ,socket:%d\r\n", soct_peer);
//将socket和ssl绑定(ssl握手)
SSL* ssl_peer = SSL_new(ssl_ctx);
if (ssl_peer == nullptr)
{
printf("socket:%d,SSL_new failed\r\n", soct_peer);
goto freessl;
}
SSL_set_fd(ssl_peer, soct_peer);
//这里会无限阻塞,为了安全应该异步增加一个超时值,如果超时仍然未连接上则应该close
if (SSL_accept(ssl_peer) < 0)
{
printf("socket:%d,SSL_accept failed\r\n", soct_peer);
goto freessl;
}
//开始ssl读写
SSL_read(ssl_peer, szbuf, 1024);
printf("socket[%d] read:%s\r\n", soct_peer, szbuf);
SSL_write(ssl_peer, szbuf, lstrlenA(szbuf));
freessl:
shutdown(soct_peer, 0);
closesocket(soct_peer);
//释放ssl
if(ssl_peer)
SSL_free(ssl_peer);
continue;
}
return 0;
}
TCP SSL 客户端端步骤
- TCP客户端连接成功之后调用
SSL_connect
进行四次握手 - SSL握手建立完成之后调用
SSL_read
、SSL_write
收发数据 - 使用完成之后关闭并释放SSL资源
SSL_shutdown
、SSL_free
#include <iostream>
#include <tchar.h>
#include <winsock.h>
#include "openssl/ssl.h"
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libcrypto_static.lib")
#pragma comment(lib,"libssl_static.lib")
using namespace std;
int main()
{
WSADATA wsdata;
int errcode = WSAStartup(MAKEWORD(2, 2), &wsdata);
if (errcode != 0)
{
std::cout << "WSAStartup failed,errcode:%d" << errcode << std::endl;
return 0;
}
//初始化ssl
SSL_library_init();
//加载ssl算法库
OpenSSL_add_all_algorithms();
//加载ssl 错误信息
SSL_load_error_strings();
//以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Context
SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (ssl_ctx == nullptr)
{
std::cout << "SSL_CTX_new failed" << std::endl;
return 0;
}
//SSL_CTX_set_timeout(ssl_ctx, 100);
//创建socket->connect
SOCKET cli_sock = socket(AF_INET, SOCK_STREAM, 0);
if (cli_sock == INVALID_SOCKET)
{
printf("socket create failed\r\n");
return 0;
}
//setsocketopt resuse port
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(32100);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (SOCKET_ERROR == connect(cli_sock, (sockaddr*)&sin, sizeof(sin)))
{
printf("socket connect failed\r\n");
return 0;
}
printf("connect client ,socket:%d\r\n", cli_sock);
//将socket和ssl绑定(ssl握手)
char szbuf[1024] = "Hellow word!!!";
SSL* ssl_peer = SSL_new(ssl_ctx);
if (ssl_peer == nullptr)
{
printf("socket:%d,SSL_new failed\r\n", cli_sock);
goto freessl;
}
SSL_set_fd(ssl_peer, cli_sock);
//这里会无限阻塞,为了安全应该异步增加一个超时值,如果超时仍然未连接上则应该close
if (SSL_connect(ssl_peer) < 0)
{
printf("socket:%d,SSL_accept failed\r\n", cli_sock);
goto freessl;
}
printf("ssl connectd ok\r\n");
SSL_write(ssl_peer, szbuf, lstrlenA(szbuf));
//开始ssl读写
memset(szbuf, 0, 1024);
SSL_read(ssl_peer, szbuf, 1024);
printf("socket[%d] read:%s\r\n", cli_sock, szbuf);
freessl:
shutdown(cli_sock, 0);
closesocket(cli_sock);
//释放ssl
if (ssl_peer)
SSL_free(ssl_peer);
return 0;
}
NOTE
1. 服务端和客户端初始化SSL_CTX参数不同
//以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Context
//客户端调用openssl客户端的方法
SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());
//服务端调用openssl服务端的方法
SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_server_method());
2. SSL_accept
、SSL_connect
阻塞函数
在同步调用 SSL_accept
、SSL_connect
的时候会进行阻塞,需要定时检测是否超时,如果超时则关闭当前socket,防止恶意链接。例如拿非openssl的客户端连接openssl的服务端,此时不会存在四次握手,在未发送数据前会一直阻塞下去,等待握手的完成。