最近在Https方面做了一些探索,在Android上做了一些實際應(yīng)用,在此分享出來以便后查。
Https協(xié)議是什么
目前,Https已經(jīng)成為了主流趨勢,無論在大型互聯(lián)網(wǎng)公司如BAT,還是小型創(chuàng)業(yè)公司,都逐漸將自己的服務(wù)切換成了Https。Https能提供一種安全可靠的加密通信連接方式,有效防止信息泄密以及劫持等情況發(fā)生。因此,使用Https無論從用戶體驗還是企業(yè)長期利益來看,都是十分有益的。
Http協(xié)議
我們首先說說什么是Http協(xié)議?Http是一個屬于應(yīng)用層的面向?qū)ο蟮膮f(xié)議,由于其簡捷、快速的方式,適用于分布式超媒體信息系統(tǒng)。
Http(超文本傳輸協(xié)議)是一個基于請求與響應(yīng)模式的、無狀態(tài)的、應(yīng)用層的協(xié)議,?;赥CP的連接方式,HTTP1.1版本中給出一種持續(xù)連接的機制,絕大多數(shù)的Web開發(fā),都是構(gòu)建在HTTP協(xié)議之上的Web應(yīng)用。
順便說一句,Http1.1協(xié)議已經(jīng)是目前最主流的協(xié)議,但是新的Http2.0協(xié)議也已經(jīng)提出,在未來10年的時間內(nèi),必將逐漸登上主舞臺。這種在應(yīng)用層的推廣會比IPV6這種網(wǎng)絡(luò)層的推廣快很多。
Http URL(URL是一種特殊類型的URI,包含了用于查找某個資源的足夠的信息)的格式如下:
http://host[":"port][abs_path]
Http的請求由三部分組成,分別是:請求行、消息報頭、請求正文。具體內(nèi)容可以查看WikiPedia,再次不多贅述。
SSL/TLS協(xié)議
那么什么是Https協(xié)議呢?簡單來說,Https = Http + SSL/TLS,即基于SSL的Http協(xié)議,使用不同于Http協(xié)議的默認端口及一個加密、身份驗證層(Http與TCP之間)。
Https實際上應(yīng)用了Netscape的安全全套接字層(SSL,TLS則是SSL的升級版,修復(fù)了SSL的漏洞)作為Http應(yīng)用層的子層。Https使用端口443,而不是像Http那樣使用端口80和TCP/IP進行通信。SSL使用40位關(guān)鍵字作為RC4流加密算法,這對于商業(yè)信息的加密是合適的。Https和SSL支持使用X.509數(shù)字認證,如果需要的話用戶可以確認發(fā)送者是誰。Https協(xié)議使用SSL在發(fā)送方把原始數(shù)據(jù)進行加密,然后在接受方進行解密,加密和解密需要發(fā)送方和接受方通過交換共知的密鑰來實現(xiàn),因此,所傳送的數(shù)據(jù)不容易被網(wǎng)絡(luò)黑客截獲和解密。
客戶端在使用Https方式與Web服務(wù)器通信時有以下幾個步驟:
- 客戶使用Https的URL訪問Web服務(wù)器,要求與Web服務(wù)器建立SSL連接。
- Web服務(wù)器收到客戶端請求后,會將網(wǎng)站的證書信息(證書中包含公鑰)傳送一份給客戶端。
- 客戶端的瀏覽器與Web服務(wù)器開始協(xié)商SSL連接的安全等級,也就是信息加密的等級。
- 客戶端的瀏覽器根據(jù)雙方同意的安全等級,建立會話密鑰,然后利用網(wǎng)站的公鑰將會話密鑰加密,并傳送給網(wǎng)站。
- Web服務(wù)器利用自己的私鑰解密出會話密鑰。
- Web服務(wù)器利用會話密鑰加密與客戶端之間的通信。
基本流程圖如下:

