歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋(píng)果6袋
6
麥子學(xué)院

Android學(xué)習(xí)之HTTPS的應(yīng)用詳解

發(fā)布時(shí)間:2017-09-04 20:21  回復(fù):0  查看:2634   最后回復(fù):2017-09-04 20:21  
現(xiàn)在做 Android開(kāi)發(fā) ,或多或少都會(huì)接觸到一些HTTPS 的網(wǎng)絡(luò)請(qǐng)求,作為新人,踩坑是避免不了的,本文 將忽略HTTP HTTPS 的一些網(wǎng)絡(luò)基礎(chǔ)知識(shí),單純的從代碼角度淺談 HTTPS Android 中的應(yīng)用,針對(duì)用  自簽名證書(shū)進(jìn)行單項(xiàng)驗(yàn)證的HTTPS 請(qǐng)求  ,如果是有CA 或者 RA 簽發(fā)的證書(shū), Android 底層會(huì)進(jìn)行證書(shū)的相關(guān)校驗(yàn),不必自己實(shí)現(xiàn)相關(guān)的功能。
   一、Android支持HTTPS的核心設(shè)計(jì)
  要想在Android 上實(shí)現(xiàn) HTTPS 通訊,最核心的有兩點(diǎn),  證書(shū)校驗(yàn)    域名校驗(yàn)  。其中前者是必須的,后者是非必須的。
   證書(shū)校驗(yàn)
  如果要想在Android 上進(jìn)行 HTTPS 通訊,通常會(huì)將證書(shū)內(nèi)置在 APP 內(nèi),或者將證書(shū)的公鑰提取出來(lái),直接以字符串的形式寫(xiě)在類(lèi)里面。其實(shí)這兩種方式都差不多,區(qū)別就是證書(shū)里面不僅僅包含公鑰,可能還有一些其他的信息。
  由于是自簽名的證書(shū),如果是直接發(fā)起HTTPS 請(qǐng)求的話,將會(huì)出現(xiàn)以下異常信息:
   javax.net.ssl.SSLHandshakeException:  java.security.cert.CertPathValidatorException:  Trust  anchor  for  certification  path  not found.
  出現(xiàn)這個(gè)異常信息的原因就是自簽名證書(shū)不被系統(tǒng)所信任,如果出現(xiàn)這個(gè)錯(cuò)誤,說(shuō)明HTTPS 請(qǐng)求在 TLS 握手過(guò)程中被中斷了。
  要想實(shí)現(xiàn)Android 系統(tǒng)信任服務(wù)器自簽名的證書(shū),核心思想如下:
   1、通過(guò)流將服務(wù)器自簽名證書(shū)轉(zhuǎn)換成X.509格式
  證書(shū)放在Assets 目錄,其他目錄同理獲取輸入流
   InputStream inputStream = mContext.getAssets().open("jetty_ssl.cer"); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); Certificate certificate = certificateFactory.generateCertificate(inputStream);
  Certificate 就是獲取到的 X.509 格式的證書(shū)類(lèi)。
   2、通過(guò)服務(wù)器自簽名證書(shū)轉(zhuǎn)換的X.509證書(shū)生成包含服務(wù)端證書(shū)的KeyStore對(duì)象
  獲取一個(gè)空的KeyStore 對(duì)象,所以這里的輸入流和密碼都為 null 。
  String defaultType = KeyStore.getDefaultType();
  KeyStore keyStore = KeyStore.getInstance(defaultType);
  keyStore.load( null, null);// 輸入流和密碼都為 null
  將服務(wù)器自簽名證書(shū)生成的X.509 證書(shū)添加進(jìn) KeyStore
   keyStore.setCertificateEntry("certificate",certificate);
   3、用包含服務(wù)器證書(shū)的KeyStore生成一個(gè)TrustManager
  這種方式生成的TrustManager 里面包含服務(wù)器證書(shū)強(qiáng)校驗(yàn)機(jī)制,也就是說(shuō)以這種方式生成的 TrustManager 都將使用強(qiáng)校驗(yàn)機(jī)制,服務(wù)器返回的證書(shū)必須匹配。
   String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm();// 獲取默認(rèn)的 TrustManagerFactory 算法名稱
  TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(defaultAlgorithm);
  trustManagerFactory.init(keyStore);
   4、通過(guò)TrustManager獲取到一個(gè)包含信任服務(wù)器自簽名證書(shū)的SSLContext
  SSLContext sslContext = SSLContext.getInstance("TLS");
  sslContext.init( null,trustManagerFactory.getTrustManagers(), null);
  到這里就已經(jīng)到了證書(shū)校驗(yàn)的關(guān)鍵所在,SSLContext 包含證書(shū)校驗(yàn)的關(guān)鍵信息,它可以通過(guò)下面方法獲取到 HTTPS 請(qǐng)求的關(guān)鍵參數(shù) SSLSocketFactory 。
   SSLSocketFactory socketFactory = sslContext.getSocketFactory();
  如果你是使用OkHttp3.0 網(wǎng)絡(luò)框架,可以使用 sslSocketFactory() 方法將包含服務(wù)器證書(shū)校驗(yàn)的 SSLSocketFactory 傳入 OkHttp3.0 的配置參數(shù)中,在發(fā)起 HTTPS 前, OkHttp3.0 框架會(huì)去驗(yàn)證證書(shū)的合法性。
   OkHttpClient okHttpClient = new OkHttpClient.Builder()
  .sslSocketFactory(socketFactory, (X509TrustManager) trustManagers[0])
  .hostnameVerifier(DO_NOT_VERIFY)
  .build();
  其他網(wǎng)絡(luò)框架同樣有類(lèi)似SSLSocketFactory 作為參數(shù)驗(yàn)證證書(shū)合法性的方法,可以自行 Google 相關(guān)的資料。
  以上證書(shū)校驗(yàn)流程僅僅是本地證書(shū)合法性校驗(yàn),服務(wù)器本地證書(shū)匹配校驗(yàn)過(guò)程是在HTTPS 請(qǐng)求發(fā)送后系統(tǒng)(其實(shí)是 TLS 協(xié)議)完成的,如果證書(shū)匹配不成功將自動(dòng)中斷網(wǎng)絡(luò)請(qǐng)求。正常情況下通過(guò)以上的校驗(yàn)已經(jīng)可以正常的進(jìn)行 HTTPS 通訊了,下面就開(kāi)始將一些非正常情況下出現(xiàn)的坑該怎么填。
   域名校驗(yàn)
  通常情況下正確的自簽名證書(shū)是不必重新域名校驗(yàn)的,需要重新域名校驗(yàn)的情況出現(xiàn)在服務(wù)器生成的證書(shū)沒(méi)有包括域名信息,或者干脆服務(wù)器沒(méi)有綁定域名,直接使用的是IP 訪問(wèn)方式。
  SSL 連接有兩個(gè)關(guān)鍵環(huán)節(jié)。
  首先是  驗(yàn)證證書(shū)是否來(lái)自值得信任的來(lái)源  ,這個(gè)環(huán)節(jié)自簽名證書(shū)可以通過(guò)自定義TrustManager 來(lái)解決,上面通過(guò)流的方式獲取的 TrustManager 從某種意義上來(lái)說(shuō)就是屬于自定義 TrustManager ,在這里多說(shuō)一句,如果是自定義 TrustManager ,強(qiáng)烈建議開(kāi)啟證書(shū)強(qiáng)驗(yàn)證,不要進(jìn)行弱驗(yàn)證。如果證書(shū)弱驗(yàn)證, SSL 握手環(huán)節(jié)無(wú)法成功交換秘鑰,數(shù)據(jù)其實(shí)還是明文傳輸?shù)?。以下是自定義 TrustManager 正確開(kāi)啟證書(shū)強(qiáng)校驗(yàn)的方式。
   private  class  PreTrustManager  implements  X509TrustManager {
  @Override
   public  void  checkClientTrusted(X509Certificate[] x509Certificates, String authType)  throws CertificateException {
  // 校驗(yàn)客戶端證書(shū),用于 HTTPS 雙向認(rèn)證
  }
  @Override
   public  void  checkServerTrusted(X509Certificate[] x509Certificates, String authType)  throws CertificateException {
  // 校驗(yàn)服務(wù)端證書(shū),實(shí)現(xiàn)強(qiáng)校驗(yàn),強(qiáng)烈不推薦弱校驗(yàn)
   if (x509Certificates ==  null) {
   throw  new IllegalArgumentException("Check Server X509Certificate[] is null");
  }
   if (x509Certificates.length < 0) {
   throw  new IllegalArgumentException("Check Server X509Certificate[] is empty");
  }
   for (X509Certificate x509Certificate : x509Certificates) {
  // 檢測(cè)服務(wù)端證書(shū)簽名時(shí)候有問(wèn)題
  x509Certificate.checkValidity();
   try {
  x509Certificate.verify(getX509Certificate().getPublicKey());
  catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
  catch (InvalidKeyException e) {
  e.printStackTrace();
  catch (NoSuchProviderException e) {
  e.printStackTrace();
  catch (SignatureException e) {
  e.printStackTrace();
  }
  }
  }
  @Override
   public X509Certificate[] getAcceptedIssuers() {
   return  new X509Certificate[0];
  }
  }
   private X509Certificate getX509Certificate(){
   try {
  InputStream inputStream = mContext.getAssets(). open("jetty_ssl.cer");
  CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
  X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
   return certificate;
  catch (IOException e) {
  e.printStackTrace();
  catch (CertificateException e) {
  e.printStackTrace();
  }
   return null;
  }
  其次  確保正在通信的服務(wù)器提供正確的證書(shū)  。如果沒(méi)有提供,通常會(huì)看到類(lèi)似于下面的異常信息:
   javax.net.ssl.SSLPeerUnverifiedException:  Hostname " 你服務(wù)器的域名 not  verified:
  出現(xiàn)此類(lèi)問(wèn)題的原因通常是由于服務(wù)器證書(shū)中配置的域名和客戶端請(qǐng)求的域名不一致所導(dǎo)致的。
  有兩種解決方案,一種是服務(wù)器用真實(shí)的域名重新生成正確的證書(shū),這種解決方式不做討論;還有一種是客戶端自定義HostnameVerifier ,添加域名校驗(yàn)白名單,同樣采用域名強(qiáng)校驗(yàn)方式。
   private HostnameVerifier TRUST_HOST_NAME =  new HostnameVerifier() {
  @Override
   public  boolean  verify(String hostname, SSLSession session) {
  //  設(shè)置接受的域名集合
   if (hostname.equals(" 服務(wù)器域名 "))  {
   return  true;
  }
   return  false;
  }
  };
  以下弱校驗(yàn)方式會(huì)導(dǎo)致安全隱患,強(qiáng)烈不推薦。
   private HostnameVerifier TRUST_HOST_NAME =  new HostnameVerifier() {
  @Override
   public  boolean  verify(String hostname, SSLSession session) {
   return  true;
  }
  };
   來(lái)源:簡(jiǎn)書(shū)
您還未登錄,請(qǐng)先登錄

熱門(mén)帖子

最新帖子

?