2018-2019 1 20165203 實驗五 通用協議設計
OpenSSL學習
- 定義:OpenSSL是為網絡通信提供安全及數據完整性的一種安全協議,囊括了主要的密碼算法、常用的密鑰和證書封裝管理功能以及SSL協議,并提供了豐富的應用程序供測試或其它目的使用。
- 基本功能:
- 密碼算法庫
- SSL協議庫
- 應用程序
- 應用程序
- 其中:密碼算法庫是一個強大完整的密碼算法庫,它是OpenSSL的基礎部分,也是很值得一般密碼安全技術人員研究的部分,它實現了目前大部分主流的密碼算法和標準。主要包括對稱算法、非對稱算法、散列算法、數字簽名和認證、X509數字證書標準、PKCS12、PKCS7等標準。其他兩個功能部分SSL協議和應用程序都是基于這個庫開發的。
- 在密碼算法庫的基礎上實現的,SSL協議部分完全實現和封裝了SSL協議的三個版本和TLS協議。使用協議庫,完全可以建立一個SSL服務器和SSL客戶端。
- 應用程序是基于密碼算法庫和SSL協議庫實現的命令,熟悉OpenSSL可以從使用這些應用程序開始。應用程序覆蓋了密碼技術的應用,主要包括了各種算法的加密程序和各種類型密鑰的產生程序(如RSA、Md5、Enc等等)、證書簽發和驗證程序(如Ca、X509、Crl等)、SSL連接測試程序(如S_client和S_server等)以及其它的標準應用程序(如Pkcs12和Smime等)。
任務一
兩人一組
基于Socket實現TCP通信,一人實現服務器,一人實現客戶端
研究OpenSSL算法,測試對稱算法中的AES,非對稱算法中的RSA,Hash算法中的MD5
選用合適的算法,基于混合密碼系統實現對TCP通信進行機密性、完整性保護。
學有余力者,對系統進行安全性分析和改進。
- 實驗步驟:
1.安裝Openssl。 - 在虛擬機的Linux系統中前往Openssl官網,下載
openssl-master.zip
。 - 下載完畢后,利用
unzip openssl-master.zip
命令解壓,解壓后如圖所示。
- 利用如下命令進行安裝。
$ ./config
$ make
$ make test
$ make install
- 安裝完畢即可。
2.進行安裝測試。
- 編寫一個測試代碼test_openssl.c
#include <stdio.h>
#include <openssl/evp.h>int main(){OpenSSL_add_all_algorithms();return 0;
}
使用命令
gcc -o test_openssl test_openssl.c -L/usr/local/ssl/lib -lcrypto -ldl -lpthread
進行編譯,生成“test_openssl”可執行文件,運行。接下來,執行echo $?
,打印出的結果是0,則代表運行成功。命令的學習:
-L選項——指定鏈接庫的文件夾地址;
-lcrypto——導入OpenSSL所需包;
-ldl選項——加載動態庫;
-lpthread選項——鏈接POSIX thread庫
3.基于Socket實現TCP通信,實現服務器和客戶端的通信。連接后,如圖所示。
4.選用合適的算法,基于混合密碼系統實現對TCP通信進行機密性、完整性保護。
- AES
編寫代碼aes.c
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/aes.h>
#pragma comment(lib,"libeay32.lib")
int main(int argc, char **argv)
{unsigned char buf[512];unsigned char buf2[512];unsigned char buf3[512];unsigned char aes_keybuf[32];memset(buf,1,sizeof(buf)); memset(buf,0,sizeof(buf2)); memset(buf,0,sizeof(buf3));memset(aes_keybuf,0,sizeof(aes_keybuf));AES_KEY aeskey;AES_set_encrypt_key(aes_keybuf,256,&aeskey);for(int i=0;i<sizeof(buf);i+=16)AES_encrypt(buf+i,buf2+i,&aeskey);AES_set_decrypt_key(aes_keybuf,256,&aeskey);for(int i=0;i<sizeof(buf);i+=16)AES_decrypt(buf2+i,buf3+i,&aeskey);if(memcmp(buf,buf3,sizeof(buf))==0)printf("test success\r\n");elseprintf("test fail\r\n");
}
使用gcc aes.c -o aes -L/usr/local/ssl/lib -lcrypto -ldl -lpthread
進行編譯,并運行,運行完結果如圖所示。
- RSA算法
編寫rsa.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<openssl/rsa.h>
#include<openssl/pem.h>
#include<openssl/err.h>
#define OPENSSLKEY "test.key"
#define PUBLICKEY "test_pub.key"
#define BUFFSIZE 1024
char* my_encrypt(char *str,char *path_key);//加密
char* my_decrypt(char *str,char *path_key);//解密
int main(void){char *source="I'm 20165203xyx who is a nice student.";char *ptr_en,*ptr_de;printf("source is :%s\n",source);ptr_en=my_encrypt(source,PUBLICKEY);printf("after encrypt:%s\n",ptr_en);ptr_de=my_decrypt(ptr_en,OPENSSLKEY);printf("after decrypt:%s\n",ptr_de);if(ptr_en!=NULL){free(ptr_en);} if(ptr_de!=NULL){free(ptr_de);} return 0;
}
char *my_encrypt(char *str,char *path_key){char *p_en;RSA *p_rsa;FILE *file;int flen,rsa_len;if((file=fopen(path_key,"r"))==NULL){perror("open key file error");return NULL; } if((p_rsa=PEM_read_RSA_PUBKEY(file,NULL,NULL,NULL))==NULL){ERR_print_errors_fp(stdout);return NULL;} flen=strlen(str);rsa_len=RSA_size(p_rsa);p_en=(unsigned char *)malloc(rsa_len+1);memset(p_en,0,rsa_len+1);if(RSA_public_encrypt(rsa_len,(unsigned char *)str,(unsigned char*)p_en,p_rsa,RSA_NO_PADDING)<0){return NULL;}RSA_free(p_rsa);fclose(file);return p_en;
}
char *my_decrypt(char *str,char *path_key){char *p_de;RSA *p_rsa;FILE *file;int rsa_len;if((file=fopen(path_key,"r"))==NULL){perror("open key file error");return NULL;}if((p_rsa=PEM_read_RSAPrivateKey(file,NULL,NULL,NULL))==NULL){ERR_print_errors_fp(stdout);return NULL;}rsa_len=RSA_size(p_rsa);p_de=(unsigned char *)malloc(rsa_len+1);memset(p_de,0,rsa_len+1);if(RSA_private_decrypt(rsa_len,(unsigned char *)str,(unsigned char*)p_de,p_rsa,RSA_NO_PADDING)<0){return NULL;}RSA_free(p_rsa);fclose(file);return p_de;
}
同樣的方法編譯運行,得到結果,成功。
- MD5算法。
#define _GNU_SOURCE#include <stdio.h>#include <string.h>#include <stdlib.h>#include <errno.h>#include <ctype.h>#include "openssl/md5.h"MD5_CTX md5_ctx;
static int MD5mod(const char* str, int length, int mod){char sign[16] = {0};MD5_Init(&md5_ctx);MD5_Update(&md5_ctx, str, length);MD5_Final(sign, &md5_ctx);printf("digest:%s\n",sign);int sum = 0;for (int i=0; i < 16; i ++) {sum += (sign[i]&0xff);}int offset = sum % mod;return offset;}int main(int argc, char** argv){if( argc < 4){fprintf(stderr, "%s num infile outfile\n", argv[0]);exit(-1);}int num = atoi(argv[1]) ;if( num <= 0){fprintf(stderr, "ERROR: num error: %s\n", argv[1]);exit(-1);}FILE* in = fopen(argv[2], "r");if( in == NULL){perror("fopen");fprintf(stderr, "ERROR: infile error: %s\n", argv[2]);exit(-1);}FILE** OUT = (FILE**)malloc(sizeof(FILE*) * num);for(int i=0; i<num; ++i){char buf[256] = {0};sprintf(buf, "%s_%d", argv[3], i);OUT[i] = fopen(buf, "w");if( OUT[i] == NULL){perror("fopen");fprintf(stderr, "ERROR: infile error: %s\n", argv[2]);exit(-1);}}size_t len = 0;ssize_t read;char * line = NULL;while ((read = getline(&line, &len, in)) != -1) {int klen = 0;while( klen < read ){if( isspace( *(line+klen)) ) break;klen++;}// char id[256]={0};// strncpy(id, line, klen);// printf("id=%s\tklen=%d\tread=%ld\tline=%s", id, klen, read, line);fprintf(OUT[MD5mod(line, klen, num)], "%s", line);}if(line) free(line);return 0;
}
編譯運行,如圖所示。
任務二
在Ubuntu中實現對實驗二中的“wc服務器”通過混合密碼系統進行防護
提交測試截圖
- 學習wc服務器模式如下。
- 編寫代碼如下。
server.c
#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>#include <openssl/evp.h>#define MAXBUF 1024int main(int argc, char **argv)
{int sockfd, new_fd;socklen_t len;struct sockaddr_in my_addr, their_addr;unsigned int myport, lisnum;char buf[MAXBUF + 1];SSL_CTX *ctx;if (argv[1])myport = atoi(argv[1]);elsemyport = 5227;if (argv[2])lisnum = atoi(argv[2]);elselisnum = 2;/* 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 (ctx == NULL) {ERR_print_errors_fp(stdout);exit(1);}/* 載入用戶的數字證書, 此證書用來發送給客戶端。 證書里包含有公鑰 */if (SSL_CTX_use_certificate_file(ctx, argv[3], SSL_FILETYPE_PEM) <= 0) {ERR_print_errors_fp(stdout);exit(1);}/* 載入用戶私鑰 */if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0){ERR_print_errors_fp(stdout);exit(1);}/* 檢查用戶私鑰是否正確 */if (!SSL_CTX_check_private_key(ctx)) {ERR_print_errors_fp(stdout);exit(1);}/* 開啟一個 socket 監聽 */if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {perror("socket");exit(1);} elseprintf("socket created\n");bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = PF_INET;my_addr.sin_port = htons(myport);my_addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))== -1) {perror("bind");exit(1);} elseprintf("binded\n");if (listen(sockfd, lisnum) == -1) {perror("listen");exit(1);} elseprintf("begin listen\n");while (1) {SSL *ssl;len = sizeof(struct sockaddr);/* 等待客戶端連上來 */if ((new_fd =accept(sockfd, (struct sockaddr *) &their_addr,&len)) == -1) {perror("accept");exit(errno);} elseprintf("server: 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) == -1) {perror("accept");close(new_fd);break;}/* 開始處理每個新連接上的數據收發 */bzero(buf, MAXBUF + 1);strcpy(buf, "hello");/* 發消息給客戶端 */len = SSL_write(ssl, buf, strlen(buf));if (len <= 0) {printf("消息'%s'發送失敗!錯誤代碼是%d,錯誤信息是'%s'\n",buf, errno, strerror(errno));goto finish;} elseprintf("消息'%s'發送成功,共發送了%d個字節!\n",buf, len);bzero(buf, MAXBUF + 1);/* 接收客戶端的消息 */len = SSL_read(ssl, buf, MAXBUF);if (len > 0)printf("接收消息成功:'%s',共%d個字節的數據\n",buf, len);elseprintf("消息接收失敗!錯誤代碼是%d,錯誤信息是'%s'\n",errno, strerror(errno));/* 處理每個新連接上的數據收發結束 */finish:/* 關閉 SSL 連接 */SSL_shutdown(ssl);/* 釋放 SSL */SSL_free(ssl);/* 關閉 socket */close(new_fd);}/* 關閉監聽的 socket */close(sockfd);/* 釋放 CTX */SSL_CTX_free(ctx);return 0;
}
talent.c
#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>#include <openssl/evp.h>#define MAXBUF 1024int main(int argc, char **argv)
{int sockfd, new_fd;socklen_t len;struct sockaddr_in my_addr, their_addr;unsigned int myport, lisnum;char buf[MAXBUF + 1];SSL_CTX *ctx;if (argv[1])myport = atoi(argv[1]);elsemyport = 5227;if (argv[2])lisnum = atoi(argv[2]);elselisnum = 2;/* 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 (ctx == NULL) {ERR_print_errors_fp(stdout);exit(1);}/* 載入用戶的數字證書, 此證書用來發送給客戶端。 證書里包含有公鑰 */if (SSL_CTX_use_certificate_file(ctx, argv[3], SSL_FILETYPE_PEM) <= 0) {ERR_print_errors_fp(stdout);exit(1);}/* 載入用戶私鑰 */if (SSL_CTX_use_PrivateKey_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0){ERR_print_errors_fp(stdout);exit(1);}/* 檢查用戶私鑰是否正確 */if (!SSL_CTX_check_private_key(ctx)) {ERR_print_errors_fp(stdout);exit(1);}/* 開啟一個 socket 監聽 */if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {perror("socket");exit(1);} elseprintf("socket created\n");bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = PF_INET;my_addr.sin_port = htons(myport);my_addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))== -1) {perror("bind");exit(1);} elseprintf("binded\n");if (listen(sockfd, lisnum) == -1) {perror("listen");exit(1);} elseprintf("begin listen\n");while (1) {SSL *ssl;len = sizeof(struct sockaddr);/* 等待客戶端連上來 */if ((new_fd =accept(sockfd, (struct sockaddr *) &their_addr,&len)) == -1) {perror("accept");exit(errno);} elseprintf("server: 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) == -1) {perror("accept");close(new_fd);break;}/* 開始處理每個新連接上的數據收發 */bzero(buf, MAXBUF + 1);strcpy(buf, "hello");/* 發消息給客戶端 */len = SSL_write(ssl, buf, strlen(buf));if (len <= 0) {printf("消息'%s'發送失敗!錯誤代碼是%d,錯誤信息是'%s'\n",buf, errno, strerror(errno));goto finish;} elseprintf("消息'%s'發送成功,共發送了%d個字節!\n",buf, len);bzero(buf, MAXBUF + 1);/* 接收客戶端的消息 */len = SSL_read(ssl, buf, MAXBUF);if (len > 0)printf("接收消息成功:'%s',共%d個字節的數據\n",buf, len);elseprintf("消息接收失敗!錯誤代碼是%d,錯誤信息是'%s'\n",errno, strerror(errno));/* 處理每個新連接上的數據收發結束 */finish:/* 關閉 SSL 連接 */SSL_shutdown(ssl);/* 釋放 SSL */SSL_free(ssl);/* 關閉 socket */close(new_fd);}/* 關閉監聽的 socket */close(sockfd);/* 釋放 CTX */SSL_CTX_free(ctx);return 0;
}
- 編譯運行。
- 使用
gcc -o server server.c -I /usr/local/ssl/include -L/usr/local/ssl/lib -lssl -lcrypto -ldl -lpthread
編譯server.c
。 - 使用
gcc -o telent telent.c -I /usr/local/ssl/include -L/usr/local/ssl/lib -lssl -lcrypto -ldl -lpthread
- 使用
openssl genrsa -out privkey.pem 1024 openssl req -new -x509 -key privkey.pem -out CAcert.pem -days 1095
來生產私鑰和證書。 - 使用
./server 5203 1 CAcert.pem privkey.pem ./telent 127.0.0.1 5203
來運行。
運行后如圖所示。
實驗中出現的問題及解答。
Q1:安裝openssl
時出現如圖所示問題。
A1:將“openssl-master”
文件夾下的“libcrypto.a”
和“libssl.a”
放在/usr/local/ssl/lib
目錄下(注意使用sudo權限),編譯時鏈接這個目錄即可。
Q2:編譯任務2時出現如圖所示問題。
A2:要使用命令gcc -o server server.c -I /usr/local/ssl/include -L/usr/local/ssl/lib -lssl -lcrypto -ldl -lpthread
來進行相應的庫鏈接。
實驗感想
本次實驗我們主要學習了Openssl的主要內容結構和用法,該實驗還與計算機網絡和密碼學的相關知識結合起來,更符合我們信息安全專業的特點,也學到了很多知識。
本學期該課的實驗就要結束了,但是學習知識的腳步要永遠進行著,生命不息,學習不止,只有不斷學習新知識才會一直進步。