Android HTTPS 自制證書實現(xiàn)雙向認(rèn)證(OkHttp + Retrofit + Rxjava)

由于最近要做一個安全性比較高的項目,因此需要用到HTTPS進(jìn)行雙向認(rèn)證。由于設(shè)計項目架構(gòu)的時候,客戶端是采用MVVM架構(gòu),基于DataBinding + Retrofit + Rxjava來實現(xiàn)Android端。

查閱很多資料,基于原生HttpClient實現(xiàn)雙向認(rèn)證的例子很多,但對于Retrofit的資料網(wǎng)上還是比較少,官方文檔也是一句帶過,沒有具體的介紹。

看了 《Android中https請求的單向認(rèn)證和雙向認(rèn)證》,給了我很大的啟發(fā),于是嘗試著博主的方式制作證書,再次嘗試的時候果然成功了。

科普一下,什么是HTTPS?

簡單來說,HTTPS就是“安全版”HTTP, HTTPS = HTTP + SSL。HTTPS相當(dāng)于在應(yīng)用層和TCP層之間加入了一個SSL(或TLS),SSL層對從應(yīng)用層收到的數(shù)據(jù)進(jìn)行加密。TLS/SSL中使用了RSA非對稱加密,對稱加密以及HASH算法。

RSA算法基于一個十分簡單的數(shù)論事實:將兩個大素數(shù)相乘十分容易,但那時想要對其乘積進(jìn)行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。

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é)議可分為兩層:
SSL記錄協(xié)議(SSL Record Protocol):它建立在可靠的傳輸協(xié)議(如TCP)之上,為高層協(xié)議提供數(shù)據(jù)封裝、壓縮、加密等基本功能的支持。
SSL握手協(xié)議(SSL Handshake Protocol):它建立在SSL記錄協(xié)議之上,用于在實際的數(shù)據(jù)傳輸開始前,通訊雙方進(jìn)行身份認(rèn)證、協(xié)商加密算法、交換加密密鑰等。

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)。

TLS處于的位置

進(jìn)入正文

基于Retrofit實現(xiàn)HTTPS思路

由于Retrofit是基于OkHttp實現(xiàn)的,因此想通過Retrofit實現(xiàn)HTTPS需要給Retrofit設(shè)置一個OkHttp代理對象用于處理HTTPS的握手過程。代理代碼如下:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .sslSocketFactory(SSLHelper.getSSLCertifcation(context))//為OkHttp對象設(shè)置SocketFactory用于雙向認(rèn)證
    .hostnameVerifier(new UnSafeHostnameVerifier())
    .build();
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://10.2.8.56:8443")
    .addConverterFactory(GsonConverterFactory.create())//添加 json 轉(zhuǎn)換器
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加 RxJava 適配器
    .client(okHttpClient)//添加OkHttp代理對象
    .build();

證書制作思路:

首先對于雙向證書驗證,也就是說,
客戶端持有服務(wù)端的公鑰證書,并持有自己的私鑰,服務(wù)端持有客戶的公鑰證書,并持有自己私鑰,
建立連接的時候,客戶端利用服務(wù)端的公鑰證書來驗證服務(wù)器是否上是目標(biāo)服務(wù)器;服務(wù)端利用客戶端的公鑰來驗證客戶端是否是目標(biāo)客戶端。(請參考RSA非對稱加密以及HASH校驗算法)**
服務(wù)端給客戶端發(fā)送數(shù)據(jù)時,需要將服務(wù)端的證書發(fā)給客戶端驗證,驗證通過才運(yùn)行發(fā)送數(shù)據(jù),同樣,客戶端請求服務(wù)器數(shù)據(jù)時,也需要將自己的證書發(fā)給服務(wù)端驗證,通過才允許執(zhí)行請求。

下面我畫了一個圖,來幫助大家來理解雙向認(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ù)端進(jìn)行雙向認(rèn)證的應(yīng)用,將server.cer導(dǎo)入到client.jks中也可。

具體步驟如下:

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.重點(diǎn):證書交換

將客戶端證書導(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/
運(yùn)行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實例進(jìn)行網(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
  }
}

結(jié)束語

由于雙向認(rèn)證涉及的原理知識太多,有些地方我也是一筆帶過,本文想著重介紹證書的制作以及應(yīng)用。在此奉勸各位,如果不了解RSA非對稱加密,對稱加密以及HASH校驗算法 的同學(xué),最好還是先看書學(xué)習(xí)一下。
了解原理對于進(jìn)步來說是十分有幫助的,網(wǎng)上的資料魚龍混雜,不了解原理的話你根本無從分辨網(wǎng)上文章的正誤。
(不過我這篇文章絕對是正確的雙向認(rèn)證,大家可以放心)

源碼地址:

GITHUB源碼下載
找不到包,或者版本不兼容的朋友可以參考一下demo。

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

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

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