最近在寫一個數(shù)據(jù)安全傳輸?shù)捻椖浚延龅降目右约霸趺唇鉀Q的,都記錄下來。
1.為什么要用HTTPS?
因為HTTP協(xié)議是沒有加密的明文傳輸協(xié)議,如果APP采用HTTP傳輸數(shù)據(jù),則會泄露傳輸內容,可能被中間人劫持,修改傳輸?shù)膬热荨榱吮Wo用戶的信息安全、保護自己的商業(yè)利益,減少攻擊面,我們需要保障通信信道的安全,采用開發(fā)方便的HTTPS是比較好的方式,比用私有協(xié)議要好,省時省力。
2.HTTPS通信原理
HTTPS是HTTP over SSL/TLS,HTTP是應用層協(xié)議,TCP是傳輸層協(xié)議,在應用層和傳輸層之間,增加了一個安全套接層SSL/TLS:
SSL/TLS層負責客戶端和服務器之間的加解密算法協(xié)商、密鑰交換、通信連接的建立,安全連接的建立過程如下所示:
3.如何使用HTTPS
3.1數(shù)字證書、CA與HTTPS
信息安全的基礎依賴密碼學,密碼學涉及算法和密鑰,算法一般是公開的,而密鑰需要得到妥善的保護,密鑰如何產(chǎn)生、分配、使用和回收,這涉及公鑰基礎設施。
公鑰基礎設施(PKI)是一組由硬件、軟件、參與者、管理政策與流程組成的基礎架構,其目的在于創(chuàng)造、管理、分配、使用、存儲以及撤銷數(shù)字證書。公鑰存儲在數(shù)字證書中,標準的數(shù)字證書一般由可信數(shù)字證書認證機構(CA,根證書頒發(fā)機構)簽發(fā),此證書將用戶的身份跟公鑰鏈接在一起。CA必須保證其簽發(fā)的每個證書的用戶身份是唯一的。
鏈接關系(證書鏈)通過注冊和發(fā)布過程創(chuàng)建,取決于擔保級別,鏈接關系可能由CA的各種軟件或在人為監(jiān)督下完成。PKI的確定鏈接關系的這一角色稱為注冊管理中心(RA,也稱中級證書頒發(fā)機構或者中間機構)。RA確保公鑰和個人身份鏈接,可以防抵賴。如果沒有RA,CA的Root 證書遭到破壞或者泄露,由此CA頒發(fā)的其他證書就全部失去了安全性,所以現(xiàn)在主流的商業(yè)數(shù)字證書機構CA一般都是提供三級證書,Root 證書簽發(fā)中級RA證書,由RA證書簽發(fā)用戶使用的證書。
X509證書鏈,左邊的是CA根證書,中間的是RA中間機構,右邊的是用戶:
3.2生成自己的CA根證書 , 生成服務端證書 省略
.............
3.3使用HttpsURLConnection進行HTTPS通信
獲取SSLContext
/**
* 獲取SSLContext
* @param context 上下文
* @return SSLContext
*/
private static SSLContext getSSLContext(Context context) {
try {
// 服務器端需要驗證的客戶端證書
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
// 客戶端信任的服務器端證書
KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
try {
keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
ksIn.close();
} catch (Exception ignore) {
}
try {
tsIn.close();
} catch (Exception ignore) {
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
return sslContext;
} catch (Exception e) {
Log.e("tag", e.getMessage(), e);
}
return null;
}
獲取HttpsURLConnection
/**
* @param context 上下文
* @param url 連接url
* @param method 請求方式
* @return HttpsURLConnection
*/
public static HttpsURLConnection getHttpsURLConnection(Context context, String url, String method,
String head_sid,String head_size,String head_dev,String head_protocol_ver,String head_msg_id,String head_md) {
URL u;
HttpsURLConnection connection = null;
try {
SSLContext sslContext = getSSLContext(context);
if (sslContext != null) {
u = new URL(url);
connection = (HttpsURLConnection) u.openConnection();
connection.setRequestMethod(method);//"POST" "GET"
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
//此處代碼和業(yè)務相關,請忽略
connection.setInstanceFollowRedirects(true);
connection.setRequestProperty("sid",head_sid);
connection.setRequestProperty("size",head_size);
connection.setRequestProperty("dev",head_dev);
connection.setRequestProperty("protocol-ver",head_protocol_ver);
connection.setRequestProperty("msg-id",head_msg_id);
connection.setRequestProperty("md",head_md);
connection.setRequestProperty("Content-Type", "application/json");
//此處代碼和業(yè)務相關,請忽略
connection.setHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
connection.setDefaultHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
connection.setSSLSocketFactory(sslContext.getSocketFactory());
connection.setConnectTimeout(30000);
}
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
至此,可以進行https的訪問了
4.遇到的問題
4.1.javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
我是因為把兩個證書的搞反了,還有如果要使用私有CA簽發(fā)的證書,必須重寫校驗證書鏈TrustManager中的方法
4.2.java.io.IOException: Hostname 'your url' was not verified
4.2.1.服務端https的證書沒有過審
解決方案:忽略ip的驗證
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
但是我發(fā)現(xiàn)這樣忽略hostname 的驗證,并沒有成功,最后我加了下面的代碼 對服務器證書域名進行強校驗:才成功
connection.setHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
4.2.2.驗證證書時發(fā)現(xiàn)真正請求和服務器的證書域名不一致。
解決這個問題有兩個方法:
1.重新生成服務器的證書,用真實的域名信息。
2.在客戶端代碼中增加如下代碼,忽略hostname 的驗證。(僅僅用于測試階段,不建議用于發(fā)布后的產(chǎn)品中。)