一篇文章讓您了解HTTPS

2023.07.31

一篇文章讓您了解HTTPS


既然有單向驗證,那麼也有雙向驗證,即不僅僅客戶端需要校驗服務端,服務端也需要驗證客戶端。這種情況下,客戶端需要將自己的證書發給服務端做驗證,這種情況只需要將證書的KeyManager作為在init()的第一個參數來SSLContext就行了。

前段時間在做HTTPS相關的需求碰到了一些問題,今天有空整理一下HTTPS的相關知識,希望對您能有所幫助。

圖片圖片

什麼是HTTPS

HTTPS,即HTTP Security,是建立在SSL / TSL協議之上,其中,TSL是SSL協議的升級版,TLS 1.0通常被標示為SSL 3.1,TLS 1.1為SSL 3.2,TLS 1.2為SSL 3.3,可以理解為同一套協議。他的作用主要以下三點:

防止竊聽。所有信息都是加密傳播,第三方無法竊聽。

防止篡改。具有校驗機制,一旦被篡改,通信雙方會立刻發現。

防止冒充。配備身份證書,防止身份被冒充。本文將從android使用者的角度,盡量解釋清楚什麼是HTTPS。

TLS驗證流程

TLS驗證流程

交互流程如下:

  1. 客戶端發出請求給服務端,請求的內容包括:
  1. 支持的協議版本,比如TLS 1.0版。
  2. 一個客戶端生成的隨機數,稍後用於生成"對話密鑰"。
  3. 支持的加密方法,比如RSA公鑰加密。
  4. 支持的壓縮方法。
  1. 服務器回應客戶端,包含以下內容:
  2. 確認使用的加密通信協議版本,比如TLS 1.0版本。如果瀏覽器與服務器支持的版本不一致,服務器關閉加密通信。
  3. 一個服務器生成的隨機數,稍後用於生成"對話密鑰"。
  4. 確認使用的加密方法,比如RSA公鑰加密。
  5. 服務器證書。
  6. 客戶端收到服務器回應以後,首先驗證服務器證書。如果證書不是可信機構頒布、或者證書中的域名與實際域名不一致、或者證書已經過期,就會向訪問者顯示一個警告,由其選擇是否還要繼續通信。
    如果證書沒有問題,客戶端就會從證書中取出服務器的公鑰。然後,向服務器發送下面三項信息。
  7. 一個隨機數。該隨機數用服務器公鑰加密,防止被竊聽。
  8. 編碼改變通知,表示隨後的信息都將用雙方商定的加密方法和密鑰發送。
  9. 客戶端握手結束通知,表示客戶端的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供服務器校驗。
  10. 服務器收到客戶端的第三個隨機數pre-master key之後,計算生成本次會話所用的"會話密鑰"。然後,向客戶端最後發送下面信息。
  11. 編碼改變通知,表示隨後的信息都將用雙方商定的加密方法和密鑰發送。
  12. 服務器握手結束通知,表示服務器的握手階段已經結束。這一項同時也是前面發送的所有內容的hash值,用來供客戶端校驗。

至於為什麼一定要用三個隨機數,來生成"會話密鑰",dog250解釋得很好:

"不管是客戶端還是服務器,都需要隨機數,這樣生成的密鑰才不會每次都一樣。由於SSL協議中證書是靜態的,因此十分有必要引入一種隨機因素來保證協商出來的密鑰的隨機性。

對於RSA密鑰交換算法來說,pre-master-key本身就是一個隨機數,再加上hello消息中的隨機,三個隨機數通過一個密鑰導出器最終導出一個對稱密鑰。

pre master的存在在於SSL協議不信任每個主機都能產生完全隨機的隨機數,如果隨機數不隨機,那麼pre master secret就有可能被猜出來,那麼僅適用pre master secret作為密鑰就不合適了,因此必須引入新的隨機因素,那麼客戶端和服務器加上pre master secret三個隨機數一同生成的密鑰就不容易被猜出了,一個偽隨機可能完全不隨機,可是是三個偽隨機就十分接近隨機了,每增加一個自由度,隨機性增加的可不是一。"

HTTPS相關名詞

HTTPS涉及到的概念比較多,什麼X509,.pem,.crt等等,理解了HTTPS的交互流程之後,先來理一理這些概念。

  • X509 - 這是一種證書標準,主要定義了證書中應該包含哪些內容.其詳情可以參考RFC5280,SSL使用的就是這種證書標準。
  • PEM - Privacy Enhanced Mail,打開看文本格式,以"-----BEGIN..."開頭, "-----END..."結尾,內容是BASE64編碼.查看PEM格式證書的信息:
openssl x509 -in certificate.pem -text -noout
  • 1.

Apache和*NIX服務器偏向於使用這種編碼格式.

  • DER - Distinguished Encoding Rules,打開看是二進制格式,不可讀.查看DER格式證書的信息:
openssl x509 -in certificate.der -inform der -text -noout
  • 1.

Java和Windows服務器偏向於使用這種編碼格式.

上面是證書標準和兩種常用的編碼格式。下面我們來認識一下都有哪些文件擴展名。

  • CRT - CRT應該是certificate的三個字母,其實還是證書的意思,常見於*NIX系統,有可能是PEM編碼,也有可能是DER編碼,大多數應該是PEM編碼,相信你已經知道怎麼辨別.
  • CER - 還是certificate,還是證書,常見於Windows系統,同樣的,可能是PEM編碼,也可能是DER編碼,大多數應該是DER編碼.
  • KEY - 通常用來存放一個公鑰或者私鑰,並非X.509證書,編碼同樣的,可能是PEM,也可能是DER.查看KEY的辦法:
