简易版SSL加密聊天程序分为服务端跟客服端,主要实现简易的加密聊天功能。服务端支持同时与多个客服端保持通信(连接),采用OpenSSL开源库实现加密功能,依赖客户端主动发起连接与对话方能回复客户端,能够检测到某个客户端断开连接。客户端指定IP与端口与服务端建立连接,需要主动发起对话方能等待服务端的回复。需要用到源文件有sslServer.c、sslClient、api.c和ssl.h,同时我们还需要利用openssl命令生成私有密钥文件privkey.pem以及证书(公有密钥)文件cacert.pem。
一、下面列出四个需要用到的C语言源文件:
1、ssl.h
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
typedef struct _SockList {
int fd;
SSL *ssl;
char ip[16];
struct _SockList *next;
}SockList;
int ssl_init(SSL_CTX **ctx);
int ssl_load(SSL_CTX *ctx, char *certificate, char *privateKey);
int ssl_accept(SSL_CTX *ctx, int sockfd, SSL **ssl);
int ssl_connect(SSL_CTX *ctx, int *sockfd, SSL **ssl, char *port, char *addr);
int ssl_close(SSL_CTX *ctx, SSL *ssl, int sockfd, int new_fd);
2、api.c
#include "ssl.h"
int ssl_init(SSL_CTX **ctx)
{
/* SSL 库初始化 */
SSL_library_init();
/* 载入所有 SSL 算法 */
OpenSSL_add_all_algorithms();
/* 载入所有 SSL 错误消息 */
SSL_load_error_strings();
/* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
*ctx = SSL_CTX_new(SSLv23_server_method());
/* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */
if (NULL == *ctx)
{
ERR_print_errors_fp(stderr);
return -1;
}
return 0;
}
int ssl_load(SSL_CTX *ctx, char *certificate, char *privateKey)
{
if (NULL == ctx || NULL == certificate || NULL == privateKey) return -1;
/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
if (SSL_CTX_use_certificate_file(ctx,certificate, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
return -1;
}
/* 载入用户私钥 */
if (SSL_CTX_use_PrivateKey_file(ctx, privateKey, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
return -1;
}
/* 检查用户私钥是否正确 */
if (!SSL_CTX_check_private_key(ctx))
{
ERR_print_errors_fp(stderr);
return -1;
}
return 0;
}
int createListen(int *listen_fd, char *port, char *addr)
{
int yes = 1;
struct sockaddr_in my_addr;
if (NULL == listen_fd || NULL == port) return -1;
/* 开启一个 socket 监听 */
if ((*listen_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
return -1;
}
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(atoi(port));
if (addr)
my_addr.sin_addr.s_addr = inet_addr(addr);
else
my_addr.sin_addr.s_addr = INADDR_ANY;
if (-1 == setsockopt(*listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)))
{
perror("setsockopt");
close(*listen_fd);
return -1;
}
if (bind(*listen_fd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) < 0)
{
perror("bind");
close(*listen_fd);
return -1;
}
if (listen(*listen_fd, 3) < 0)
{
perror("listen");
close(*listen_fd);
return -1;
}
return 0;
}
int ssl_accept(SSL_CTX *ctx, int listen_fd, SSL **ssl)
{
struct sockaddr_in their_addr;
socklen_t len = sizeof(struct sockaddr);
int new_fd;
if (NULL == ctx || listen_fd < 0 || NULL == ssl) return -1;
/* 等待客户端连上来 */
bzero(&their_addr, sizeof(their_addr));
if ((new_fd = accept(listen_fd, (struct sockaddr *)&their_addr, &len)) < 0)
{
perror("accept");
return -1;
} else
printf("\n>>>>>>> got connection from %s, port %d, socket %d <<<<<<<\n",
inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
/* 基于 ctx 产生一个新的 SSL */
*ssl = SSL_new(ctx);
/* 将连接用户的 socket 加入到 SSL */
SSL_set_fd(*ssl, new_fd);
/* 建立 SSL 连接 */
if (SSL_accept(*ssl) < 0)
{
perror("accept");
close(new_fd);
return -1;
}
return new_fd;
}
int ssl_connect(SSL_CTX *ctx, int *sockfd, SSL **ssl, char *port, char *addr)
{
struct sockaddr_in dest;
if (NULL == ctx || NULL == sockfd ||
NULL == ssl || NULL == port || NULL == addr)
return -1;
/* 创建一个 socket 用于 tcp 通信 */
if ((*sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("Socket");
return -1;
}
/* 初始化服务器端(对方)的地址和端口信息 */
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(port));
if (inet_aton(addr, (struct in_addr *) &dest.sin_addr.s_addr) == 0)
{
perror(addr);
return -1;
}
/* 连接服务器 */
if (connect(*sockfd, (struct sockaddr *) &dest, sizeof(dest)) < 0)
{
perror("Connect ");
return -1;
}
/* 基于 ctx 产生一个新的 SSL */
*ssl = SSL_new(ctx);
SSL_set_fd(*ssl, *sockfd);
/* 建立 SSL 连接 */
if (SSL_connect(*ssl) < 0)
{
ERR_print_errors_fp(stderr);
return -1;
}
return 0;
}
int ssl_close(SSL_CTX *ctx, SSL *ssl, int listen_fd, int new_fd)
{
if (NULL == ctx || NULL == ssl || listen_fd < 0 || new_fd < 0) return -1;
/* 关闭 SSL 连接 */
SSL_shutdown(ssl);
/* 释放 SSL */
SSL_free(ssl);
/* 关闭 socket(服务端专用) */
if (new_fd)
close(new_fd);
/* 关闭监听的 socket */
close(listen_fd);
/* 释放 CTX */
SSL_CTX_free(ctx);
return 0;
}
int disconnect(SockList *head, int fd)
{
if (NULL == head || fd < 0) return -1;
SockList *preNode, *curNode;
preNode = head;
curNode = head->next;
while (curNode)
{
if (curNode->fd == fd)
{
printf("\n>>>>>>> disconnect with the fd %d from %s\n", curNode->fd, curNode->ip);
preNode->next = curNode->next;
SSL_shutdown(curNode->ssl);
SSL_free(curNode->ssl);
close(fd);
free(curNode);
break;
}
preNode = preNode->next;
curNode = curNode->next;
}
return 0;
}
3、sslServer.c
#include "ssl.h"
#define MAXLEN 2048
int main(int argc, char *argv[])
{
int listen_fd, new_fd, max_fd;
int ret, recv_len;
SSL_CTX *ctx;
SSL *ssl;
fd_set readfds;
time_t now;
char *pos;
char recvBuf[MAXLEN];
SockList head, *tmpNode;
socklen_t cli_len = sizeof(struct sockaddr);
struct timeval timeout;
struct sockaddr_in cli;
if (argc != 4)
{
printf("Please set the arguments as follow:\n");
printf("%s <port> <certificate> <privateKey>\n", argv[0]);
return -1;
}
signal(SIGPIPE, SIG_IGN);
if (ssl_init(&ctx)) return -1;
if (ssl_load(ctx, argv[2], argv[3])) return -1;
if (createListen(&listen_fd, argv[1], NULL)) return -1;
bzero(&head, sizeof(head));
while (1)
{
FD_ZERO(&readfds);
max_fd = listen_fd;
FD_SET(listen_fd, &readfds);
tmpNode = head.next;
while (tmpNode)
{
max_fd = max_fd > tmpNode->fd ? max_fd : tmpNode->fd;
FD_SET(tmpNode->fd, &readfds);
tmpNode = tmpNode->next;
}
timeout.tv_sec = 10;
timeout.tv_usec = 0;
ret = select(max_fd + 1, &readfds, NULL, NULL, &timeout);
if(ret <= 0)
continue;
else
{
if (FD_ISSET(listen_fd, &readfds))
{
new_fd = ssl_accept(ctx, listen_fd, &ssl);
if (new_fd < 0) continue;
tmpNode = (SockList *)malloc(sizeof(SockList));
memset(tmpNode, 0, sizeof(SockList));
tmpNode->fd = new_fd;
tmpNode->ssl = ssl;
bzero(&cli, sizeof(cli));
getpeername(new_fd, (struct sockaddr *)&cli, &cli_len);
strncpy(tmpNode->ip, inet_ntoa(cli.sin_addr), sizeof(tmpNode->ip));
tmpNode->next = head.next;
head.next = tmpNode;
}
tmpNode = head.next;
while (tmpNode)
{
if (FD_ISSET(tmpNode->fd, &readfds))
{
memset(recvBuf, 0, sizeof(recvBuf));
recv_len = SSL_read(tmpNode->ssl, recvBuf, sizeof(recvBuf));
if (recv_len <= 0)
{
ERR_print_errors_fp(stderr);
disconnect(&head, tmpNode->fd);
}
else
{
now = time(NULL);
pos = ctime(&now);
pos[strlen(pos)-1] = '\0';
printf("\n[%s] Recv Msg from Client %s, fd %d >>>>>>> %s\n",
pos, tmpNode->ip, tmpNode->fd, recvBuf);
printf("[%s] Input the Msg you want to Response <<<<<<< ", pos);
memset(recvBuf, 0, sizeof(recvBuf));
scanf("%s", recvBuf);
SSL_write(tmpNode->ssl, recvBuf, strlen(recvBuf));
}
break;
}
tmpNode = tmpNode->next;
}
}
}
/* 释放 CTX */
SSL_CTX_free(ctx);
return 0;
}
4、sslClient.c
#include "ssl.h"
#define MAXLEN 2048
int main(int argc, char *argv[])
{
int sockfd;
int len;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
char buf[MAXLEN];
time_t now;
char *pos;
if (argc != 3)
{
printf("Please set the arguments as follow:\n");
printf("%s <serverIP> <port>\n", argv[0]);
return -1;
}
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL)
{
ERR_print_errors_fp(stdout);
return -1;
}
if (ssl_connect(ctx, &sockfd, &ssl, argv[2], argv[1])) return -1;
now = time(NULL);
pos = ctime(&now);
pos[strlen(pos)-1] = '\0';
while (1)
{
bzero(buf, sizeof(buf));
printf("[%s] Input the Msg send to Server <<<<<<< ", pos);
scanf("%s", buf);
len = SSL_write(ssl, buf, strlen(buf));
if (len <= 0)
{
ERR_print_errors_fp(stderr);
printf("Disconnect with the Server!\n");
return -1;
}
bzero(buf, sizeof(buf));
len = SSL_read(ssl, buf, sizeof(buf));
if (len <= 0)
{
ERR_print_errors_fp(stderr);
printf("Disconnect with the Server!\n");
return -1;
}
now = time(NULL);
pos = ctime(&now);
pos[strlen(pos)-1] = '\0';
printf("\n[%s] Recv Msg from Server >>>>>>> %s\n", pos, buf);
}
return 0;
}
二、编译程序
gcc sslServer.c api.c -o server -lssl gcc sslClient.c api.c -o client -lssl
三、生成私有密钥文件与证书文件
openssl genrsa -out privkey.pem 2048 openssl req -new -x509 -key privkey.pem -out cacert.pem -days 3650
第二步生成证书文件cacert.pem的时候,按提示依次输入国家(两位英文字母,如中国“CN”)、省份名称(如“Beijing”或“北京”)、城市名称(如“Beijing”或“北京”)、公司名称(如“Haier”或“海尔”)、部门名称(如“R&D”或“研发部”)、你的名字(如“Tom”或“汤姆”)以及你的邮箱地址。以上数据纯粹为了接收方方便辨别证书提供方用,所以不必太过真实。
四、运行程序
./server 8888 cacert.pem privkey.pem ./client 127.0.0.1 8888
五、以上就是简易版SSL加密聊天程序的全部内容,接下来的工作就是留待大家通过该程序慢慢研究SSL通信的实现机制了,如有问题,欢迎留言。
除非注明,文章均为CppLive 编程在线原创,转载请注明出处,谢谢。



