因為最近ios需要用到https童擎,所以公司的項目都從http的請求轉(zhuǎn)成了https的雙向認(rèn)證捆蜀,這里我關(guān)于安卓端https相關(guān)的知識點以及在請求過程中遇到的一些問題当宴。
一、https的介紹以及相關(guān)的專有名詞
https
簡單來說步清,HTTPS就是“安全版”的HTTP, HTTPS = HTTP + SSL。HTTPS相當(dāng)于在應(yīng)用層和TCP層之間加入了一個SSL(或TLS)虏肾,SSL層對從應(yīng)用層收到的數(shù)據(jù)進行加密廓啊。TLS/SSL中使用了RSA非對稱加密,對稱加密以及HASH算法封豪。
HTTPS和HTTP的區(qū)別
https協(xié)議需要到CA申請證書谴轮。
http是超文本傳輸協(xié)議,信息是明文傳輸吹埠;https 則是具有安全性的ssl加密傳輸協(xié)議第步。
http和https使用的是完全不同的連接方式,用的端口也不一樣缘琅,前者是80粘都,后者是443。
http的連接很簡單胯杭,是無狀態(tài)的驯杜;HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議做个,比http協(xié)議安全鸽心。
http默認(rèn)使用80端口,https默認(rèn)使用443端口
Https的劣勢
對數(shù)據(jù)進行加解密決定了它比http慢需要進行非對稱的加解密居暖,且需要三次握手顽频,首次連接比較慢點。
CA證書:
證書授權(quán)中心簽發(fā)的證書太闺,在訪問這類網(wǎng)站的時候糯景,瀏覽器地址欄的左端一般都有一個綠色盾牌。
自簽名證書:
Android應(yīng)用程序開發(fā)最可能使用到,一般由服務(wù)器管理者生成蟀淮。平常大家使用自簽名證書的情況比較多最住,因為自簽名證書不僅免費,而且有效時間可以比較長怠惶。
TLS:(Transport Layer Security涨缚,傳輸層安全協(xié)議)
用于兩個應(yīng)用程序之間提供保密性和數(shù)據(jù)完整性。TLS 1.0是IETF(Internet Engineering Task Force策治,Internet工程任務(wù)組)制定的一種新的協(xié)議脓魏,它建立在SSL 3.0協(xié)議規(guī)范之上,是SSL 3.0的后續(xù)版本通惫,可以理解為SSL 3.1茂翔,它是寫入了 RFC的。
該協(xié)議由兩層組成: TLS 記錄協(xié)議(TLS Record)和 TLS 握手協(xié)議(TLS Handshake)履腋。
SSL(Secure Socket Layer珊燎,安全套接字層)
為Netscape所研發(fā),用以保障在Internet上數(shù)據(jù)傳輸之安全府树,利用數(shù)據(jù)加密(Encryption)技術(shù)俐末,可確保數(shù)據(jù)在網(wǎng)絡(luò)上之傳輸過程中不會被截取。它已被廣泛地用于Web瀏覽器與服務(wù)器之間的身份認(rèn)證和加密數(shù)據(jù)傳輸奄侠。SSL協(xié)議位于TCP/IP協(xié)議與各種應(yīng)用層協(xié)議之間卓箫,為數(shù)據(jù)通訊提供安全支持。
SSL協(xié)議可分為兩層:
1)SSL記錄協(xié)議(SSL Record Protocol):它建立在可靠的傳輸協(xié)議(如TCP)之上垄潮,為高層協(xié)議提供數(shù)據(jù)封裝烹卒、壓縮、加密等基本功能的支持弯洗。
2)SSL握手協(xié)議(SSL Handshake Protocol):它建立在SSL記錄協(xié)議之上旅急,用于在實際的數(shù)據(jù)傳輸開始前,通訊雙方進行身份認(rèn)證牡整、協(xié)商加密算法藐吮、交換加密密鑰等。
二逃贝、https的單雙向認(rèn)證
服務(wù)器證書需要配置CA證書谣辞,不能是自簽名證書,甚至不能是不知名的證書機構(gòu)簽發(fā)的證書沐扳,否則就會有異常泥从。先說說https的認(rèn)證原理,如圖
相關(guān)格式說明
JKS:數(shù)字證書庫沪摄。
JKS里有KeyEntry和CertEntry躯嫉,在庫里的每個Entry都是靠別名(alias)來識別的纱烘。
P12:是PKCS12的縮寫。
同樣是一個存儲私鑰的證書庫祈餐,由.jks文件導(dǎo)出的擂啥,用戶在PC平臺安裝,用于標(biāo)示用戶的身份帆阳。
CER:俗稱數(shù)字證書啤它。
目的就是用于存儲公鑰證書,任何人都可以獲取這個文件 舱痘。
BKS:
由于Android平臺不識別。keystore和.jks格式的證書庫文件离赫,因此Android平臺引入一種的證書庫格式芭逝,BKS。
有些人可能有疑問渊胸,為什么Tomcat只有一個server.keystore文件旬盯,而客戶端需要兩個庫文件?
因為有時客戶端可能需要訪問過個服務(wù)翎猛,而服務(wù)器的證書都不相同胖翰,因此客戶端需要制作一個truststore來存儲受信任的服務(wù)器的證書列表。因此為了規(guī)范創(chuàng)建一個truststore.jks用于存儲受信任的服務(wù)器證書切厘,創(chuàng)建一個client.jks來存儲客戶端自己的私鑰萨咳。對于只涉及與一個服務(wù)端進行雙向認(rèn)證的應(yīng)用,將server.cer導(dǎo)入到client.jks中也可疫稿。
1 單向認(rèn)證
如果你的項目的網(wǎng)絡(luò)框架是okhttp培他,那么使用https還是挺簡單的,因為okhttp默認(rèn)支持HTTPS遗座。
/**
* HttpsUrlConnection 方式舀凛,支持指定**.crt證書驗證,此種方式Android官方建議
*
* @throws CertificateException
* @throws IOException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @throws NoSuchProviderException
*/
public void initHttpsConnection() {
try {
InputStream in = getAssets().open("server.cer");
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate cer = certificateFactory.generateCertificate(in);
System.out.println("ca=" + ((X509Certificate) cer).getSubjectDN());
// Create a KeyStore containing our trusted CAs
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null);
keystore.setCertificateEntry("ca", cer);
// Create a TrustManager that trusts the CAs in our KeyStore
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
trustManagerFactory.init(keystore);
// Create an SSLContext that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
URL url = new URL(URL);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());
conn.setHostnameVerifier(DO_NOT_VERIFY);
conn.setRequestMethod("POST");// 設(shè)置請求類型為post
conn.setConnectTimeout(10000);// 設(shè)置超時時間
conn.setReadTimeout(50000);
conn.setDoInput(true);
conn.setDoOutput(true);
InputStream input = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
StringBuffer result = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
Log.d("TAG", line);
}
} catch (Exception e) {
Log.e(this.getClass().getName(), e.getMessage());
}
}
static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
// 信任所有主機
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
三途蒋、谷歌官網(wǎng) HttpsURLConnection 信任自簽名證書代碼示例
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStreamor ...)
// X.509 是Android唯一支持的證書格式
CertificateFactory cf =CertificateFactory.getInstance("X.509");
// From[https://www.washington.edu/itconnect/security/ca/load-der.crt](https://www.washington.edu/itconnect/security/ca/load-der.crt)
InputStream caInput = newBufferedInputStream(new FileInputStream("load-der.crt"));
// 證書的加載也可以是字符串的方式猛遍,建議使用字符串,并且對字符串做些額外的處理
// InputStream caInput=newByteArrayInputStream(cerString.getBytes());
Certificate ca;
try {
ca =cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
// KeyStore 默認(rèn)類型是 BKS号坡,雖然 Android 的文檔中的例子寫了 JKS懊烤,但是 Android 并不支持JKS
String keyStoreType =KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
// 加載一個默認(rèn)的秘鑰倉庫,倉庫是空的
keyStore.setCertificateEntry("ca",ca);
// Create a TrustManager that trusts the CAs inour KeyStore
// TrustManager 是證書校驗的關(guān)鍵筋帖,不使用系統(tǒng)默認(rèn)校驗方式時奸晴,需要開發(fā)者自己實現(xiàn)接口,完成校驗代碼
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();// 默認(rèn)算法 PKIX
TrustManagerFactory tmf =TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses ourTrustManager
// Android 不僅支持 TLS日麸,還有 TLSv1.2 等寄啼,TLSv1.2需要 API levels 20+
// 更多選擇可以參考[https://developer.android.com/re ... /ssl/SSLSocket.html](https://developer.android.com/reference/javax/net/ssl/SSLSocket.html)
SSLContext context =SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(),null);
// Tell the URLConnection to use aSocketFactory from our SSLContext
URL url = newURL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
// 證書校驗的關(guān)鍵逮光,設(shè)置SSLSocketFactory
// 執(zhí)行TrustManagerImpl 中 checkServerTrusted 方法完成服務(wù)器證書校驗
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in =urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
以上代碼,可以解決證書信任問題墩划。但同時需要注意的是涕刚,這里是基于Android默認(rèn)的信任檢查來解決的。因為我們沒有對TrustManager做任何修改乙帮,如果仍然遇到證書校驗不通過的情況杜漠,則需要重新實現(xiàn)TrustManager,請用以下代碼代替“tmf.getTrustManagers()”:
TrustManager tm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {return null; }
@Override
public void checkServerTrusted(X509Certificate[] chain,String authType) throwsCertificateException {}
@Override
public void checkClientTrusted(X509Certificate[] chain,String authType) throwsCertificateException {
// 方法直接返回察净,將不對服務(wù)器證書做任何校驗
// 確認(rèn)服務(wù)器端證書頒發(fā)者和代碼中的證書主體一致
if (!chain[0].getIssuerDN().equals(cert.getSubjectDN())) { }
// 確認(rèn)服務(wù)器端證書被代碼中證書公鑰簽名
chain[0].verify(cert.getPublicKey());
// 確認(rèn)服務(wù)器端證書沒有過期
chain[0].checkValidity();
}
};
context.init(null, new TrustManager[]{tm},null);
如何校驗域名
在HttpsURLConnection中校驗域名是比較簡單的驾茴,這里Google提供了為URLConnection替換驗證過程的例子:
// Create an HostnameVerifier that hardwiresthe expected hostname.
// Note that is different than the URL'shostname:
// example.com versus example.org
// 這里強調(diào)的是URL中的域名和證書中不一致,所以默認(rèn)的校驗不能通過HostnameVerifier hostnameVerifier = newHostnameVerifier() {
@Override
publicboolean verify(String hostname, SSLSession session) {
// 這段代碼中 hostname 應(yīng)該是 example.org
// 獲取默認(rèn)的 HostnameVerifier
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
// 如果直接返回 true氢卡,等于不做域名校驗
return hv.verify("example.com", session);
}
};
// Tell the URLConnection to use ourHostnameVerifier
URL url = newURL("https://example.org/");
HttpsURLConnection urlConnection =(HttpsURLConnection)url.openConnection();
urlConnection.setHostnameVerifier(hostnameVerifier);
InputStream in =urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
正確使用域名校驗很重要锈至,直接返回true顯然不安全∫肭兀回調(diào)參數(shù)hostname是訪問的域名峡捡,session可以通過getPeerCertificates獲取到證書的相關(guān)信息,如果需要自己寫代碼完成域名校驗筑悴,需要根據(jù)實際開發(fā)情況正確校驗们拙。
總結(jié)
用HttpsURLConnection來校驗證書和域名的方法如下(不明確指定校驗方式時,系統(tǒng)會采用默認(rèn)的方式):
HttpsURLConnection urlConnection =(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
urlConnection.setHostnameVerifier(hostnameVerifier);
校驗的順序是先校驗證書阁吝,再校驗域名砚婆。為了能夠更好的理解Android HTTPS證書校驗和域名校驗的默認(rèn)實現(xiàn),可以參考Android的源代碼:
證書校驗的默認(rèn)實現(xiàn):類:TrustManagerImpl.java
Git 獲取源碼:git clonehttps://android.googlesource.com/platform/external/conscrypt
域名校驗的默認(rèn)實現(xiàn):類:OkHostnameVerifier.java
Git 獲取源碼:git clonehttps://android.googlesource.com/platform/external/okhttp
四突勇、https證書的制作
很明顯要測https射沟,必須要有證書∮刖常客戶端持有服務(wù)端的公鑰證書验夯,并持有自己的私鑰,服務(wù)端持有客戶的公鑰證書摔刁,并持有自己私鑰挥转。
https認(rèn)證原理:
1)建立連接的時候,客戶端利用服務(wù)端的公鑰證書來驗證服務(wù)器是否上是目標(biāo)服務(wù)器共屈。
2)服務(wù)端利用客戶端的公鑰來驗證客戶端是否是目標(biāo)客戶端绑谣。(請參考RSA非對稱加密以及HASH校驗算法)
3)服務(wù)端給客戶端發(fā)送數(shù)據(jù)時,需要將服務(wù)端的證書發(fā)給客戶端驗證拗引,驗證通過才運行發(fā)送數(shù)據(jù)借宵。
4)同樣,客戶端請求服務(wù)器數(shù)據(jù)時矾削,也需要將自己的證書發(fā)給服務(wù)端驗證壤玫,通過才允許執(zhí)行請求豁护。
證書制作步驟如下:
1 生成客戶端keystore
keytool -genkeypair -alias client -keyalg RSA -validity 3650 -keypass 123456 -storepass 123456 -keystore client.jks
2 生成服務(wù)端keystore
keytool -genkeypair -alias server -keyalg RSA -validity 3650 -keypass 123456 -storepass 123456 -keystore server.keystore
//注意:CN必須與IP地址匹配,否則需要修改host
3 導(dǎo)出客戶端證書
keytool -export -alias client -file client.cer -keystore client.jks -storepass 123456
4 導(dǎo)出服務(wù)端證書
keytool -export -alias server -file server.cer -keystore server.keystore -storepass 123456
5 重點:證書交換
將客戶端證書導(dǎo)入服務(wù)端keystore中欲间,再將服務(wù)端證書導(dǎo)入客戶端keystore中楚里, 一個keystore可以導(dǎo)入多個證書,生成證書列表猎贴。
- 生成客戶端信任證書庫(由服務(wù)端證書生成的證書庫):
keytool -import -v -alias server -file server.cer -keystore truststore.jks -storepass 123456 - 將客戶端證書導(dǎo)入到服務(wù)器證書庫(使得服務(wù)器信任客戶端證書):
keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456
6 生成Android識別的BKS庫文件
用Portecle工具轉(zhuǎn)成bks格式班缎,最新版本是1.10。
下載鏈接:https://sourceforge.net/projects/portecle/
運行protecle.jar將client.jks和truststore.jks分別轉(zhuǎn)換成client.bks和truststore.bks,然后放到android客戶端的assert目錄下
File -> open Keystore File -> 選擇證書庫文件 -> 輸入密碼 -> Tools -> change keystore type -> BKS -> save keystore as -> 保存即可
這個操作很簡單她渴,如果不懂可自行百度达址。
我在Windows下生成BKS的時候會報錯失敗,后來我換到CentOS用OpenJDK1.7立馬成功了趁耗,如果在這步失敗的同學(xué)可以換到Linux或Mac下操作苏携,
將生成的BKS拷貝回Windows即可。
7 配置Tomcat服務(wù)器
修改server.xml文件对粪,配置8443端口
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="${catalina.base}/key/server.keystore" keystorePass="123456"
truststoreFile="${catalina.base}/key/server.keystore" truststorePass="123456"/>
備注: - keystoreFile:指定服務(wù)器密鑰庫,可以配置成絕對路徑装蓬,本例中是在Tomcat目錄中創(chuàng)建了一個名為key的文件夾著拭,僅供參考。
- keystorePass:密鑰庫生成時的密碼
- truststoreFile:受信任密鑰庫牍帚,和密鑰庫相同即可
- truststorePass:受信任密鑰庫密碼
8 Android App編寫B(tài)KS讀取創(chuàng)建證書自定義的SSLSocketFactory
private final static String CLIENT_PRI_KEY = "client.bks";
private final static String TRUSTSTORE_PUB_KEY = "truststore.bks";
private final static String CLIENT_BKS_PASSWORD = "123456";
private final static String TRUSTSTORE_BKS_PASSWORD = "123456";
private final static String KEYSTORE_TYPE = "BKS";
private final static String PROTOCOL_TYPE = "TLS";
private final static String CERTIFICATE_FORMAT = "X509";
public static SSLSocketFactory getSSLCertifcation(Context context) {
SSLSocketFactory sslSocketFactory = null;
try {
// 服務(wù)器端需要驗證的客戶端證書儡遮,其實就是客戶端的keystore
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);// 客戶端信任的服務(wù)器端證書
KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE);//讀取證書
InputStream ksIn = context.getAssets().open(CLIENT_PRI_KEY);
InputStream tsIn = context.getAssets().open(TRUSTSTORE_PUB_KEY);//加載證書
keyStore.load(ksIn, CLIENT_BKS_PASSWORD.toCharArray());
trustStore.load(tsIn, TRUSTSTORE_BKS_PASSWORD.toCharArray());
ksIn.close();
tsIn.close();
//初始化SSLContext
SSLContext sslContext = SSLContext.getInstance(PROTOCOL_TYPE);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(CERTIFICATE_FORMAT);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(CERTIFICATE_FORMAT);
trustManagerFactory.init(trustStore);
keyManagerFactory.init(keyStore, CLIENT_BKS_PASSWORD.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
sslSocketFactory = sslContext.getSocketFactory();
} catch (KeyStoreException e) {...}//省略各種異常處理,請自行添加
return sslSocketFactory;
}
9 Android App獲取SSLFactory實例進行網(wǎng)絡(luò)訪問
private void fetchData() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(SSLHelper.getSSLCertifcation(context))//獲取SSLSocketFactory
.hostnameVerifier(new UnSafeHostnameVerifier())//添加hostName驗證器
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://10.2.8.56:8443")//填寫自己服務(wù)器IP
.addConverterFactory(GsonConverterFactory.create())//添加 json 轉(zhuǎn)換器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加 RxJava 適配器
.client(okHttpClient)
.build();
IUser userIntf = retrofit.create(IUser.class);
userIntf.getUser(user.getPhone())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<UserBean>() {
//省略onCompleted暗赶、onError蹈丸、onNext
}
});
}
private class UnSafeHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;//自行添加判斷邏輯多柑,true->Safe,false->unsafe
}
}
okttp設(shè)置證書的方法
private staticSSLSocketFactorygetSSLSocketFactory_Certificate(Context context, String keyStoreType,intkeystoreResId)
throwsCertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = context.getResources().openRawResource(keystoreResId);
Certificate ca = cf.generateCertificate(caInput);
caInput.close();
if(keyStoreType ==null|| keyStoreType.length() ==0) {
keyStoreType = KeyStore.getDefaultType();
}
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null,null);
keyStore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
TrustManager[] wrappedTrustManagers =getWrappedTrustManagers(tmf.getTrustManagers());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, wrappedTrustManagers,null);
returnsslContext.getSocketFactory();
}
調(diào)用時把服務(wù)器生成的.cer證書放到raw目錄下(其他地方自己搞),然后調(diào)用:
SSLSocketFactory sslSocketFactory =getSSLSocketFactory_Certificate(context,"BKS", R.raw.XXX);
//new一個OkHttpClient
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setSslSocketFactory(sslSocketFactory);
Retrofit構(gòu)造的adapter使用自定義okHttpClient
RestAdapter.Builder builder =newRestAdapter.Builder()
.setEndpoint(serverUrl)
.setClient(newOkClient(okHttpClient));
五蓝翰、https遇到的一些坑
1 Https網(wǎng)絡(luò)請求的時候經(jīng)常碰到handshake aborted。解決辦法如下
package com.guiying.common.http;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RawRes;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* HttpsUtils來自于https://github.com/guiying712/AndroidModulePattern/blob/master/common/src/main/java/com/guiying/common/http/HttpsUtil.java圆米;
* 其他參考的文章有:http://android.jobbole.com/83787/吊输;
*
* Android 4.X 對TLS1.1、TLS1.2的支持參考了http://blog.csdn.net/joye123/article/details/53888252
*/
public class HttpsUtil {
/**
* 包裝的 SSL(Secure Socket Layer)參數(shù)類
*/
public static class SSLParams {
public SSLSocketFactory sSLSocketFactory;
public X509TrustManager trustManager;
}
/**
* @param context 上下文
* @param certificatesId "XXX.cer" 文件 (文件位置res/raw/XXX.cer)
* @param bksFileId "XXX.bks"文件(文件位置res/raw/XXX.bks)
* @param password The certificate's password.
* @return SSLParams
*/
public static SSLParams getSslSocketFactory(Context context, @RawRes int[] certificatesId, @RawRes int bksFileId, String password) {
if (context == null) {
throw new NullPointerException("context == null");
}
SSLParams sslParams = new SSLParams();
try {
TrustManager[] trustManagers = prepareTrustManager(context, certificatesId);
KeyManager[] keyManagers = prepareKeyManager(context, bksFileId, password);
//創(chuàng)建TLS類型的SSLContext對象激率,that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
X509TrustManager x509TrustManager;
if (trustManagers != null) {
x509TrustManager = new MyTrustManager(chooseTrustManager(trustManagers));
} else {
x509TrustManager = new UnSafeTrustManager();
}
//用上面得到的trustManagers初始化SSLContext咳燕,這樣sslContext就會信任keyStore中的證書
sslContext.init(keyManagers, new TrustManager[]{x509TrustManager}, null);
//通過sslContext獲取SSLSocketFactory對象
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
/*Android 4.X 對TLS1.1、TLS1.2的支持*/
sslParams.sSLSocketFactory = new Tls12SocketFactory(sslContext.getSocketFactory());
sslParams.trustManager = x509TrustManager;
return sslParams;
}
sslParams.sSLSocketFactory = sslContext.getSocketFactory();
sslParams.trustManager = x509TrustManager;
return sslParams;
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
throw new AssertionError(e);
}
}
/**
* 主機名校驗方法
*/
public static HostnameVerifier getHostnameVerifier() {
return new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return hostname.equalsIgnoreCase(session.getPeerHost());
}
};
}
private static TrustManager[] prepareTrustManager(Context context, int[] certificatesId) {
if (certificatesId == null || certificatesId.length <= 0) {
return null;
}
try {
//創(chuàng)建X.509格式的CertificateFactory
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 創(chuàng)建一個默認(rèn)類型的KeyStore乒躺,存儲我們信任的證書
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (int certificateId : certificatesId) {
//從本地資源中獲取證書的流
InputStream cerInputStream = context.getResources().openRawResource(certificateId);
String certificateAlias = Integer.toString(index++);
//certificate是java.security.cert.Certificate招盲,而不是其他Certificate
//證書工廠根據(jù)證書文件的流生成證書Certificate
Certificate certificate = certificateFactory.generateCertificate(cerInputStream);
//將證書certificate作為信任的證書放入到keyStore中
keyStore.setCertificateEntry(certificateAlias, certificate);
try {
if (cerInputStream != null)
cerInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//TrustManagerFactory是用于生成TrustManager的,這里創(chuàng)建一個默認(rèn)類型的TrustManagerFactory
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
//用我們之前的keyStore實例初始化TrustManagerFactory,這樣trustManagerFactory就會信任keyStore中的證書
trustManagerFactory.init(keyStore);
return trustManagerFactory.getTrustManagers();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static KeyManager[] prepareKeyManager(Context context, @RawRes int bksFileId, String password) {
try {
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(context.getResources().openRawResource(bksFileId), password.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, password.toCharArray());
return keyManagerFactory.getKeyManagers();
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) {
e.printStackTrace();
}
return null;
}
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
return null;
}
/**
* 客戶端不對證書做任何檢查;
* 客戶端不對證書做任何驗證的做法有很大的安全漏洞嘉冒。
*/
private static class UnSafeTrustManager implements X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
private static class MyTrustManager implements X509TrustManager {
private X509TrustManager defaultTrustManager;
private X509TrustManager localTrustManager;
private MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
//TrustManagerFactory是用于生成TrustManager的,創(chuàng)建一個默認(rèn)類型的TrustManagerFactory
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
defaultTrustManager = chooseTrustManager(trustManagerFactory.getTrustManagers());
this.localTrustManager = localTrustManager;
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ce) {
localTrustManager.checkServerTrusted(chain, authType);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
/**
* 自行實現(xiàn)SSLSocketFactory 曹货,實現(xiàn)Android 4.X 對TLSv1.1咆繁、TLSv1.2的支持
*/
private static class Tls12SocketFactory extends SSLSocketFactory {
private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"};
final SSLSocketFactory delegate;
private Tls12SocketFactory(SSLSocketFactory base) {
this.delegate = base;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return patch(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return patch(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return patch(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return patch(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return patch(delegate.createSocket(address, port, localAddress, localPort));
}
private Socket patch(Socket s) {
//代理SSLSocketFactory在創(chuàng)建一個Socket連接的時候,會設(shè)置Socket的可用的TLS版本控乾。
if (s instanceof SSLSocket) {
((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);
}
return s;
}
}
}
2 SSLHandshakeException
導(dǎo)致SSLHandshakeException是由于簽署證書的CA不被系統(tǒng)所信任么介。一種原因是CA機構(gòu)比較新,還沒被android系統(tǒng)證書庫內(nèi)置蜕衡∪蓝蹋或者你的android版本比較舊,證書庫不全慨仿。
可能有以下幾個原因:
1.簽名證書的CA機構(gòu)不知名
2.使用了自簽名的證書
3.服務(wù)端配置問題
這種情況的解決方案和使用了自簽名證書的方案是一樣的久脯,重寫Google默認(rèn)的證書校驗邏輯。
六镰吆、最后
雖然https算是加密成功了帘撰,但是客戶端里面放私鑰并不安全。 很容易被拿出來. 所以銀行都用u盾解決万皿。網(wǎng)上有一些關(guān)于客戶端安全性的說法摧找,粘出來給大家看下
- 手動輸入密碼. 如社交類, 電商
- 通過jni, 花指令方式, 或者經(jīng)過一系列復(fù)雜運算繞幾圈得出密鑰---增加破解難度
- 銀行那種用硬件。
- 加強服務(wù)器接口的安全性, 能保證接口泄露了也不會造成大的危害. 極端點甚至可以公開接口鼓勵第三方實現(xiàn).