openssl rsa -in mykey.key -text -noout
  • 1.

如果是DER格式的話,同理應該這樣了:openssl rsa -in mykey.key -text -noout -inform der

  • CSR - Certificate Signing Request,即證書籤名請求,這個並不是證書,而是向權威證書頒發機構獲得簽名證書的申請,其核心內容是一個公鑰(當然還附帶了一些別的信息),在生成這個申請的時候,同時也會生成一個私鑰,私鑰要自己保管好.做過iOS APP的朋友都應該知道是怎麼向蘋果申請開發者證書的吧.查看的辦法:(如果是DER格式的話照舊加上-inform der,這裡不寫了)
openssl req -noout -text -in my.csr
  • 1.
  • PFX/P12 - predecessor of PKCS#12,對*nix服務器來說,一般CRT和KEY是分開存放在不同文件中的,但Windows的IIS則將它們存在一個PFX文件中,(因此這個文件包含了證書及私鑰)這樣會不會不安全?應該不會,PFX通常會有一個"提取密碼",你想把裡面的東西讀取出來的話,它就要求你提供提取密碼,PFX使用的時DER編碼,如何把PFX轉換為PEM編碼?
openssl pkcs12 -in for-iis.pfx -out for-iis.pem -nodes
  • 1.

這個時候會提示你輸入提取代碼. for-iis.pem就是可讀的文本.生成pfx的命令類似這樣:openssl pkcs12 -export -in certificate.crt -inkey privateKey.key -out certificate.pfx -certfile CACert.crt

其中CACert.crt是CA(權威證書頒發機構)的根證書,有的話也通過-certfile參數一起帶進去.這麼看來,PFX其實是個證書密鑰庫.

  • JKS - 即Java Key Storage,這是Java的專利,跟OpenSSL關係不大,利用Java的一個叫"keytool"的工具,可以將PFX轉為JKS,當然了,keytool也能直接生成JKS,不過在此就不多表了.
  • BKS - 來自BouncyCastleProvider,它使用的也是TripleDES來保護密鑰庫中的Key,它能夠防止證書庫被不小心修改(Keystore的keyentry改掉1個bit都會產生錯誤),BKS能夠跟JKS互操作。通過以下命令可以將crt轉換成bks,也就是說,其實bks就是上文說的證書文件,jks也是。
keytool -importcert -trustcacerts -keystore e:\key.bks -file e:\server.crt -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
  • 1.

bks的生成參考:SSL通信中使用的bks格式證書的生成

Java上的HTTPS

java上使用HTTPS很簡單,一個典型的HTTPS方式如下:

URL url = new URL("https://google.com");  
HttpsURLConnection urlConnection = url.openConnection();  
InputStream in = urlConnection.getInputStream();
  • 1.
  • 2.
  • 3.

此時使用的是默認的SSLSocketFactory,與下段代碼使用的SSLContext是一致的

private synchronized SSLSocketFactory getDefaultSSLSocketFactory() {  
  try {
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, null, null);
    return defaultSslSocketFactory = sslContext.getSocketFactory();
  } catch (GeneralSecurityException e) {
    throw new AssertionError(); // The system has no TLS. Just give up.
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

這段代碼裡面最重要的方法是sslContext.init(null, null, null);,這裡有三個參數,分別是:

  • KeyManager[],自己的證書,用來校驗服務端是否可信,如果是單向校驗則傳空,表示不需要校驗服務端。
  • TrustManager[],認證服務端證書是否可信,如果傳空則使用android自己的證書庫,如果服務端證書的辦法機構在默認庫裡面,則校驗通過。

有時候CA也是不可信的,為了獲得更高的安全新,需要我們自己指定新人的錨點,可以採用如下的代碼:

// 取到证书的输入流
InputStream is = new FileInputStream("anchor.crt");  
CertificateFactory cf = CertificateFactory.getInstance("X.509");  
Certificate ca = cf.generateCertificate(is);


// 创建 Keystore 包含我们的证书
String keyStoreType = KeyStore.getDefaultType();  
KeyStore keyStore = KeyStore.getInstance(keyStoreType);  
keyStore.load(null);  
keyStore.setCertificateEntry("anchor", ca);


// 创建一个 TrustManager 仅把 Keystore 中的证书 作为信任的锚点
String algorithm = TrustManagerFactory.getDefaultAlgorithm();  
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);  
trustManagerFactory.init(keyStore);  
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();


// 用 TrustManager 初始化一个 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");  
sslContext.init(null, trustManagers, null);  
return sslContext.getSocketFactory();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

這種情況下,只有anchor.crt 以及他簽發的證書才會被信任。

  • SecureRandom,也就是生成隨機數的策略,一般都是直接new SecureRandom()作為參數傳進去。

注意這裡使用的是單向認證,也就是只需要客戶端驗證服務端的證書,客戶端本地部緩存證書,所以這裡sslContext.init()方法的三個參數都是空的。

默認的SSLSocketFactory 校驗服務器的證書時(也就是TrustManager[]傳空的時候),會信任設備內置的100多個根證書。

既然有單向驗證,那麼也有雙向驗證,即不僅僅客戶端需要校驗服務端,服務端也需要驗證客戶端。這種情況下,客戶端需要將自己的證書發給服務端做驗證,這種情況只需要將證書的KeyManager作為在init()的第一個參數來SSLContext就行了。