Android CA證書庫BKS製作、讀取和使用
前言
通常情況下,安卓使用Https與伺服器通訊時,專案中預設是不用內建CA根證的。
因為手機上預設內建了所有主流的CA根證,我們可以在設定 --> 安全 -->授信的證書列表檢視。

image.png
如果我們在設定中,手動停用了伺服器對應的根證,再與伺服器通訊,就會報SSL異常。如果通過瀏覽器訪問服務端的網頁,也會彈出類似的提示:未授信的網站。
實際上,使用者一般不清楚手機中授信的列表是什麼意思,也不會隨便禁用根證的。
但是也有很多軟體還是選擇將CA證書存放在專案本地,一起打包到apk中,原因:
1.怕使用者手動禁用掉手機授信列表中的CA根證
2.怕中間人攻擊(與CA廠商溝通,這種事情一般不會發生的,如果真被黑客攔截,基本上也破解不了,否則,證書也就沒安全性可言了)
3.使用了非官方CA機構頒發的證書(如自己製作的證書)
那麼,安卓APP中如何內建伺服器對應的根證呢?
在安卓中,通常使用.bks格式的證書庫來放置證書,好處是:我們可以將1~多個如.crt格式的CA根證存入bks庫中,當與伺服器https通訊時,會自動匹配、使用bks庫中合適的證書。(舉例,直白一點:如果bks中內建了a、b兩個根證,伺服器預設使用a進行通訊,當a快過期時,伺服器SSL配置切換成未過期的根證b,這時候app不用做任何調整,也不用升級的)
本期,我們就來了解下,bks證書如何製作、讀取和使用的。
Keytool 環境
Java的 Keytool工具可以用來製作bks。
環境的配置以mac系統為例(windows類似):
1.安裝java環境, 配置環境變數(我下的是jdk1.8.0_20);
2.下載bcprov-ext-jdk15on-151.jar;
3.將jar檔案拷貝到\Java\jdk1.8.0_20\jre\lib\ext;
開啟終端, 輸入keytool, 點選回車鍵, 如果顯示下圖效果, 則環境配置正常。

image.png
建立/匯入根證書到bks庫
要匯入的根證型別一般是.cer或者.crt。
命令:
keytool -importcert -v -trustcacerts -alias myalas1 -file VeriSign.cer -keystore mytrustcerts.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass '123456'
注意:
-alias別名:請修改成自己的別名
-file VeriSign.cer:指要匯入的根證檔案,請修改成自己的根證檔名稱
-keystore:本地bks證書庫檔案,修改成自己的證書庫檔名稱,如果檔案不存在,會自動建立
-storepass:證書庫密碼

image.png

image.png
檢視bks證書庫列表
keytool -list -rfc -keystore mytrustcerts.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass '123456'

image.png
從bks證書庫中匯出證書
keytool -export -alias myalas1 -file VeriSign.cer -keystore mytrustcerts.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass '123456'

image.png

image.png
檢視單個證書資訊(CA機構、md5、sha、有效期等)
keytool -printcert -file VeriSign_temp.cer

image.png
程式碼中的應用:
OKHttp為例:
1.將*.bks檔案放在專案的本地,可以是raw下,也可以是assets下
2.編寫程式碼:
public class MyOkhttpClient { private static MyOkhttpClient singleton; public static OkHttpClient getInstance() { if (singleton == null) { synchronized (CBOkhttpClient.class) { if (singleton == null) { OkHttpClient.Builder builder = new OkHttpClient().newBuilder(); builder.connectTimeout(50000, TimeUnit.MILLISECONDS); builder.writeTimeout(50000, TimeUnit.MILLISECONDS); builder.readTimeout(50000, TimeUnit.MILLISECONDS); try { SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance("BKS"); InputStream ksinstream = CBFramework.getApplication().getResources().openRawResource(R.raw.cbframework_trustcerts); trustStore.load(ksinstream, "".toCharArray()); ksinstream.close(); trustManagerFactory.init(trustStore); sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); builder.sslSocketFactory(sslContext.getSocketFactory(), new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }); } catch (Exception e) { CBLogger.t(e); } singleton = builder.build(); } } } return singleton; } }