在Android應用中使用自定義證書的HTTPS連接(下)

轉自:http://blog.csdn.net/raptor/article/details/18898937

因爲這部分纔是本文的重點,要說得詳細一點,所以單獨做成一篇來說。

安全地使用自定義證書的HTTPS連接方式

終極解決方案是:把證書編譯到應用中去,由應用自己來驗證證書。

生成KeyStore

要驗證自定義證書,首先要把證書編譯到應用中去,這需要JSSE提供的keytool工具來生成KeyStore文件。參考《Java 安全套接字編程以及 keytool 使用最佳實踐》,我試過了用JKS格式,但是結果連接失敗,報錯:Wrong version of key store。後來看了SO的這個帖才知道必須使用BKS的1.46版。更詳細的內容參考這篇《Using a Custom Certificate Trust Store on Android》。

這裏所謂的證書,實際上就是公鑰,你可以從web服務器配置的.crt文件或.pem文件裏獲得。比如12306就直接提供了公鑰證書下載,真是“服務周到”啊。

還 有一個比較簡單的辦法就是直接從瀏覽器裏獲得。比如用 FireFox 打開 https 鏈接,在地址欄頂部的小鎖上點一下,然後點“更多信息……”-“查看證書”-“詳細內容”-“導出”,即可將網站的X.509證書導出爲一個文本文件。不 過需要注意的是,這種方法只對某些HTTPS服務器有效——通常是使用自簽名證書或是使用類似StarCom免費證書服務器,但是對 12306 或 google 這種的就無效了,具體原因不明。

另外,不論是瀏覽器導出,還是服務器端獲得,都是公鑰證書,有兩種格式:純文本的.crt格式或是二進制的.cer格式。兩種都可以用。

然後,你需要一個特定版本的JCE Provider,就是上面說過的那個SO帖裏給的:http://www.bouncycastle.org/download/bcprov-jdk15on-146.jar 。注意,bouncycastle官網上目前發佈的1.50版我試了一下不可用,不知道是不是我打開的方式不對,總之用這個1.46版的是沒錯的。

把這兩個文件放在一起,然後在這個目錄下運行以下命令:

keytool -importcert -v -trustcacerts -alias cert12306 -file srca.cer \
-keystore cert12306.bks -storetype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath ./bcprov-jdk15on-146.jar -storepass pw12306

運行後將顯示證書內容並提示你是否確認,按Y回車確認即可。

其中cert12306是個隨便取的別名,供keytool管理時方便而已。srca.cer就是從12306網站下載的證書文件。cert12306.bks是生成的keyStore文件,注意,這個文件必須以Java變量名的方式命名,比如不能直接叫12306.bks,否則在加載資源時會因爲名字不是合格的JAVA變量名而出錯。 ./bcprov-jdk15on-146.jar 就是剛纔下載的那個JCE Provider。最後pw12306是一個密碼,用於確保KeyStore文件本身的安全。

使用自定義keyStore實現連接

以下就是這個方案的實現,基本上和TrustAll差不多,也是需要一個自定義的SSLSocketFactory,不過因爲還是需要驗證證書的,所以就不需要再定義TrustManager了,用系統內置的即可。不過爲了讀取KeyStore資源,需要增加一個Context參數。

另外,前面生成的那個cert12306.bks文件要放到 res/raw/ 目錄下。

  1. public class SSLCustomSocketFactory extends SSLSocketFactory {  
  2.     private static final String TAG = "SSLCustomSocketFactory";  
  3.   
  4.     private static final String KEY_PASS = "pw12306";  
  5.   
  6.     public SSLCustomSocketFactory(KeyStore trustStore) throws Throwable {  
  7.         super(trustStore);  
  8.     }  
  9.   
  10.     public static SSLSocketFactory getSocketFactory(Context context) {  
  11.         try {  
  12.             InputStream ins = context.getResources().openRawResource(R.raw.cert12306);  
  13.   
  14.             KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());  
  15.             try {  
  16.                 trustStore.load(ins, KEY_PASS.toCharArray());  
  17.             }  
  18.             finally {  
  19.                 ins.close();  
  20.             }  
  21.             SSLSocketFactory factory = new SSLCustomSocketFactory(trustStore);  
  22.             return factory;  
  23.         } catch (Throwable e) {  
  24.             Log.d(TAG, e.getMessage());  
  25.             e.printStackTrace();  
  26.         }  
  27.         return null;  
  28.     }  
  29. }  

其中cert12306就是bks資源文件名,pw12306就是前面設置的密碼。

同樣的,使用這個Factory註冊到scheme:

  1. schReg.register(new Scheme("https", SSLCustomSocketFactory.getSocketFactory(context), 443));  

現在,也可以成功地連接12306了,而且如此實現,基本上就能達到與用商業證書一樣的安全性了。

不過因爲現在只使用了12306的證書,沒有使用系統證書,所以只能連接12306,連GOOGLE都連接不了了。但這個問題好解決,只需要在連接不同網站時使用不同的HttpClient即可。或者自己實現一個混合驗證的SSLSocketFactory。

更加安全的雙向認證的HTTPS連接方式

當然,還有一種更安全的HTTPS通訊方式叫做雙向認證。相對的,上面所說的全都是指服務端的單向認證:即只有服務器配置了證書,客戶端只是使用服務器證書的公鑰。而雙向認證則是客戶端也有一個由服務器簽發的證書,這樣服務器可以確認連接過來的客戶端的身份,網絡銀行就使用了這種方式。

雙向認證的實現方式與本篇的實現方式差不多,只是在trustStore基礎之上再增加一個keyStore,其內容爲客戶端證書,當然這個證書就是同時包含公鑰和私鑰的。因爲搭建一個雙向認證的服務器比較麻煩,一般應用也沒必要,所以這裏不再詳細說明。

最後再次放出本文例子程序的完整代碼(Android Studio 0.4.2項目):https://bitbucket.org/raptorz/democert


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章