Thanks
HTTPS理論基礎(chǔ)及其在Android中的最佳實踐
聊聊HTTPS和SSL/TLS協(xié)議
理解HTTPS
HTTPS概述
HTTPS是建立在HTTP的基礎(chǔ)上疙筹,Http是一個網(wǎng)絡(luò)協(xié)議讶请,用于傳輸內(nèi)容魁巩,我們知道HTTP的通信過程大致如下:
Http基于4層網(wǎng)絡(luò)模型:
┌────------────┐┌─┬─┬─-┬─┬─-┬─┬─-┬─┬─-┬─┬─-┐
│ ││D│F│W│F│H│G│T│I│S│U│ │
│ ││N│I│H│T│T│O│E│R│M│S│其│
│第四層涧尿,應(yīng)用層 ││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│ │
│第二層,網(wǎng)絡(luò)層 ││ └──---──┘ │
│ ││ ⌒砥稹IP │
└────────-----┘└────────────────────-------------─-┘
┌────────-----┐┌─────────-------┬──────--------─────┐
│第一層十偶,網(wǎng)絡(luò)接口││ARP/RARP │ 其它 │
└────────------┘└─────────------┴─────--------──────┘
TCP/IP四層參考模型
從層級上我們可以發(fā)現(xiàn),應(yīng)用層產(chǎn)生的數(shù)據(jù)园细,直接經(jīng)由傳輸層直接傳輸惦积,存在一個很大的問題,安全性猛频。原本的Http就是傳輸明文的狮崩,數(shù)據(jù)一經(jīng)攔截就很容易被別人盜取信息。那怎么解決呢伦乔?HTTPS的方法是厉亏,對傳輸?shù)臄?shù)據(jù)進(jìn)行加密,在應(yīng)用層和傳輸層的中間加一層烈和,S層: SSL/TLS爱只,Secure Sockets Layer 安全套接層 / Transport Layer Security 傳輸層安全協(xié)議,這一層使用的主要是 SSL/TLS 技術(shù)招刹,這兩個協(xié)議其實就是安全加密算法的不同:
┌────------────┐┌─┬─┬─-┬─┬─-┬─┬─-┬─┬─-┬─┬─-┐
│ ││D│F│W│F│H│G│T│I│S│U│ │
│ ││N│I│H│T│T│O│E│R│M│S│其│
│第四層恬试,應(yīng)用層 ││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│ │
│第二層哑舒,網(wǎng)絡(luò)層 ││ └──---──┘ │
│ ││ IP │
└────────-----┘└────────────────────-------------─-┘
┌────────-----┐┌─────────-------┬──────--------─────┐
│第一層幻馁,網(wǎng)絡(luò)接口││ARP/RARP │ 其它 │
└────────------┘└─────────------┴─────--------──────┘
TCP/IP四層參考模型
對稱加密算法
既然加密洗鸵,那怎么一個規(guī)則呢?加密仗嗦,在我們的理解是這樣的:
用一個密碼/密鑰膘滨,用某種算法對明文進(jìn)行加密,得到密文稀拐,解密也容易:
像上面這樣火邓,用同一個密碼/密鑰進(jìn)行加密和解密的,就是對稱加密算法德撬,因為其密碼/密鑰是一樣的铲咨,所以名曰為對稱。但如果HTTPS中使用這種算法的話蜓洪,就需要通信雙方知道密鑰纤勒,這樣就必須在通信的時候,先把密鑰發(fā)給對方蝠咆,但這樣踊东,黑客若截獲這密碼北滥,等于沒用刚操。
非對稱加密算法
“非對稱加密技術(shù)”,意思就是說:“加密”和“解密”使用不同的密鑰再芋。其基本原理菊霜,就是大數(shù)的因式分解。這里就不展開了济赎,密碼學(xué)的東西鉴逞,很神奇。因為有兩個不同的密鑰司训,我們命名為公鑰和私鑰构捡,公鑰是可以對外公開的,私鑰是自己保管的壳猜,用公鑰或私鑰中的任何一個進(jìn)行加密勾徽,用另一個進(jìn)行解密。 加密和解密就變成這樣:
明文 + 加密算法 + 公鑰 => 密文统扳,
密文 + 解密算法 + 私鑰 => 明文
或者是這樣:
明文 + 加密算法 + 私鑰 => 密文喘帚,
密文 + 解密算法 + 公鑰 => 明文
總結(jié)來說畅姊,兩個密鑰皆可加密,加密后只有由另一個密鑰解密吹由。
HTTPS中加密算法
非對稱加密很棒若未,但是呢,效率不高倾鲫,速度慢粗合,對稱加密雖然安全性不高,但是效率高啊乌昔。怎么選擇呢舌劳?小孩子才做選擇,大人全都要玫荣。HTTPS結(jié)合了兩種加密算法的優(yōu)勢甚淡,加密解密變成這樣:
這個過程涉及到四個密鑰:私鑰,公鑰捅厂,對稱加密的Key(這里稱為KeyX)贯卦,公鑰加密KeyX后的KeyY。首先客戶端會得到服務(wù)器發(fā)來的公鑰焙贷,然后撵割,客戶端會隨機生成一個對稱加密用的Key,這里稱為KeyX辙芍,用KeyX對我們要傳輸?shù)拿魑倪M(jìn)行對稱加密啡彬,因為明文可能很多,所以這里用對稱加密故硅,就效率高很多庶灿,得到對稱加密的密文,然后用公鑰對KeyX進(jìn)行非對此加密得到KeyY吃衅,然后一并傳輸密文和KeyY給服務(wù)器往踢。想要解釋密文,就得先得到KeyX徘层,想要得到KeyX峻呕,就得有密鑰,完美趣效。所以只能擁有密鑰的服務(wù)器能得到明文瘦癌。
具體的HTTPS請求實際可細(xì)分為一下步驟:(摘自大神博客)
- 客戶端向服務(wù)器發(fā)起HTTPS請求,連接到服務(wù)器的443端口跷敬。
- 服務(wù)器端有一個密鑰對讯私,即公鑰和私鑰,是用來進(jìn)行非對稱加密使用的,服務(wù)器端保存著私鑰妄帘,不能將其泄露楞黄,公鑰可以發(fā)送給任何人。
- 服務(wù)器將自己的公鑰發(fā)送給客戶端抡驼。
- 客戶端收到服務(wù)器端的公鑰之后鬼廓,會對公鑰進(jìn)行檢查,驗證其合法性致盟。如果發(fā)現(xiàn)發(fā)現(xiàn)公鑰有問題碎税,那么HTTPS傳輸就無法繼續(xù)。嚴(yán)格的說馏锡,這里應(yīng)該是驗證服務(wù)器發(fā)送的數(shù)字證書的合法性雷蹂。如果公鑰合格,那么客戶端會生成一個隨機值杯道,這個隨機值就是用于進(jìn)行對稱加密的密鑰匪煌,我們將該密鑰稱之為client key,即客戶端密鑰党巾,這樣在概念上和服務(wù)器端的密鑰容易進(jìn)行區(qū)分萎庭。然后用服務(wù)器的公鑰對客戶端密鑰進(jìn)行非對稱加密,這樣客戶端密鑰就變成密文了齿拂,至此驳规,HTTPS中的第一次HTTP請求結(jié)束。
- 客戶端會發(fā)起HTTPS中的第二個HTTP請求署海,將加密之后的客戶端密鑰發(fā)送給服務(wù)器吗购。
- 服務(wù)器接收到客戶端發(fā)來的密文之后,會用自己的私鑰對其進(jìn)行非對稱解密砸狞,解密之后的明文就是客戶端密鑰捻勉,然后用客戶端密鑰對數(shù)據(jù)進(jìn)行對稱加密,這樣數(shù)據(jù)就變成了密文趾代。
- 然后服務(wù)器將加密后的密文發(fā)送給客戶端贯底。
- 客戶端收到服務(wù)器發(fā)送來的密文,用客戶端密鑰對其進(jìn)行對稱解密撒强,得到服務(wù)器發(fā)送的數(shù)據(jù)。這樣HTTPS中的第二個HTTP請求結(jié)束笙什,整個HTTPS傳輸完成飘哨。
數(shù)字證書
我們拿到的公鑰怎么確保真的是服務(wù)器的公鑰呢?黑客有可能中途篡改公鑰琐凭,將其改成黑客自己的芽隆。摘個例子:
假設(shè)一個鎮(zhèn)里面有兩個人A和B,A是個富豪,B想向A借錢胚吁,但是A和B不熟牙躺,怕B借了錢之后不還。這時候B找到了鎮(zhèn)長腕扶,鎮(zhèn)長給B作擔(dān)保孽拷,告訴A說:“B人品不錯,不會欠錢不還的半抱,你就放心借給他吧脓恕。” A聽了這話后窿侈,心里想:“鎮(zhèn)長是全鎮(zhèn)最德高望重的了炼幔,他說B沒問題的話那就沒事了,我就放心了”史简。 于是A相信B的為人乃秀,把錢借給了B。
類似地圆兵,公鑰需要一個擔(dān)保人:證書認(rèn)證中心(Certificate Authority)环形,簡稱CA。CA本身有一對公鑰和私鑰衙傀,CA會用CA自己的私鑰對要進(jìn)行認(rèn)證的公鑰進(jìn)行非對稱加密呢蛤,此處待認(rèn)證的公鑰就相當(dāng)于是明文柑潦,加密完之后,得到的密文再加上證書的過期時間、頒發(fā)給凯肋、頒發(fā)者等信息,就組成了數(shù)字證書粟害。
Android-Retrofit 配置證書 訪問HTTPS
首先甚疟,如果證書是由CA頒發(fā)的或者是CA授權(quán)的機構(gòu)頒發(fā)的,直接可以使用金麸,因為Android有CA的根證書擎析,所以默認(rèn)支持
如果是阿里云申請的證書,可能會有一個 pem 證書挥下,需要先轉(zhuǎn)換格式 cer 參考:https://blog.csdn.net/qq_33315813/article/details/73532846揍魂,
關(guān)于格式的說明,可以參考 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
先把轉(zhuǎn)換后的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);
}
調(diào)用:
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);
}