使用 OpenSSL 实现 SSL/TLS 握手的流程和 Demo 示例
使用 OpenSSL 实现 SSL/TLS 握手的流程和 Demo 示例
- 1. 什么是 SSL/TLS 握手?
- 2. SSL/TLS 握手流程解析
- 2.1 客户端 Hello
- 2.2 服务端 Hello
- 2.3 服务端证书
- 2.4 客户端密钥交换
- 2.5 会话密钥生成
- 2.6 握手完成
- 3. 使用 OpenSSL 实现 SSL/TLS Demo
- 3.1 环境准备
- 3.2 服务端代码
- 3.3 客户端代码
- 3.4 编译和运行
1. 什么是 SSL/TLS 握手?
SSL/TLS 是保护网络通信的基础协议,主要用于确保数据传输的安全性、完整性和机密性。在 SSL/TLS 握手过程中,客户端和服务端协商会话密钥和加密参数,以确保后续通信的安全性。
2. SSL/TLS 握手流程解析
以下是一个典型的 SSL/TLS 握手流程:
2.1 客户端 Hello
- 步骤:客户端发送
ClientHello
消息。 - 内容:
- 支持的协议版本(如 TLS 1.2/1.3)。
- 支持的加密套件(如 AES、RSA 等)。
- 一个随机数(用于生成会话密钥)。
- 会话 ID(可选)。
2.2 服务端 Hello
- 步骤:服务端响应
ServerHello
消息。 - 内容:
- 协商的协议版本。
- 选择的加密套件。
- 服务端生成的随机数。
- 服务端的数字证书。
2.3 服务端证书
- 步骤:服务端发送数字证书。
- 内容:证书包含服务端的公钥、服务端域名、证书颁发机构签名等。
2.4 客户端密钥交换
- 步骤:
- 客户端生成一个随机密钥(称为 Pre-Master Secret)。
- 用服务端的公钥加密 Pre-Master Secret,并发送给服务端。
2.5 会话密钥生成
- 步骤:客户端和服务端分别基于双方的随机数和 Pre-Master Secret,通过密钥派生函数生成对称加密密钥。
2.6 握手完成
- 双方使用对称密钥验证握手消息的完整性,确保协商过程未被篡改。
- 此后,双方可以基于对称密钥进行安全通信。
3. 使用 OpenSSL 实现 SSL/TLS Demo
下面的示例展示了如何使用 OpenSSL 编写简单的 SSL/TLS 服务端和客户端。
3.1 环境准备
-
安装 OpenSSL:
sudo apt-get install openssl libssl-dev
-
生成服务端证书和私钥:
openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365
3.2 服务端代码
用 C 语言编写一个简单的 OpenSSL 服务端。
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 4443
void initialize_openssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
SSL_CTX *create_context() {
const SSL_METHOD *method = SSLv23_server_method();
SSL_CTX *ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
void configure_context(SSL_CTX *ctx) {
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0 ||
SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
}
int main() {
int sock;
struct sockaddr_in addr;
initialize_openssl();
SSL_CTX *ctx = create_context();
configure_context(ctx);
sock = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
listen(sock, 1);
printf("Server is listening on port %d...\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client = accept(sock, (struct sockaddr *)&client_addr, &client_len);
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, client);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
const char reply[] = "Hello, SSL Client!\n";
SSL_write(ssl, reply, strlen(reply));
}
SSL_free(ssl);
close(client);
}
close(sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return 0;
}
3.3 客户端代码
编写一个对应的 SSL 客户端。
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 4443
#define SERVER "127.0.0.1"
void initialize_openssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl() {
EVP_cleanup();
}
int main() {
int sock;
struct sockaddr_in addr;
initialize_openssl();
SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
sock = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER, &addr.sin_addr);
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
char buf[1024];
SSL_read(ssl, buf, sizeof(buf));
printf("Received: %s", buf);
}
SSL_free(ssl);
close(sock);
SSL_CTX_free(ctx);
cleanup_openssl();
return 0;
}
3.4 编译和运行
编译服务端和客户端代码:
gcc -o server server.c -lssl -lcrypto
gcc -o client client.c -lssl -lcrypto
运行服务端:
./server
运行客户端:
./client
输出示例:
- 服务端日志:
Server is listening on port 4443...
- 客户端输出:
Received: Hello, SSL Client!