1. 程式人生 > >2018-2019 1 20165203 實驗五 通用協議設計

2018-2019 1 20165203 實驗五 通用協議設計

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");
    else
        printf("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伺服器”通過混合密碼系統進行防護
提交測試截圖  
  1. 學習wc伺服器模式如下。

  1. 編寫程式碼如下。

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 1024

int 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]);
    else
        myport = 5227;

    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 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);
    } else
        printf("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);
    } else
        printf("binded\n");

    if (listen(sockfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    } else
        printf("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);
        } else
            printf("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;
        } else
            printf("訊息'%s'傳送成功,共傳送了%d個位元組!\n",
                   buf, len);

        bzero(buf, MAXBUF + 1);
        /* 接收客戶端的訊息 */
        len = SSL_read(ssl, buf, MAXBUF);
        if (len > 0)
            printf("接收訊息成功:'%s',共%d個位元組的資料\n",
                   buf, len);
        else
            printf
                ("訊息接收失敗!錯誤程式碼是%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 1024

int 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]);
    else
        myport = 5227;

    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 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);
    } else
        printf("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);
    } else
        printf("binded\n");

    if (listen(sockfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    } else
        printf("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);
        } else
            printf("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;
        } else
            printf("訊息'%s'傳送成功,共傳送了%d個位元組!\n",
                   buf, len);

        bzero(buf, MAXBUF + 1);
        /* 接收客戶端的訊息 */
        len = SSL_read(ssl, buf, MAXBUF);
        if (len > 0)
            printf("接收訊息成功:'%s',共%d個位元組的資料\n",
                   buf, len);
        else
            printf
                ("訊息接收失敗!錯誤程式碼是%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;
}  
  1. 編譯執行。
  • 使用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的主要內容結構和用法,該實驗還與計算機網路和密碼學的相關知識結合起來,更符合我們資訊保安專業的特點,也學到了很多知識。 本學期該課的實驗就要結束了,但是學習知識的腳步要永遠進行著,生命不息,學習不止,只有不斷學習新知識才會一直進步。