Android HTTPS之自簽名證書認證(一)

使用okhttp框架 單向認證

鑒于技術或者框架的不斷更新,網(wǎng)上介紹的Android客戶端訪問自簽名證書的網(wǎng)站的博客很多已無法使用,故本博文將詳細介紹一下Android SSL/TLS HTTPS請求。

開發(fā)環(huán)境:

  • JDK 1.8

  • Tomcat 8.0

  • IDE: Android Studio 2.3.3

1.1 生成自簽名證書

  • 生成server端證書庫
keytool -genkey -alias server -keyalg RSA -storetype PKCS12 -keysize 2048 -keystore E:/ssl/server.p12 -validity 3650 -storepass 123456 -keypass 123456

C:\Users\liting.wang>keytool -genkey -alias server -keyalg RSA -storetype PKCS12-keysize 2048 -keystore E:/ssl/server.p12 -validity 3650 -storepass 123456 -keypass 123456

您的名字與姓氏是什么?

[Unknown]: Liting Wang

您的組織單位名稱是什么?

[Unknown]: Onroad

您的組織名稱是什么?

[Unknown]: Onroad

您所在的城市或區(qū)域名稱是什么?

[Unknown]: Xiamen

您所在的省/市/自治區(qū)名稱是什么?

[Unknown]: Fujian

該單位的雙字母國家/地區(qū)代碼是什么?

[Unknown]: CN

CN=Liting Wang, OU=Onroad, O=Onroad, L=Xiamen, ST=Fujian, C=CN是否正確?

[否]: y

填入各類信息,也可以放空,最后輸入y, 即可在E:/ssl/目錄下生成server.p12證書庫

當然了,如果你覺得一個個的輸入比較麻煩,也可以用命令行的方式表達,如下

keytool -genkey -alias server -keyalg RSA -storetype PKCS12 -keysize 2048 -keystore E:/ssl/server.p12 -dname "CN=Liting Wang,OU=onroad,O=onroad,L=Xiamen,ST=Fujian,c=cn" -validity 3650 -storepass 123456 -keypass 123456

解釋:

-genkey:生成key

-alias:別名,獨一無二,通常不區(qū)分大小寫

-keyalg: 指定密鑰的算法(如 RSA, DSA(如果不指定默認采用DSA))

-storetype:指定密鑰倉庫類型,無指定默認為JKS,但oracle建議使用PKCS12

-keysize :指定證書大小

-keystore:指定密鑰庫的名稱,可指定路徑,例如:E:/ssl/ 需要注意的是運行該命令之前需要先創(chuàng)建好該目錄。

-validity:證書的有效期,單位:天。

  • 利用Server.p12來簽發(fā)證書
keytool -export -alias server -file E:/ssl/server.cer -keystore E:/ssl/server.p12 -storepass 123456 

生成的server.cer證書是給客戶端使用的。

1.2 Tomcat 配置

找到Tomcat安裝目錄的tomcat 8.0/conf/下找到server.xml配置文件,并以文本形式打開

在Service標簽中,加入:

<Connector SSLEnabled="true" acceptCount="100" clientAuth="false" 
    disableUploadTimeout="true" enableLookups="true" keystoreFile="conf/server.p12" keystorePass="123456" maxSpareThreads="75" 
    maxThreads="200" minSpareThreads="5" port="8443" 
    protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https" 
    secure="true" sslProtocol="TLS"
      /> 

單向認證:clientAuth須為false。keystoreFile的值為我們剛才生成的server.p12, 我們將其到conf/目錄下,keystorePass值為密鑰庫密碼:123456。

啟動Tomcat 8.0 service,(Server端代碼請參考https://github.com/onroadtech/SpringbootBase/tree/acc0324d5b38ec698c7c87e0d20f6fcb07f82454/)在瀏覽器地址欄里輸入https://localhost:8443/即可看到證書不可信任的警告,選擇繼續(xù)訪問, 如下圖1所示:

圖1

如果在這個Tomcat部署了項目,如上一篇的《Spring Boot之HTTPS配置》介紹的SpringbootBase項目,按如下的URL訪問http://ip:8443/SpringBootBase/進行訪問,得到不安全鏈接,選擇繼續(xù)訪問,得到的結果為:Hello world!

我們Android端就是要利用server端的證書server.cer訪問https://ip:8443/SpringBootBase/,如果也能得到上述結果,就可以說明我們的https單向認證成功。

1.3 Android 端開發(fā)

對于自簽名的網(wǎng)站的訪問,網(wǎng)上部分的做法是直接設置信任所有的證書,但這種做法肯定會存在安全隱患的,下面我們介紹如何使用okhttp框架讓OkHttpClient去信任我們自己簽名的證書

首先把第一節(jié)生成的server.cer放到assets文件夾下,其實你可以隨便放哪,反正能讀取到就行,然后在OkHttpClientManager里面添加如下的方法:

/******************************
 *  單向認證
******************************/
public void setOneWayCertificates(InputStream... certificates){
  try{
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null);
    int index = 0;
    for (InputStream certificate : certificates){
      String certificateAlias = Integer.toString(index++);
      keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));

      try{
        if (certificate != null)
          certificate.close();
      } catch (IOException e){
        Log.e("OkHttpClientManager", e.getMessage());
      }
    }

    SSLContext sslContext = SSLContext.getInstance("TLS");
    TrustManagerFactory trustManagerFactory =
      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

    trustManagerFactory.init(keyStore);
    sslContext.init(
      null,
      trustManagerFactory.getTrustManagers(),
      new SecureRandom());
    mOkHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
  } catch (Exception e){
    Log.e("OkHttpClientManager", e.getMessage());
  }
}

