最近在Https方面做了一些探索炊琉,在Android上做了一些實際應用,在此分享出來以便后查。
Https協(xié)議是什么
目前佃却,Https已經(jīng)成為了主流趨勢,無論在大型互聯(lián)網(wǎng)公司如BAT窘俺,還是小型創(chuàng)業(yè)公司饲帅,都逐漸將自己的服務切換成了Https复凳。Https能提供一種安全可靠的加密通信連接方式,有效防止信息泄密以及劫持等情況發(fā)生灶泵。因此育八,使用Https無論從用戶體驗還是企業(yè)長期利益來看,都是十分有益的赦邻。
Http協(xié)議
我們首先說說什么是Http協(xié)議髓棋?Http是一個屬于應用層的面向對象的協(xié)議,由于其簡捷惶洲、快速的方式按声,適用于分布式超媒體信息系統(tǒng)。
Http(超文本傳輸協(xié)議)是一個基于請求與響應模式的恬吕、無狀態(tài)的签则、應用層的協(xié)議,愁砹希基于TCP的連接方式渐裂,HTTP1.1版本中給出一種持續(xù)連接的機制,絕大多數(shù)的Web開發(fā)钠惩,都是構建在HTTP協(xié)議之上的Web應用柒凉。
順便說一句,Http1.1協(xié)議已經(jīng)是目前最主流的協(xié)議篓跛,但是新的Http2.0協(xié)議也已經(jīng)提出扛拨,在未來10年的時間內(nèi),必將逐漸登上主舞臺举塔。這種在應用層的推廣會比IPV6這種網(wǎng)絡層的推廣快很多。
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實際上應用了Netscape的安全全套接字層(SSL泳猬,TLS則是SSL的升級版,修復了SSL的漏洞)作為Http應用層的子層。Https使用端口443得封,而不是像Http那樣使用端口80和TCP/IP進行通信埋心。SSL使用40位關鍵字作為RC4流加密算法,這對于商業(yè)信息的加密是合適的忙上。Https和SSL支持使用X.509數(shù)字認證拷呆,如果需要的話用戶可以確認發(fā)送者是誰。Https協(xié)議使用SSL在發(fā)送方把原始數(shù)據(jù)進行加密疫粥,然后在接受方進行解密茬斧,加密和解密需要發(fā)送方和接受方通過交換共知的密鑰來實現(xiàn),因此手形,所傳送的數(shù)據(jù)不容易被網(wǎng)絡黑客截獲和解密啥供。
客戶端在使用Https方式與Web服務器通信時有以下幾個步驟:
- 客戶使用Https的URL訪問Web服務器,要求與Web服務器建立SSL連接库糠。
- Web服務器收到客戶端請求后伙狐,會將網(wǎng)站的證書信息(證書中包含公鑰)傳送一份給客戶端。
- 客戶端的瀏覽器與Web服務器開始協(xié)商SSL連接的安全等級瞬欧,也就是信息加密的等級贷屎。
- 客戶端的瀏覽器根據(jù)雙方同意的安全等級,建立會話密鑰艘虎,然后利用網(wǎng)站的公鑰將會話密鑰加密唉侄,并傳送給網(wǎng)站。
- Web服務器利用自己的私鑰解密出會話密鑰野建。
- Web服務器利用會話密鑰加密與客戶端之間的通信属划。
基本流程圖如下:
另外,假如為了安全保密候生,將一個網(wǎng)站所有的Web應用都啟用SSL技術來加密同眯,并使用Https協(xié)議進行傳輸,那么該網(wǎng)站的性能和效率會降低唯鸭,而且沒有這個必要须蜗,因為一般來說并不是所有數(shù)據(jù)都要求那么高的安全保密級別。我們只需對那些涉及機密數(shù)據(jù)的交互處理使用Https協(xié)議目溉,這樣就做到魚與熊掌兼得明肮。
Https在Android的使用——HttpsURLConnection
在Android領域,如何建立一個可靠的Https協(xié)議鏈接呢缭付?Android是基于Java進行編程的柿估,常見的Http協(xié)議建立方式有Apache的HttpClient和Sun公司提供的HttpURLConnection。不過陷猫,從API19開始官份,Google官方已經(jīng)推薦Android開發(fā)者使用HttpURLConnection來進行網(wǎng)絡連接只厘,當然,為了更方便的使用Http舅巷,很多開源庫也是提供了許多方便的功能羔味,比如Volley、OKHttp等钠右。
但是赋元,萬變不離其宗,基本流程都與HttpsURLConnection連接相似飒房。這里我就主要分享一下關于HttpsURLConnection的使用心得搁凸。
首先,明確其繼承關系狠毯。
HttpsURLConnection -> HttpURLConnection -> URLConnection
從這個繼承關系可以看出Https其實就是Http的延伸护糖,同時也是一種URLConnection的實現(xiàn)。
URLConnection是abstract類型嚼松,它是所有端到URL連接的一個父類嫡良,其實例可以用來讀寫URL的資源。要建立一個這樣的URL連接需要幾個步驟:
- openConnection:建立對應URL的連接
- 設置參數(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();
}
總結一下,基本是這幾個步驟:
- 通過調(diào)用URL.openConnection()獲取一個HttpURLConnection實例
- 設置相應請求頭信息
- 設置上傳的請求體(這是可選項)setDoOutput(true)
- 讀取返回信息罕偎,消息頭包括消息體類型很澄、長度、Cookie等颜及,用getInputStream()獲取消息體
- 讀完信息后甩苛,斷開連接disconnect()
熟悉了HttpsURLConnection的父類,那么Https的連接就很好建立了俏站。從前面的基礎知識讯蒲,我們可以知道,在Http的基礎上增加TLS/SSL的校驗乾翔,就可以得到Https了。
用URL.openConnection()打開一個協(xié)議為Https的Url施戴,并返回一個HttpsURLConnection的實例反浓,其余的設置與Http一模一樣,其實就可以建立一個默認的Https連接了赞哗。這是因為在Android默認的機制中已經(jīng)幫我們處理好了HostNameVarify和SSLSocketFactory這兩個類雷则,并且很友好地允許開發(fā)者重寫這兩個函數(shù),配置符合自己業(yè)務特點的校驗機制(后面會有具體應用)肪笋。
另外月劈,Android默認使用了X509TrustManager來解析證書鏈度迂,這個是標準權威機構的證書管理工具。在HTTPS的證書未經(jīng)權威機構認證的情況下猜揪,也要訪問HTTPS站點的兩種方法惭墓,一種方法是把該證書導入到Java的TrustStore文件中,另一種是自己實現(xiàn)并覆蓋缺省的X509證書信任管理器類而姐。
HttpDNS在Https的解決方案
什么是HttpDNS腊凶?HttpDNS使用Http協(xié)議進行域名解析,代替現(xiàn)有基于UDP的DNS協(xié)議拴念,域名解析請求直接發(fā)送到HttpDNS服務器钧萍,從而繞過運營商的Local DNS,能夠避免Local DNS造成的域名劫持問題和調(diào)度不精準問題政鼠。目前风瘦,比較有名的就是阿里云的HttpDNS解析服務了,同時公般,其它各大公司也有對應的域名解析服務万搔。但是,將HttpDNS應用到HttpDNS時俐载,就出現(xiàn)一些棘手的問題了蟹略。
問題背景是這樣的:用HttpDNS來進行Https的連接,客戶端需要驗證服務端下發(fā)的證書遏佣,驗證過程有兩個關鍵:
- 本地保存的根證書解開服務器發(fā)送的證書鏈挖炬,確認服務端下發(fā)的證書是由可信任的機構頒發(fā)的。
- 客戶端需要檢查證書的domain域和擴展域状婶,看是否包含本次請求的host意敛,即發(fā)送證書的是不是我的請求方。
上述兩點都校驗通過膛虫,就證明當前的服務端是可信任的草姻,否則就是不可信任,應當中斷當前連接稍刀。
當客戶端使用HttpDNS解析域名時撩独,請求URL中的host會被替換成HttpDNS解析出來的IP,所以在證書驗證的第2步账月,會出現(xiàn)domain不匹配的情況综膀,導致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頭設置
String newUrl = originalUrl.replaceFirst(url.getHost(), ip);
connection = (HttpsURLConnection) new URL(newUrl).openConnection();
// 設置HTTP請求頭Host域
connection.setRequestProperty("Host", url.getHost());
}
final String urlHost = connection.getURL().getHost();
connection.setHostnameVerifier(new HostnameVerifier() {
// 為解決HTTPDNS中,session攜帶的Host和IP切換后的Host不一致導致握手失敗
@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ǎng)上進行查看取胎。