简易版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 编程在线原创,转载请注明出处,谢谢。