代碼內(nèi)部,我們:

  • 構造CertificateFactory對象,通過它的generateCertificate(is)方法得到Certificate。
  • 然后將得到的Certificate放入到keyStore中。
  • 接下來利用keyStore去初始化我們的TrustManagerFactory
  • 由trustManagerFactory.getTrustManagers獲得TrustManager[]初始化我們的SSLContext
  • 最后,設置我們mOkHttpClient.setSslSocketFactory即可。

這樣就完成了我們代碼的編寫,當客戶端進行SSL連接時,就可以根據(jù)我們設置的證書去決定是否該服務端網(wǎng)站是不是證書所俯的網(wǎng)址。

記得在Application中進行初始化:

public class Android4HttpsApplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        try {
            //單向認證
            OkHttpClientManager.getInstance()
                    .setOneWayCertificates(getAssets().open("server.cer"));
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

然后嘗試以下代碼訪問我們自己搭建的服務器:

private void postTest() {
    OkHttpClientManager.getAsyn("https://192.168.0.101:8443/SpringBootBase/", new OkHttpClientManager.ResultCallback<String>() {

        @Override
        public void onError(com.squareup.okhttp.Request request, Exception e) {
            Log.e(TAG, e.getMessage());
        }

        @Override
        public void onResponse(String u) {
            Log.d(TAG,"Response is " + u);
        }
    });
}

運行,發(fā)現(xiàn)報如下錯誤

12-11 13:32:18.092 22181-22181/? E/MainActivity: Hostname 192.168.0.101 not verified:

? certificate: sha1/iY0cn+EnYvx3fnRugIsLx4sFJEE=

? DN: CN=Liting Wang,OU=onroad,O=onroad,L=Xiamen,ST=Fujian,C=cn

? subjectAltNames: []

出現(xiàn)這個錯誤的原因是:如果請求的主機是一個IP, 而“CN”又沒有去匹配這個IP地址,就會報這個錯誤。所以一般"CN"填寫的是主機綁定的域名,而不是keytool提示的名和姓。但是如果我們沒有申請域名或者我們的主機外網(wǎng)無法訪問,那應該如何處理。其實keytool還有另外一個參數(shù)- ext SAN=IP,可以用于指定訪問的主機IP地址,當然了也可以指定域名-ext SAN=dns:xxxxxxxx,ip:xxx.xxx.xxx.xxx

重新用如下命令生成server端證書庫及導出證書

keytool -genkey -alias server -keyalg RSA -storetype PKCS12 -keysize 2048 -keystore E:/ssl/server.p12 -dname "CN=Liting Wang,OU=onroad,O=onroad,L=Xiamen,ST=Fujian,c=cn" -validity 3650 -storepass 123456 -keypass 123456 -ext SAN=IP:192.168.0.101
keytool -export -alias server -file E:/ssl/server.cer -keystore E:/ssl/server.p12 -storepass 123456

將生成的server.p12及server.cer重新分別復制到Tomcat conf目錄下及Android project的Assets目錄下,重啟Tomcat service及運行android app, 即可得到如下log

12-12 02:18:06.704 3407-3407/? D/MainActivity: Response is Hello world!

說明我們的https單向驗證成功。

參考:


完整代碼可到我的github下載:
server: https://github.com/onroadtech/SpringbootBase/tree/acc0324d5b38ec698c7c87e0d20f6fcb07f82454/
android:https://github.com/onroadtech/Android4HTTPS/tree/https_one_way_certification


本博文已同步發(fā)表于我的個人博客網(wǎng)站,歡迎轉載指正并注明出處。
個人博客: www.onroad.tech
指正郵箱: onroad_tech@163.com

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容