1. 程式人生 > >SSL socket 通訊詳解

SSL socket 通訊詳解

作者:小馬

最近剛弄完一個ssl socket通訊,整理個筆記。

SSL原理

比如 A要和B互相通訊,為了安全他們希望雙方傳送的資料都是經過加密的。這就要求雙方有一個共同的加解密金鑰(一般加密都是基於對稱加密演算法)。如何才能讓雙方都擁有同一個金鑰呢?有人說由一方生成發給另一方不就行了。這樣就帶來另一個問題,如何保證這個傳送的金鑰是安全的呢?

ssl實現了通過非對稱的RSA加解密原理實現金鑰的交換。(這裡就不細講RSA的原理了,不明白的可以先補充一下這部分知識)。我們假設A是伺服器端,B是客戶端。然後還有一個角色,是一個可信任的第三方CA。A首先生成一個RSA公私鑰對,然後再由這個可信的第三方用自己的私鑰(為了便於描述, 後面叫CA_PRIVATE_KEY)把這個DES金鑰連同RSA公鑰一起簽發一個客戶端證書(後面叫CLIENT_CERT),這個證書中同時也包含一段簽名。

A會把CA證書(證書中包含CA_PRIVATE_KEY 對應的公鑰, 後面叫CA_CERT)連同CLIENT_CERT一起發給客戶端,當然客戶端也可以由其它途徑獲取這兩個證書(比如由專門的安全裝置中匯入到本地)。同時RSA私鑰(後面叫CLIENT_PRIVATE_KEY)也會一起下發到客戶端。

客戶端首先用CA_CERT對CLIENT_CERT做驗籤,以確保資料確實是由A發出來的。如果驗籤能過,其實就已經說明B已經認證了A的身份。前面說到CLIENT_CERT包含伺服器的公鑰,B用這個公鑰對一個對稱的DES金鑰加密,然後可以公開發出去,他不用擔心這個金鑰會被擷取,因為只有A才有CLIENT_PRIVATE_KEY,也只有A才能解密出來這個DES金鑰。這樣就完成了對稱金鑰的交換。

盜個圖,流程基本是下面這樣的,

這裡寫圖片描述

大部分時候到這裡,SSL通訊前的握手已經完成了,可以進行安全的資料通訊了。不過有時候會有雙向認證的需求,也就是A也想認證B。這個時候CLIENT_PRIVATE_KEY就發生作用了,B會用這個私鑰自己生成簽名,然後發給A來認證。

這些基本就是SSL的原理了。

上面講到的這些流程,如果用程式實現還是有點複雜的。幸運的是,開源庫openssl已經幫我們做了大部分的事情(openssl的實現機制更復雜也更靈活,但基本原理跟上面是一致的)。我們只需要呼叫一些基本的介面就可以完成SSL socket通訊。

openssl 與 socket

基於ssl的socket通訊一般分為幾個步驟。

第一步, 初始化

這一步主要是初始化openssl庫,建立會話上下文等,

SSL_METHOD *method = NULL;

SSL_library_init ();

SSL_load_error_strings();

OpenSSL_add_ssl_algorithms();

method = SSLv3_client_method();

g_ctx = SSL_CTX_new(method); /* Create new context */

SSLv3_client_method 是指定ssl要使用的協議。SSL協議由美國 NetScape公司開發的,V1.0版本從沒有公開發表過;V2.0版本於1995年2月釋出。但是,由於V2.0版本有許多安全漏洞,所以,1996年緊接著就釋出了V3.0版本。微軟從IE 7開始就已經把瀏覽器的預設設定不支援SSL 2.0,但可能是考慮到有些網站還只支援SSL 2.0,所以IE瀏覽器留了一個可以由使用者設定支援SSL 2.0的選項,以便能正常訪問只支援SSL 2.0的網站。IE7/IE8支援SSL 3.0和TLS1.0,而IE9還支援TLS1.1和1.2。

在openssl裡指定協議很簡單,每個協議都有對應的函式,一行程式碼就可以搞定。

SSL_METHOD* TLSv1_client_method(void); TLSv1.0 協議

SSL_METHOD* SSLv2_client_method(void); SSLv2 協議

SSL_METHOD* SSLv3_client_method(void); SSLv3 協議

SSL_METHOD* SSLv23_client_method(void); SSLv2/v3 協議

SSL_CTX_new建立ssl上下文,這裡面很多全域性變數要被各個階段共享。

SSL_load_error_strings(void );

如果想打印出一些方便閱讀的除錯資訊的話,便要在一開始呼叫此函式.

第二步,載入證書和私鑰

前面提到過,證書有CA證書,還是客戶端的證書,私鑰是客戶端私鑰。也是幾個介面就搞定的事情,

int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,const char *CApath);

此函式用來便是載入CA證書檔案的.

int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);

載入客戶端自己的證書檔案.

int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);

載入自己的私鑰,以用於簽名.

void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u);

載入私鑰時一般要一個驗證密碼,這個密碼是由生成私鑰的一方來設定的,客戶端得到這個密碼後要通過一個介面傳入驗證:

void SSL_CTX_set_verify(SSL_CTX ctx,int mode,int (*callback)(int, X509_STORE_CTX ));

預設mode是SSL_VERIFY_NONE,如果想要驗證對方的話,便要將此項變成SSL_VERIFY_PEER.SSL/TLS中預設只驗證server,如果沒有設定 SSL_VERIFY_PEER的話,客戶端連證書都不會發過來.

第三步,建立ssl socket連線

首先要建立普通的socket連線,這個就不多說了,連線成功後返回一個socket描述符,假設名稱為socket_fd。然後與ssl進行關聯,三個介面就可以完成:

g_ssl = SSL_new(g_ctx);

SSL_set_fd(g_ssl, socket_fd);

SSL_connect(g_ssl);

這樣後面所以的傳送,接收過程都是基於g_ssl這個ssl的全域性欄位了。

第四步,傳送和接收

收發資料就更簡單了,

傳送,

SSL_write(g_ssl, pData, bytes_left);

接收,

SSL_read(g_ssl, pData, bytes_left);

當然,一般我們在普通的socket收發都會加一些超時處理機制,對於ssl的收發也是一樣的,後面我的示例中會給出來一個較完善的處理過程。

第五步,釋放資源

如果不使用了,就要把所有佔用的資源都釋放掉。

當然首先要把socket關閉,

close(socket_fd);

然後是和ssl相關的資源釋放

SSL_CTX_free(g_ctx);

SSL_shutdown(g_ssl);

SSL_free(g_ssl);

ERR_free_strings();

一個使用示例

初始化,連線等操作都很簡單,這裡只給出一個ssl傳送函式的實現方法,帶超時處理的,供大家參考。

int socket_send_ssl(const char *data,

    unsigned int data_len,int time_out)
{
    int iRet = -1;
    struct timeval tm;
    tm.tv_sec = time_out;
    tm.tv_usec = 0;

    char *pData = data;

    int bytes_left = data_len;
    int written_bytes = 0;
    if((data == NULL) || (data_len <= 0))
    {
        return -2;
    }

    iRet = setsockopt(socket_fd,SOL_SOCKET,SO_SNDTIMEO,&tm,sizeof(tm));//設定傳送超時

    while(bytes_left > 0)
    {
        written_bytes = SSL_write(g_ssl, pData, bytes_left);
        if(written_bytes <= 0)
        {
            if(errno == EINTR)
            {
                written_bytes = 0;   
            }
            else
            {
                return -1;

            }

        }
        bytes_left -= written_bytes;
        pData += written_bytes;

    }
    return 0;
}



如有錯誤,請不吝賜教。