文章目录
- 1. Socket连接建立流程
- 2、Socket+SSL的初始化流程
- 3、初始化SSL环境,证书和密钥
- 4、Socket+SSL 的c语言实现
- 4.1 编写SSL连接函数
- 4.2 编写加密服务端server.c
- 4.3 编写加密客户端client.c
- 5、使用tcpdump检验
- 源码获取
在进行网络编程的时候,我们通常使用socket进行数据的传输。然而socket作为一个数据传输协议,其本身对数据并不会作加密。所以数据传输的过程可以很轻松地被监听并截获到传输的数据。openssl提供了SSL的加密库,通过ssl+socket的方式可以保证连接安全和数据的加密。
1. Socket连接建立流程
在做socket加密之前,还是先与普通的socket做一个对比。
上面的是我们通常在做一个socket连接的时候会涉及到的握手过程。服务端会通过accept
去接收客户端的请求。客户端通过connect
去连接到客户端。使用send
和recv
去做数据的传输。那么传输过程中的参数data也就是我们交互的数据了。当我们建立连接开始发送数据的时候。使用一些抓包工具wireshark,或者tcpdump去监听socket端口就能很轻松地获取到传输的明文数据了。
2、Socket+SSL的初始化流程
所以为了避免数据的监听,我们就需要使用SSL去建立一个安全的通道。这里我们先把SSL的建立当作一个子流程
SSL子过程主要插入在socket的connect()/accept()和数据交换之间。通过SSL建立完成的所以,一旦SSL握手完成,数据发送和接收的流程与普通的socket通信非常相似,只需要使用SSL_write()和SSL_read()来代替send()和recv()。c
替换 send()
为 SSL_write(ssl, buffer, length)
替换recv()
为 SSL_read(ssl, buffer, length)
3、初始化SSL环境,证书和密钥
- openssl 生成证书(分别生成私钥和自签名证书),确保环境上已经安装完成了openssl
openssl genpkey -algorithm RSA -out key.pem
openssl req -new -x509 -key key.pem -out cert.pem -days 365
days为证书的有效期。命令执行完成后在当前目录下就会生成key.pem
和cert.pem
。记下文件路径,后续会作为参数传入到我们的函数中去。
4、Socket+SSL 的c语言实现
4.1 编写SSL连接函数
在进行SSL初始化的时候需要注意的是,由于连接协商的过程使用的是非对称加密,因此客户端和服务端在初始化的时候是使用的不同的算法。因此,我在函数中加入了一个SSL_MODE参数。用来指明当前是服务端还是客户端的SSL。
SSL* sync_initialize_ssl(const char* cert_path, const char* key_path, SSL_MODE mode, int fd) {
const SSL_METHOD *method;
SSL_CTX *ctx;
SSL *ssl = NULL;
// 初始化OpenSSL库
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
// 根据模式(客户端/服务端)选择合适的方法
if (mode == SSL_MODE_SERVER) {
method = SSLv23_server_method();
} else if (mode == SSL_MODE_CLIENT) {
method = SSLv23_client_method();
} else {
// 未知模式
printf("Not found method");
return NULL;
}
// 创建SSL上下文
ctx = SSL_CTX_new(method);
if (!ctx) {
printf("Unable to create SSL context");
return NULL;
}
// 配置SSL上下文
if (SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) <= 0 || SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0 ) {
printf("Not found certificate or private key");
SSL_CTX_free(ctx);
return NULL;
}
// SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
// 创建SSL对象
ssl = SSL_new(ctx);
if (!ssl) {
printf("Failed to create SSL object.");
return NULL;
}
// 设置文件描述符
if (SSL_set_fd(ssl, fd) == 0) {
printf("Failed to set fd to SSL object.");
return NULL;
}
// SSL握手
if ((mode == SSL_MODE_CLIENT && SSL_connect(ssl) <= 0) ||
(mode == SSL_MODE_SERVER && SSL_accept(ssl) <= 0)) {
int ssl_result;
if (mode == SSL_MODE_CLIENT) {
ssl_result = SSL_connect(ssl);
} else if (mode == SSL_MODE_SERVER) {
ssl_result = SSL_accept(ssl);
}
int ssl_err = SSL_get_error(ssl, ssl_result);
const char *err_str;
switch (ssl_err) {
case SSL_ERROR_NONE:
err_str = "No error";
break;
case SSL_ERROR_SSL:
err_str = "Error in the SSL protocol";
break;
case SSL_ERROR_WANT_READ:
err_str = "SSL read operation did not complete";
break;
case SSL_ERROR_WANT_WRITE:
err_str = "SSL write operation did not complete";
break;
case SSL_ERROR_WANT_X509_LOOKUP:
err_str = "SSL X509 lookup operation did not complete";
break;
case SSL_ERROR_SYSCALL:
err_str = "Syscall error";
break;
case SSL_ERROR_ZERO_RETURN:
err_str = "SSL connection was shut down cleanly";
break;
case SSL_ERROR_WANT_CONNECT:
err_str = "SSL connect operation did not complete";
break;
case SSL_ERROR_WANT_ACCEPT:
err_str = "SSL accept operation did not complete";
break;
default:
err_str = "Unknown error";
break;
}
printf("===============SSL handshake failed. Error: %s========!\n", err_str ? err_str : "Unknown");
return NULL;
}
return ssl;
}
4.2 编写加密服务端server.c
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define SERVER_PORT 9990
#define MAXLINE 4096
typedef enum {
SSL_MODE_SERVER,
SSL_MODE_CLIENT
} SSL_MODE;
int main(int argc, char **argv) {
int listenfd, connfd;
struct sockaddr_in servaddr, cliaddr;
char buf[MAXLINE];
// 创建监听套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 绑定地址和端口
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERVER_PORT);
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
// 监听连接
listen(listenfd, 10);
printf("Listening on port %d...\n", SERVER_PORT);
while (1) {
// 接受连接请求
socklen_t len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &len);
printf("Accepted connection from %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
// 创建 SSL 对象并进行握手
SSL *ssl = sync_initialize_ssl("cert.pem", "key.pem", SSL_MODE_SERVER, connfd);
// 读取客户端发送的数据并回复
memset(buf, 0, MAXLINE);
SSL_read(ssl, buf, MAXLINE);
printf("Received: %s\n", buf);
SSL_write(ssl, "Hello, client!", strlen("Hello, client!"));
// 关闭连接和清理资源
close(connfd);
SSL_shutdown(ssl);
SSL_free(ssl);
}
return 0;
}
4.3 编写加密客户端client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define SERVER_IP "127.0.0.1" // 请根据需要更改服务器IP
#define SERVER_PORT 9990
#define MAXLINE 4096
typedef enum {
SSL_MODE_SERVER,
SSL_MODE_CLIENT
} SSL_MODE;
int main(int argc, char **argv) {
int sockfd;
struct sockaddr_in servaddr;
char buf[MAXLINE];
// 创建 socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务器地址和端口
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
servaddr.sin_port = htons(SERVER_PORT);
// 连接到服务器
connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
// 创建 SSL 对象并进行握手
SSL *ssl = sync_initialize_ssl("cert.pem", "key.pem", SSL_MODE_CLIENT, sockfd);
// 发送数据给服务器
SSL_write(ssl, "Hello, server!", strlen("Hello, server!"));
// 读取服务器的回复
memset(buf, 0, MAXLINE);
SSL_read(ssl, buf, MAXLINE);
printf("Received: %s\n", buf);
// 关闭连接和清理资源
close(sockfd);
SSL_shutdown(ssl);
SSL_free(ssl);
return 0;
}
5、使用tcpdump检验
服务端和客户端编写完成后,分别运行起来,这里我运行的端口是9990。使用tcpdump抓取9990端口的传输数据
tcpdump -i any port 9990 -A
此时分别运行后,服务端客户端数据完成交互后,抓包所看到的数据已经是密文传输了。
源码获取
需要获取源码的可以关注公众号"一颗程序树",点击菜单栏的免费源码输入关键词socket即可