HTTPS研究一
差不多花了一週的時間,研究了下https,研究的不算深入,頂多算是入門,先做個記錄,遺留問題再抽時間研究
SSL
https可以看成是http+ssl或tls,tls它的前身是ssl,這裏簡單介紹下ssl
位置:ssl介於http和tcp之間
協議:握手協議、記錄協議、報警協議
握手協議
協議大致過程如上圖所示
階段一、client通過client hello將client支持的ssl最高version、32字節隨機數、密碼套件(包括祕鑰交換算法、加密算法、散列算法)、壓縮算法發送給server、server通過server hello將ssl version、32字節隨機數、從client中選擇的密碼套件、壓縮算法發送給client,通過階段一,client和server雙方協商確認了ssl版本、祕鑰交換算法、對稱加密算法、散列算法、壓縮算法、祕鑰生成的兩個隨機數(client和server一邊一個)
階段二、server端發送證書,祕鑰交換(基於祕鑰交換算法),客戶端證書請求(可選,如果server段需要驗證client的話,則發送)
階段三、client端發送證書(可選,如果server段需要的話),client端祕鑰交換,server端證書驗證(可選,如果需要驗證,需要第三方認證機構)
階段四、兩端互相發送改變密碼規格值,握手完成
記錄協議
握手協議完成之後,可以開始正式的數據傳輸,通過對稱加密算法對傳輸的數據進行加密,通過散列算法來保證數據的完整性。
報警協議
客戶機和服務器發現錯誤時,向對方發送一個警報消息。如果是致命錯誤,則算法立即關閉SSL連接
https概述
https可以看做是http+ssl(tls),http本身明文傳輸,而https卻通過ssl解決了3個問題
- client對server的信任問題,通過ca證書
- 數據的加密問題,通過非對稱加密交換對稱祕鑰,再通過對稱加密對數據進行加密
- 數據的完整性問題,通過散列算法保證數據的完整性
https性能調優
- 服務器端配置HSTS,減少302跳轉,其實HSTS的最大作用是防止302 HTTP劫持。HSTS的缺點是瀏覽器支持率不高,另外配置HSTS後HTTPS很難實時降級成HTTP。
- 設置ssl session 的共享內存cache. 以nginx爲例,它目前只支持session cache的單機多進程共享。如果是前端接入是多服務器架構,這樣的session cache是沒有作用的,所以需要實現session cache的多機共享機制。可以訪問外部session cache server,比如通過redis來實現。nginx配置如下
ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m;
- 配置相同的session ticket key,部署在多個服務器上,這樣多個不同的服務器也能產生相同的 session ticket。session ticket的缺點是支持率不廣,大概只有40%。而session id是client hello的標準內容,從SSL2.0開始就被全部客戶支持。nginx配置如下:ticket.key需要通過 openssl rand 48 > ticket.key 來生成
ssl_session_tickets on; ssl_session_ticket_key /usr/local/cdn_cache/nginx/conf/ticket.key;
- 打開keepalive,keepalive可以保證多個http請求在同一個tcp連接上進行,而不是每個http請求建立一個tcp連接,每建立一個tcp連接都需要一次祕鑰交換的過程,非對稱加解密是https性能最大的消耗者。nginx配置如下,keepalive是指tcp連接保持的時間,注意這個參數nginx會在每次接收http request之後,重新設置,也就是說nginx會在最後接收http request之後的65s纔去釋放tcp連接。第二個參數表示每個tcp連接所能接收的最大http request的個數。
keepalive_timeout 65s; keepalive_requests 1000;
- 設置ocsp stapling file,這樣ocsp的請求就不會發往ca提供的ocsp站點,而是發往網站的webserver。
- 優先使用ecdhe密鑰交換算法,因爲它支持PFS(perfect forward secrecy),能夠實現false
start。libcurl 7.42.0版本開始支持
- 啓用tcp fast open。Linux內核版本3.7以上
- 啓用spdy或http2,libcurl 7.47.0版本開始支持http2
https nginx server的配置過程
1、編譯nginx,./configure時加上--with-http_ssl_module,即./configure --with-http_ssl_module......
2、安裝openssl
#yum install openssl #yum install openssl-devel
3、生成server端ca證書
#cd /usr/local/cdn_cache/nginx/conf #openssl genrsa -des3 -out server.key 1024 #openssl req -new -key server.key -out server.csr #openssl rsa -in server.key -out server_nopwd.key #openssl x509 -req -days 365 -in server.csr -signkey server_nopwd.key -out server.crt
4、nginx配置文件
init_by_lua 'cjson = require "cjson"';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets on;
ssl_session_ticket_key /usr/local/cdn_cache/nginx/conf/ticket.key;
server {
listen 443;
ssl on;
ssl_certificate /usr/local/cdn_cache/nginx/conf/server.crt;
ssl_certificate_key /usr/local/cdn_cache/nginx/conf/server_nopwd.key;
location /shen_post {
content_by_lua '
ngx.req.read_body()
local data = cjson.decode(ngx.req.get_body_data())
local chnlid = data.chnlid
local bps = data.bps
local name = data.name
ret = {}
ret["cid"] = chnlid
ret["bps"] = bps
ret["name"] = name
ngx.say(cjson.encode(ret))
';
}
location /shen_get {
content_by_lua '
ret = {}
ret["cid"] = "10000"
ret["bps"] = 500
ret["name"] = "xxxx"
ngx.say(cjson.encode(ret))
';
}
}
https client
通過libevent+libcurl謝了一個https client,代碼如下:
#include <unistd.h>
#include <event.h>
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#define inter_t 10*1000
#ifndef false
#define false 0
#endif
#ifndef true
#define true 1
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef NULL
#define NULL 0
#endif
typedef unsigned char bool;
struct timeval tv_get;
struct event ev_get;
struct timeval tv_post;
struct event ev_post;
struct event_base* base = NULL;
typedef struct private_ {
CURL * curl;
CURLM * multi_handle;
bool data_valid;
int running_count;
}private_t;
static size_t
handle_recv_header(void *ptr, size_t size, size_t nmemb, void *private) {
size_t total = size * nmemb;
printf("handle_recv_header---%s\n", ptr);
return total;
}
static size_t
handle_recv_data(void *ptr, size_t size, size_t nmemb, void *private) {
private_t * pri = (private_t *)private;
size_t total = size * nmemb;
pri->data_valid = true;
printf("handle_recv_data---%s\n", ptr);
return total;
}
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp) {
printf("%s\n", buffer);
return size * nmemb;
}
void get_timeout_cb(int fd, short event, void *arg) {
private_t * pri = (private_t *)arg;
while(1) {
pri->data_valid = false;
CURLMcode curl_rc = curl_multi_perform(pri->multi_handle, &(pri->running_count));
if((curl_rc != CURLM_CALL_MULTI_PERFORM && curl_rc != CURLM_OK)) {
printf("curl_multi_perform err happened, jump out loop, curl_rc=%d\n", curl_rc);
break;
}
if (false == pri->data_valid) {
break;
}
}
if (pri->running_count) {
printf("get_timeout_cb, running_count[%d]\n", pri->running_count);
evtimer_add(&ev_get, &tv_get);
return;
}
sleep(15);
curl_multi_remove_handle(pri->multi_handle, pri->curl);
curl_multi_add_handle(pri->multi_handle, pri->curl);
evtimer_add(&ev_get, &tv_get);
return;
}
void post_timeout_cb(int fd, short event, void *arg) {
private_t * pri = (private_t *)arg;
CURLMcode curl_rc = curl_multi_perform(pri->multi_handle, &(pri->running_count));
if((curl_rc != CURLM_CALL_MULTI_PERFORM && curl_rc != CURLM_OK)) {
printf("curl_multi_perform err happened, jump out loop, curl_rc=%d\n", curl_rc);
return;
}
if (pri->running_count) {
printf("post_timeout_cb, running_count[%d]\n", pri->running_count);
evtimer_add(&ev_post, &tv_post);
return;
}
curl_multi_remove_handle(pri->multi_handle, pri->curl);
curl_multi_add_handle(pri->multi_handle, pri->curl);
evtimer_add(&ev_post, &tv_post);
return;
}
int curl_get(char * url) {
private_t * pri = calloc(1, sizeof(private_t));
CURL * curl = curl_easy_init();
if (NULL == curl) {
printf("failed in curl_easy_init.\n");
return -1;
}
CURLM * multi_handle = curl_multi_init();
if (NULL == multi_handle) {
curl_easy_cleanup(curl);
printf("failed in curl_multi_init\n");
return -1;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_recv_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)pri);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, handle_recv_header);
curl_easy_setopt(curl, CURLOPT_WRITEHEADER, (void *)pri);
//curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, on_set_sock_fd);
//curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA, (void *)pri);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 10);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 10);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
CURLcode xx = curl_easy_setopt(curl, CURLOPT_SSL_SESSIONID_CACHE, 1L);
if (CURLE_OK != xx) {
printf("xxxxxx\n");
}
if (curl_multi_add_handle(multi_handle, curl) != CURLM_OK) {
printf("failed in curl_multi_add_handle. ");
curl_easy_cleanup(curl);
curl_multi_cleanup(multi_handle);
return -1;
}
pri->curl = curl;
pri->multi_handle = multi_handle;
pri->running_count = 1;
tv_get.tv_sec = 0;
tv_get.tv_usec = inter_t;
//event_set(&ev_appget, -1, EV_PERSIST|EV_TIMEOUT, timeout_cb, pri);
evtimer_set(&ev_get, get_timeout_cb, pri);
event_base_set(base, &ev_get);
evtimer_add(&ev_get, &tv_get);
}
int curl_post(char * url) {
private_t * pri = calloc(1, sizeof(private_t));
char * post_content = "{\
\"chnlid\":\"shen\",\
\"bps\":555,\
\"name\":\"shen\"\
}";
CURL * curl = curl_easy_init();
CURLM * multi_handle = curl_multi_init();
//char *list = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256";
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_content);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
//curl_easy_setopt(curl, CURLOPT_SSL_CIPHER_LIST, list);
curl_multi_add_handle(multi_handle, curl);
pri->curl = curl;
pri->multi_handle = multi_handle;
pri->running_count = 1;
tv_post.tv_sec = 0;
tv_post.tv_usec = inter_t;
evtimer_set(&ev_post, post_timeout_cb, pri);
event_base_set(base, &ev_post);
evtimer_add(&ev_post, &tv_post);
}
int main(int argc, char *argv[]) {
base = event_base_new();
if (0 == strcmp("get", argv[1])) {
curl_get(argv[2]);
}
else if (0 == strcmp("post", argv[1])) {
curl_post(argv[2]);
}
event_base_dispatch(base);
return 0;
}
編譯:gcc -levent -lcurl -g -o lt lbe_curl.c
執行:./lt get "https://10.10.73.18/shen_get"或./lt post "https://10.10.73.18/shen_post"
遺留問題
- ssl和tls協議的差異點,主要是tls具體有哪些改進點
- ssl非對稱加密算法的種類和算法實現細節,不同算法對性能的影響
- ssl對稱加密種類和實現細節
- session cache、session ticket對性能的優化程度
- http2協議細節
參考資料
ssl:
http://www.cnblogs.com/zhuqil/archive/2012/10/06/ssl_detail.html
http://vincent.bernat.im/en/blog/2011-ssl-perfect-forward-secrecy.html
https://www.openssl.org/docs/manmaster/apps/ciphers.html
https性能調優:
http://blog.httpwatch.com/2009/01/15/https-performance-tuning/(強烈推薦)
http://www.mahaixiang.cn/seoyjy/1422.html
http://blog.jobbole.com/78042/
http://www.admin10000.com/document/6236.html
http2:
http://chimera.labs.oreilly.com/books/1230000000545/ch12.html(強烈推薦)
http://io.upyun.com/2015/05/13/http2/?utm_source=tuicool&utm_medium=referral
nginx配置:
http://nginx.org/en/docs/http/configuring_https_servers.html(強烈推薦)
http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_tickets(強烈推薦)