另外,假如為了安全保密,將一個網(wǎng)站所有的Web應(yīng)用都啟用SSL技術(shù)來加密,并使用Https協(xié)議進行傳輸,那么該網(wǎng)站的性能和效率會降低,而且沒有這個必要,因為一般來說并不是所有數(shù)據(jù)都要求那么高的安全保密級別。我們只需對那些涉及機密數(shù)據(jù)的交互處理使用Https協(xié)議,這樣就做到魚與熊掌兼得。
Https在Android的使用——HttpsURLConnection
在Android領(lǐng)域,如何建立一個可靠的Https協(xié)議鏈接呢?Android是基于Java進行編程的,常見的Http協(xié)議建立方式有Apache的HttpClient和Sun公司提供的HttpURLConnection。不過,從API19開始,Google官方已經(jīng)推薦Android開發(fā)者使用HttpURLConnection來進行網(wǎng)絡(luò)連接,當然,為了更方便的使用Http,很多開源庫也是提供了許多方便的功能,比如Volley、OKHttp等。
但是,萬變不離其宗,基本流程都與HttpsURLConnection連接相似。這里我就主要分享一下關(guān)于HttpsURLConnection的使用心得。
首先,明確其繼承關(guān)系。
HttpsURLConnection -> HttpURLConnection -> URLConnection
從這個繼承關(guān)系可以看出Https其實就是Http的延伸,同時也是一種URLConnection的實現(xiàn)。
URLConnection是abstract類型,它是所有端到URL連接的一個父類,其實例可以用來讀寫URL的資源。要建立一個這樣的URL連接需要幾個步驟:
- openConnection:建立對應(yīng)URL的連接
- 設(shè)置參數(shù)和請求屬性
- connection:發(fā)起實質(zhì)的鏈接
- 獲取請求頭信息和請求內(nèi)容
其實,HttpURLConnection建立連接也就是遵循這一流程。下面給出一個基本的Http連接建立的實例:
URL url = new URL("http://www.baidu.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
} finally {
urlConnection.disconnect();
}
總結(jié)一下,基本是這幾個步驟:
- 通過調(diào)用URL.openConnection()獲取一個HttpURLConnection實例
- 設(shè)置相應(yīng)請求頭信息
- 設(shè)置上傳的請求體(這是可選項)setDoOutput(true)
- 讀取返回信息,消息頭包括消息體類型、長度、Cookie等,用getInputStream()獲取消息體
- 讀完信息后,斷開連接disconnect()
熟悉了HttpsURLConnection的父類,那么Https的連接就很好建立了。從前面的基礎(chǔ)知識,我們可以知道,在Http的基礎(chǔ)上增加TLS/SSL的校驗,就可以得到Https了。
用URL.openConnection()打開一個協(xié)議為Https的Url,并返回一個HttpsURLConnection的實例,其余的設(shè)置與Http一模一樣,其實就可以建立一個默認的Https連接了。這是因為在Android默認的機制中已經(jīng)幫我們處理好了HostNameVarify和SSLSocketFactory這兩個類,并且很友好地允許開發(fā)者重寫這兩個函數(shù),配置符合自己業(yè)務(wù)特點的校驗機制(后面會有具體應(yīng)用)。
另外,Android默認使用了X509TrustManager來解析證書鏈,這個是標準權(quán)威機構(gòu)的證書管理工具。在HTTPS的證書未經(jīng)權(quán)威機構(gòu)認證的情況下,也要訪問HTTPS站點的兩種方法,一種方法是把該證書導(dǎo)入到Java的TrustStore文件中,另一種是自己實現(xiàn)并覆蓋缺省的X509證書信任管理器類。
HttpDNS在Https的解決方案
什么是HttpDNS?HttpDNS使用Http協(xié)議進行域名解析,代替現(xiàn)有基于UDP的DNS協(xié)議,域名解析請求直接發(fā)送到HttpDNS服務(wù)器,從而繞過運營商的Local DNS,能夠避免Local DNS造成的域名劫持問題和調(diào)度不精準問題。目前,比較有名的就是阿里云的HttpDNS解析服務(wù)了,同時,其它各大公司也有對應(yīng)的域名解析服務(wù)。但是,將HttpDNS應(yīng)用到HttpDNS時,就出現(xiàn)一些棘手的問題了。
問題背景是這樣的:用HttpDNS來進行Https的連接,客戶端需要驗證服務(wù)端下發(fā)的證書,驗證過程有兩個關(guān)鍵:
- 本地保存的根證書解開服務(wù)器發(fā)送的證書鏈,確認服務(wù)端下發(fā)的證書是由可信任的機構(gòu)頒發(fā)的。
- 客戶端需要檢查證書的domain域和擴展域,看是否包含本次請求的host,即發(fā)送證書的是不是我的請求方。
上述兩點都校驗通過,就證明當前的服務(wù)端是可信任的,否則就是不可信任,應(yīng)當中斷當前連接。
當客戶端使用HttpDNS解析域名時,請求URL中的host會被替換成HttpDNS解析出來的IP,所以在證書驗證的第2步,會出現(xiàn)domain不匹配的情況,導(dǎo)致SSL/TLS握手不成功。因此,針對“domain不匹配”問題,需要hook證書校驗過程中第2步,即HostNameVerify,將IP直接替換成原來的域名,再執(zhí)行證書驗證。
/*
* 使用HttpDNS在https的情況下的Demo
* 適用于Java和Android
*/
private void connectHttps() {
try {
String originalUrl = "https://m.baidu.com/";
URL url = new URL(originalUrl);
connection = (HttpsURLConnection) url.openConnection();
// 獲取IP Host地址
String ip = getIpHost(url.getHost());
if (ip != null) {
// 進行URL替換和HOST頭設(shè)置
String newUrl = originalUrl.replaceFirst(url.getHost(), ip);
connection = (HttpsURLConnection) new URL(newUrl).openConnection();
// 設(shè)置HTTP請求頭Host域
connection.setRequestProperty("Host", url.getHost());
}
final String urlHost = connection.getURL().getHost();
connection.setHostnameVerifier(new HostnameVerifier() {
// 為解決HTTPDNS中,session攜帶的Host和IP切換后的Host不一致導(dǎo)致握手失敗
@Override
public boolean verify(String hostname, SSLSession session) {
String host = connection.getRequestProperty("Host");
boolean isHostNameVerify = HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
if (isHostNameVerify) {
// 判斷IP來源和Session的IP一致后,強制將Host信息賦值到Session中
if (!host.equals(session.getPeerHost()) && !urlHost.equals(session.getPeerHost())) {
return false;
}
}
return isHostNameVerify;
}
});
DataInputStream dis = new DataInputStream(connection.getInputStream());
int len;
byte[] buff = new byte[4096];
StringBuilder response = new StringBuilder();
while ((len = dis.read(buff)) != -1) {
response.append(new String(buff, 0, len));
}
Log.d(TAG, "Response: " + response.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
只有這樣處理,才能真正使用上HttpDNS服務(wù),相關(guān)介紹也可以去阿里云服務(wù)的官網(wǎng)上進行查看。