Thanks
HTTPS理論基礎及其在Android中的最佳實踐
聊聊HTTPS和SSL/TLS協議
理解HTTPS
HTTPS概述
HTTPS是建立在HTTP的基礎上,Http是一個網絡協議,用于傳輸內容,我們知道HTTP的通信過程大致如下:
Http基于4層網絡模型:
┌────------────┐┌─┬─┬─-┬─┬─-┬─┬─-┬─┬─-┬─┬─-┐
│ ││D│F│W│F│H│G│T│I│S│U│ │
│ ││N│I│H│T│T│O│E│R│M│S│其│
│第四層,應用層 ││S│N│O│P│T│P│L│C│T│E│ │
│ ││ │G│I│ │P│H│N│ │P│N│ │
│ ││ │E│S│ │ │E│E│ │ │E│它│
│ ││ │R│ │ │ │R│T│ │ │T│ │
└───────------─┘└─┴─┴─-┴─┴─-┴─┴─-┴─┴─-┴─┴-─┘
┌───────-----─┐┌─────────-------┬──--------─────────┐
│第三層,傳輸層 ││ TCP │ UDP │
└───────-----─┘└────────-------─┴──────────--------─┘
┌───────-----─┐┌───----──┬───---─┬────────-------──┐
│ ││ │ICMP│ │
│第二層,網絡層 ││ └──---──┘ │
│ ││ IP │
└────────-----┘└────────────────────-------------─-┘
┌────────-----┐┌─────────-------┬──────--------─────┐
│第一層,網絡接口││ARP/RARP │ 其它 │
└────────------┘└─────────------┴─────--------──────┘
TCP/IP四層參考模型
從層級上我們可以發現,應用層產生的數據,直接經由傳輸層直接傳輸,存在一個很大的問題,安全性。原本的Http就是傳輸明文的,數據一經攔截就很容易被別人盜取信息。那怎么解決呢?HTTPS的方法是,對傳輸的數據進行加密,在應用層和傳輸層的中間加一層,S層: SSL/TLS,Secure Sockets Layer 安全套接層 / Transport Layer Security 傳輸層安全協議,這一層使用的主要是 SSL/TLS 技術,這兩個協議其實就是安全加密算法的不同:
┌────------────┐┌─┬─┬─-┬─┬─-┬─┬─-┬─┬─-┬─┬─-┐
│ ││D│F│W│F│H│G│T│I│S│U│ │
│ ││N│I│H│T│T│O│E│R│M│S│其│
│第四層,應用層 ││S│N│O│P│T│P│L│C│T│E│ │
│ ││ │G│I│ │P│H│N│ │P│N│ │
│ ││ │E│S│ │ │E│E│ │ │E│它│
│ ││ │R│ │ │ │R│T│ │ │T│ │
└───────------─┘└─┴─┴─-┴─┴─-┴─┴─-┴─┴─-┴─┴-─┘
┌───────-----─┐
│SSL/TLS │
└───────-----─┘
┌───────-----─┐┌─────────-------┬──--------─────────┐
│第三層,傳輸層 ││ TCP │ UDP │
└───────-----─┘└────────-------─┴──────────--------─┘
┌───────-----─┐┌───----──┬───---─┬────────-------──┐
│ ││ │ICMP│ │
│第二層,網絡層 ││ └──---──┘ │
│ ││ IP │
└────────-----┘└────────────────────-------------─-┘
┌────────-----┐┌─────────-------┬──────--------─────┐
│第一層,網絡接口││ARP/RARP │ 其它 │
└────────------┘└─────────------┴─────--------──────┘
TCP/IP四層參考模型
對稱加密算法
既然加密,那怎么一個規則呢?加密,在我們的理解是這樣的:
用一個密碼/密鑰,用某種算法對明文進行加密,得到密文,解密也容易:
像上面這樣,用同一個密碼/密鑰進行加密和解密的,就是對稱加密算法,因為其密碼/密鑰是一樣的,所以名曰為對稱。但如果HTTPS中使用這種算法的話,就需要通信雙方知道密鑰,這樣就必須在通信的時候,先把密鑰發給對方,但這樣,黑客若截獲這密碼,等于沒用。
非對稱加密算法
“非對稱加密技術”,意思就是說:“加密”和“解密”使用不同的密鑰。其基本原理,就是大數的因式分解。這里就不展開了,密碼學的東西,很神奇。因為有兩個不同的密鑰,我們命名為公鑰和私鑰,公鑰是可以對外公開的,私鑰是自己保管的,用公鑰或私鑰中的任何一個進行加密,用另一個進行解密。 加密和解密就變成這樣:
明文 + 加密算法 + 公鑰 => 密文,
密文 + 解密算法 + 私鑰 => 明文
或者是這樣:
明文 + 加密算法 + 私鑰 => 密文,
密文 + 解密算法 + 公鑰 => 明文
總結來說,兩個密鑰皆可加密,加密后只有由另一個密鑰解密。
HTTPS中加密算法
非對稱加密很棒,但是呢,效率不高,速度慢,對稱加密雖然安全性不高,但是效率高啊。怎么選擇呢?小孩子才做選擇,大人全都要。HTTPS結合了兩種加密算法的優勢,加密解密變成這樣:
這個過程涉及到四個密鑰:私鑰,公鑰,對稱加密的Key(這里稱為KeyX),公鑰加密KeyX后的KeyY。首先客戶端會得到服務器發來的公鑰,然后,客戶端會隨機生成一個對稱加密用的Key,這里稱為KeyX,用KeyX對我們要傳輸的明文進行對稱加密,因為明文可能很多,所以這里用對稱加密,就效率高很多,得到對稱加密的密文,然后用公鑰對KeyX進行非對此加密得到KeyY,然后一并傳輸密文和KeyY給服務器。想要解釋密文,就得先得到KeyX,想要得到KeyX,就得有密鑰,完美。所以只能擁有密鑰的服務器能得到明文。
具體的HTTPS請求實際可細分為一下步驟:(摘自大神博客)
- 客戶端向服務器發起HTTPS請求,連接到服務器的443端口。
- 服務器端有一個密鑰對,即公鑰和私鑰,是用來進行非對稱加密使用的,服務器端保存著私鑰,不能將其泄露,公鑰可以發送給任何人。
- 服務器將自己的公鑰發送給客戶端。
- 客戶端收到服務器端的公鑰之后,會對公鑰進行檢查,驗證其合法性。如果發現發現公鑰有問題,那么HTTPS傳輸就無法繼續。嚴格的說,這里應該是驗證服務器發送的數字證書的合法性。如果公鑰合格,那么客戶端會生成一個隨機值,這個隨機值就是用于進行對稱加密的密鑰,我們將該密鑰稱之為client key,即客戶端密鑰,這樣在概念上和服務器端的密鑰容易進行區分。然后用服務器的公鑰對客戶端密鑰進行非對稱加密,這樣客戶端密鑰就變成密文了,至此,HTTPS中的第一次HTTP請求結束。
- 客戶端會發起HTTPS中的第二個HTTP請求,將加密之后的客戶端密鑰發送給服務器。
- 服務器接收到客戶端發來的密文之后,會用自己的私鑰對其進行非對稱解密,解密之后的明文就是客戶端密鑰,然后用客戶端密鑰對數據進行對稱加密,這樣數據就變成了密文。
- 然后服務器將加密后的密文發送給客戶端。
- 客戶端收到服務器發送來的密文,用客戶端密鑰對其進行對稱解密,得到服務器發送的數據。這樣HTTPS中的第二個HTTP請求結束,整個HTTPS傳輸完成。
數字證書
我們拿到的公鑰怎么確保真的是服務器的公鑰呢?黑客有可能中途篡改公鑰,將其改成黑客自己的。摘個例子:
假設一個鎮里面有兩個人A和B,A是個富豪,B想向A借錢,但是A和B不熟,怕B借了錢之后不還。這時候B找到了鎮長,鎮長給B作擔保,告訴A說:“B人品不錯,不會欠錢不還的,你就放心借給他吧。” A聽了這話后,心里想:“鎮長是全鎮最德高望重的了,他說B沒問題的話那就沒事了,我就放心了”。 于是A相信B的為人,把錢借給了B。
類似地,公鑰需要一個擔保人:證書認證中心(Certificate Authority),簡稱CA。CA本身有一對公鑰和私鑰,CA會用CA自己的私鑰對要進行認證的公鑰進行非對稱加密,此處待認證的公鑰就相當于是明文,加密完之后,得到的密文再加上證書的過期時間、頒發給、頒發者等信息,就組成了數字證書。
Android-Retrofit 配置證書 訪問HTTPS
首先,如果證書是由CA頒發的或者是CA授權的機構頒發的,直接可以使用,因為Android有CA的根證書,所以默認支持
如果是阿里云申請的證書,可能會有一個 pem 證書,需要先轉換格式 cer 參考:https://blog.csdn.net/qq_33315813/article/details/73532846,
關于格式的說明,可以參考 https://blog.csdn.net/qq_30698633/article/details/77895151retrofit的寫法參考于:https://blog.csdn.net/qq_20521573/article/details/79233793
https://blog.csdn.net/lmj623565791/article/details/48129405
先把轉換后的cer文件放到 R.raw.
下
private SSLContext initCertificates(InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try {
if (certificate != null)
certificate.close();
} catch (IOException ignored) { }
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void init(Context context) {
InputStream inputStream = context.getResources().openRawResource(R.raw.https);
sslContext = initCertificates(inputStream);
}
調用:
private HttpsContract getBaseHttpProtocol(SSLContext sslContext, String baseServer) {
OkHttpClient client=new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.hostnameVerifier(new SafeHostnameVerifier())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseServer)
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
return retrofit.create(HttpsContract.class);
}