1.HTTPS的過程
1.客戶端向服務端發送請求,客戶端主要向服務器提供以下信息:
- 支持的協議版本,比如TLS 1.0版。
- 一個客戶端生成的隨機數,稍后用於生成"對話密鑰"。
- 支持的加密方法,比如RSA公鑰加密。
- 支持的壓縮方法。
2.服務器端收到請求后,向客戶端做出回應,回應的內容包括:
- 確認使用的加密通信協議版本,比如TLS 1.0版本。如果瀏覽器與服務器支持的版本不一致,服務器關閉加密通信。
- 一個服務器生成的隨機數,稍后用於生成"對話密鑰"。
- 確認使用的加密方法,比如RSA公鑰加密。
- 服務器證書。
3.客戶端收到服務器回應以后,首先驗證服務器證書。
如果證書不是可信機構頒布、或者證書中的域名與實際域名不一致、或者證書已經過期,就會向訪問者顯示一個警告,如下
如果證書沒有問題,客戶端就會從證書中取出服務器的公鑰。然后,向服務器發送下面三項信息:
- 一個隨機數。該隨機數用服務器公鑰加密,防止被竊聽。
- 編碼改變通知,表示隨后的信息都將用雙方商定的加密方法和密鑰發送。
- 客戶端握手結束通知,表示客戶端的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供服務器校驗
證書的驗證過程如下:
CA機構在簽發證書的時候,都會使用自己的私鑰對證書進行簽名,如果我們使用的是購買的證書,那么很有可能,頒發這個證書的CA機構的公鑰已經預置在操作系統中。這樣瀏覽器就可以使用CA機構的公鑰對服務器的證書進行驗簽,驗簽之后得到的是CA機構使用sha256得到的證書摘要,客戶端就會對服務器發送過來的證書使用sha256進行哈希計算得到一份摘要,然后對比之前由CA得出來的摘要,就可以知道這個證書是不是正確的,是否被修改過。
4. 服務端回應
服務器收到客戶端的第三個隨機數之后,計算生成本次會話所用的"會話密鑰"。然后,向客戶端最后發送下面信息:
- 編碼改變通知,表示隨后的信息都將用雙方商定的加密方法和密鑰發送。
- 服務器握手結束通知,表示服務器的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供客戶端校驗。
會話秘鑰是根據前面幾次對話過程中產生的三個隨機數以及一些其他算法產生的,后面服務器與客戶端的交互都是通過這對話秘鑰進行加密解密處理的,其他的都和HTTP協議一樣。
2.HTTPS中自簽名證書的生成
需要注意的是接下來幾種文件的類型:
- key是服務器上的私鑰文件,用於對發送給客戶端數據的加密,以及對從客戶端接收到數據的解密
- csr是證書簽名請求文件(公鑰),用於提交給證書頒發機構(CA)對證書簽名
- crt是由證書頒發機構(CA)簽名后的證書,或者是開發者自簽名的證書,包含證書持有人的信息,持有人的公鑰,以及簽署者的簽名等信息
- keystore 包含證書的文件,可以自己去導入證書
- PEM 文件格式存儲證書和密鑰,用於導出,導入證書時候的證書的格式,有證書開頭,結尾的格式。
還有就是X.509是一個標准,規范了公開秘鑰認證、證書吊銷列表、授權憑證、憑證路徑驗證算法等。
a. 服務器端用戶證書的生成過程:
1. 生成私鑰(.key)文件
# private key $openssl genrsa -des3 -out server.key 1024
2. 生成證書請求(.csr)文件(公鑰)
# generate csr $openssl req -new -key server.key -out server.csr
Country Name (2 letter code) [XX]:CN----------------------------------- 證書持有者所在國家
State or Province Name (full name) []:BJ------------------------------- 證書持有者所在州或省份(可省略不填)
Locality Name (eg, city) []:BJ----------------------------------------- 證書持有者所在城市(可省略不填)
Organization Name (eg, company) []:NH---------------------------------- 證書持有者所屬組織或公司
Organizational Unit Name (eg, section) []:.---------------------------- 證書持有者所屬部門(可省略不填)
Common Name (eg, your name or your server's hostname) []:www.hellobcdb.com----- 必須要填寫域名或者ip地址
Email Address []:------------------------------------------------------ 郵箱(可省略不填)
challenge password:............................................................自定義密碼
An optional company name:.............................................(可選)公司名稱
3. 自簽名的證書文件
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
4.本案例需要用到pem格式的證書,可以用以下方式合並證書文件(crt)和私鑰文件(key)來生成
cat server.crt server.key > server.pem
b. 客戶端證書文件的生成
這里采用的是自簽名的方式,所以客戶端需要有一個導入服務端證書的文件,以供客戶端去驗證服務段的證書過程。
1.生成.keystore文件
keytool -genkey -validity 36000 -alias www.hellobcdb.com -keyalg RSA -keystore client.keystore
2.導入服務端證書
keytool -import -alias serverkey -file server.crt -keystore client.keystore
3.HTTPS案例
環境及工具:ubuntu16.04,QtCreator,mongoose(
#include "mongoose.h" static const char *s_http_port = "8443"; static const char *s_ssl_cert = "/home/gqx/workplace/TestHttps/server.pem"; static const char *s_ssl_key = "/home/gqx/workplace/TestHttps/server.key"; static struct mg_serve_http_opts s_http_server_opts; static void ev_handler(struct mg_connection *nc, int ev, void *p) { if (ev == MG_EV_HTTP_REQUEST) { mg_serve_http(nc, (struct http_message *) p, s_http_server_opts); } } int main(void) { struct mg_mgr mgr; struct mg_connection *nc; struct mg_bind_opts bind_opts; const char *err; mg_mgr_init(&mgr, NULL); memset(&bind_opts, 0, sizeof(bind_opts)); bind_opts.ssl_cert = s_ssl_cert; bind_opts.ssl_key = s_ssl_key; bind_opts.error_string = &err; printf("Starting SSL= server on port %s, cert from %s, key from %s\n", s_http_port, bind_opts.ssl_cert, bind_opts.ssl_key); nc = mg_bind_opt(&mgr, s_http_port, ev_handler, bind_opts); if (nc == NULL) { printf("Failed to create listener: %s\n", err); return 1; } // Set up HTTP server parameters mg_set_protocol_http_websocket(nc); s_http_server_opts.document_root = "."; // Serve current directory s_http_server_opts.enable_directory_listing = "yes"; for (;;) { mg_mgr_poll(&mgr, 1000); } mg_mgr_free(&mgr); return 0; }
QT中C++項目的pro文件內容如下,注意要添加相應的庫:
TEMPLATE = app CONFIG += console c++11 CONFIG -= app_bundle CONFIG -= qt LIBS += -lpthread LIBS += -lssl -lcrypto LIBS += -L /usr/local/bin -lcryptopp -ldl -lz SOURCES += main.cpp \ mongoose.cpp HEADERS += \ mongoose.h
java客戶端代碼(也可以直接在瀏覽器端直接訪問,不過會出現證書不安全的警告提示)
HttpsClient類
package com.gqx.test; import java.io.FileInputStream; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP; public class HttpsClient { private static final int SET_CONNECTION_TIMEOUT = 5 * 1000; private static final int SET_SOCKET_TIMEOUT = 20 * 1000; public static HttpClient getNewHttpClient() { try { FileInputStream fis = new FileInputStream("/home/gqx/文檔/oscar/client.keystore"); String storepass = "starchain"; KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(fis, storepass.toCharArray()); SSLSocketFactory sf = new MySSLSocketFactory(trustStore); sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); HttpParams params = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(params, 10000); HttpConnectionParams.setSoTimeout(params, 10000); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", sf, 443)); ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); HttpConnectionParams.setConnectionTimeout(params, SET_CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(params, SET_SOCKET_TIMEOUT); DefaultHttpClient client = new DefaultHttpClient(ccm, params); return client; } catch (Exception e) { return new DefaultHttpClient(); } } private static class MySSLSocketFactory extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[] { tm }, null); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } } }
測試類,post請求
package com.gqx.test; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; public class TestMain { public static void main(String[] args) { String urlStr = "https://www.hellobcdb.com:7999/fff"; Map<String,String> params = new HashMap<>(); params.put("value", "publish.1666,'Database','computer science','Alice'"); params.put("name", "gqx"); params.put("password", "111"); try { //DefaultHttpClient client = new DefaultHttpClient(); HttpClient client = HttpsClient.getNewHttpClient(); HttpPost httpPost = new HttpPost(urlStr); HttpEntity entity; ArrayList<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>(); if(params != null){ Set<String> keys = params.keySet(); for(Iterator<String> i = keys.iterator(); i.hasNext();) { String key = (String)i.next(); pairs.add(new BasicNameValuePair(key, params.get(key))); } } entity = new UrlEncodedFormEntity(pairs, "utf-8"); httpPost.setEntity(entity); //Log.i(TAG, "post總字節數:"+entity.getContentLength()); HttpResponse response = client.execute(httpPost); try { // 獲取響應實體 HttpEntity entitys = response.getEntity(); System.out.println("--------------------------------------"); // 打印響應狀態 System.out.println(response.getStatusLine()); if (entitys != null) { // 打印響應內容長度 String result = new BufferedReader(new InputStreamReader(entitys.getContent())) .lines().collect(Collectors.joining(System.lineSeparator())); System.out.println(result); } System.out.println("------------------------------------"); } catch (Exception e) { e.printStackTrace(); } }catch(Exception e){ e.printStackTrace(); } } }