下面是一个简单的 C 语言程序示例,它展示了如何使用 OpenSSL 来实现基于 TLS 的加密 TCP 通信。这个程序包括一个服务器和一个客户端,它们通过 TLS 加密的 TCP 连接进行通信。
步骤概览
初始化 OpenSSL 库。
创建 SSL 上下文(SSL_CTX)。
在服务器端,加载服务器证书和私钥;在客户端,加载 CA 证书。
使用 SSL 套接字进行加密通信。
服务器端代码
c
复制代码
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 4444
#define CERT_FILE “server.crt”
#define KEY_FILE “server.key”
void handle_client(SSL *ssl) {
char buf[1024];
int bytes;
// 接收数据
bytes = SSL_read(ssl, buf, sizeof(buf));
if (bytes > 0) {
buf[bytes] = 0;
printf("Client message: %s\n", buf);
// 发送数据
SSL_write(ssl, "Hello from server", strlen("Hello from server"));
} else {
ERR_print_errors_fp(stderr);
}
// 关闭 SSL 连接
SSL_shutdown(ssl);
SSL_free(ssl);
}
int main() {
SSL_CTX *ctx;
SSL *ssl;
int server_fd, client_fd;
struct sockaddr_in addr;
// 初始化 OpenSSL
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
// 创建 SSL 上下文
ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 加载服务器证书和私钥
if (SSL_CTX_use_certificate_file(ctx, CERT_FILE, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, SSL_FILETYPE_PEM) <= 0 ) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 创建服务器 socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Unable to create server socket");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to bind server socket");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 1) < 0) {
perror("Unable to listen on server socket");
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d...\n", PORT);
// 接受客户端连接
client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) {
perror("Unable to accept client connection");
exit(EXIT_FAILURE);
}
// 为连接创建 SSL 对象
ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_fd);
// 执行 TLS/SSL 握手
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
handle_client(ssl);
}
close(client_fd);
close(server_fd);
SSL_CTX_free(ctx);
EVP_cleanup();
return 0;
}
客户端代码
c
复制代码
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 4444
#define SERVER_IP “127.0.0.1”
#define CA_CERT_FILE “ca.crt”
void communicate_with_server(SSL *ssl) {
char buf[1024];
int bytes;
// 发送数据
SSL_write(ssl, "Hello from client", strlen("Hello from client"));
// 接收数据
bytes = SSL_read(ssl, buf, sizeof(buf));
if (bytes > 0) {
buf[bytes] = 0;
printf("Server message: %s\n", buf);
} else {
ERR_print_errors_fp(stderr);
}
}
int main() {
SSL_CTX *ctx;
SSL *ssl;
int server_fd;
struct sockaddr_in addr;
// 初始化 OpenSSL
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
// 创建 SSL 上下文
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 加载 CA 证书
if (!SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL)) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 创建客户端 socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Unable to create client socket");
exit(EXIT_FAILURE);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER_IP, &addr.sin_addr);
if (connect(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to connect to server");
exit(EXIT_FAILURE);
}
// 为连接创建 SSL 对象
ssl = SSL_new(ctx);
SSL_set_fd(ssl, server_fd);
// 执行 TLS/SSL 握手
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
communicate_with_server(ssl);
}
// 关闭 SSL 连接
SSL_shutdown(ssl);
SSL_free(ssl);
close(server_fd);
SSL_CTX_free(ctx);
EVP_cleanup();
return 0;
}
运行步骤
生成证书和密钥:
生成 CA 证书和密钥。
生成服务器证书和密钥,并使用 CA 证书对其签名。
将 CA 证书分发给客户端以进行验证。
例如,可以使用 OpenSSL 命令生成这些文件:
bash
复制代码
openssl genpkey -algorithm RSA -out ca.key
openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.crt
openssl genpkey -algorithm RSA -out server.key
openssl req -new -key server.key -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256
编译:
使用编译器编译服务器和客户端代码,并确保链接 OpenSSL 库:
bash
复制代码
gcc -o server server.c -lssl -lcrypto
gcc -o client client.c -lssl -lcrypto
运行:
启动服务器程序:
bash
复制代码
./server
启动客户端程序:
bash
复制代码
./client
说明
服务器端会加载自己的证书和私钥,并等待客户端连接。
客户端会连接到服务器,并验证服务器的证书。
双方通过 SSL/TLS 进行加密通信。
这个简单的例子展示了如何使用 OpenSSL 在 C 语言中实现基于 TLS 的安全通信。实际应用中,可能需要更多的错误处理和其他安全措施。
上面代码中“执行 TLS/SSL 握手”具体是怎么样的?
ChatGPT
在上面的代码中,“执行 TLS/SSL 握手”是通过调用 SSL_connect(客户端)和 SSL_accept(服务器端)函数来完成的。TLS/SSL 握手的主要目的是在客户端和服务器之间建立一个安全的加密通信通道。具体来说,握手过程包括以下几个步骤:
- 客户端发起握手请求
客户端通过 SSL_connect 函数发起握手请求。这个函数会执行以下操作:
生成随机数:客户端生成一个随机数,用于后续的密钥生成。
发送客户端问候消息(ClientHello):客户端发送一个包含加密套件列表、SSL/TLS 版本号、随机数等信息的消息给服务器。
2. 服务器响应客户端
服务器通过 SSL_accept 函数响应客户端的握手请求,执行以下操作:
选择加密套件:服务器从客户端提供的加密套件列表中选择一个双方都支持的加密套件。
生成随机数:服务器生成另一个随机数。
发送服务器问候消息(ServerHello):服务器将选定的加密套件、自己的随机数、证书等信息发送给客户端。
发送证书:服务器会将自己的公钥证书发送给客户端,以便客户端验证服务器的身份。
3. 客户端验证服务器证书
客户端收到服务器的证书后,会进行以下操作:
验证证书:客户端使用预先加载的 CA 证书来验证服务器证书的有效性。如果验证失败,握手将中止。
生成预主密钥(Pre-Master Secret):客户端生成一个预主密钥,并用服务器的公钥加密,然后发送给服务器。
4. 服务器和客户端生成会话密钥
服务器解密预主密钥:服务器使用自己的私钥解密预主密钥。
生成会话密钥:客户端和服务器各自使用之前的随机数和预主密钥,生成对称会话密钥。这个密钥将用于加密后续的通信数据。
5. 完成握手
交换Finished消息:客户端和服务器交换一个“Finished”消息,用以确认握手阶段的所有数据都已成功传递且未被篡改。
开始加密通信:握手完成后,客户端和服务器就可以使用对称会话密钥进行加密通信了。
握手过程中的函数调用
在代码中,客户端和服务器的握手分别通过以下函数调用实现:
客户端:SSL_connect(ssl):客户端通过此函数与服务器进行握手。这一步将触发上述的 ClientHello 消息发送,接收服务器的 ServerHello 消息,并完成密钥交换。
服务器:SSL_accept(ssl):服务器通过此函数接受客户端的握手请求,并返回服务器的证书和其他必要信息,最终完成握手。
握手过程的图解
plaintext
复制代码
Client Server
| |
| ClientHello |
|----------------------------->|
| |
| ServerHello |
|<-----------------------------|
| |
| Server Certificate |
|<-----------------------------|
| |
| Client Key Exchange |
|----------------------------->|
| |
| |
|<---- ChangeCipherSpec --------|
| Finished |
| |
| |
| ChangeCipherSpec |
|<------------------------------|
| Finished |
| |
|— Encrypted Application Data -->
|<— Encrypted Application Data–|
| |
结论
“执行 TLS/SSL 握手”涉及一系列复杂的消息交换和加密操作,以确保双方能够在一个安全的、加密的通道上进行通信。在握手期间,双方会验证彼此的身份,协商加密方式,并生成用于加密通信的对称密钥。