Android HTTPS 自制證書實現雙向認證(OkHttp + Retrofit + Rxjava)
由于最近要做一個安全性比較高的項目,因此需要用到HTTPS進行雙向認證。由于設計項目架構的時候,客戶端是采用MVVM架構,基于DataBinding + Retrofit + Rxjava來實現Android端。
查閱很多資料,基于原生HttpClient實現雙向認證的例子很多,但對于Retrofit的資料網上還是比較少,官方文檔也是一句帶過,沒有具體的介紹。
科普一下,什么是HTTPS?
簡單來說, HTTPS 就是 “安全版” 的 HTTP , HTTPS = HTTP + SSL 。HTTPS相當于在應用層和TCP層之間加入了一個SSL(或TLS),SSL層對從應用層收到的數據進行加密。 TLS/SSL中使用了RSA非對稱加密,對稱加密以及HASH算法 。
RSA算法基于一個十分簡單的數論事實:將兩個大素數相乘十分容易,但那時想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。
SSL:(Secure Socket Layer,安全套接字層),為Netscape所研發,用以保障在Internet上數據傳輸之安全,利用數據加密(Encryption)技術,可確保數據在網絡上之傳輸過程中不會被截取。它已被廣泛地用于Web瀏覽器與服務器之間的身份認證和加密數據傳輸。SSL協議位于TCP/IP協議與各種應用層協議之間,為數據通訊提供安全支持。
SSL協議可分為兩層:
SSL記錄協議(SSL Record Protocol):它建立在可靠的傳輸協議(如TCP)之上,為高層協議提供數據封裝、壓縮、加密等基本功能的支持。
SSL握手協議(SSL Handshake Protocol):它建立在SSL記錄協議之上,用于在實際的數據傳輸開始前,通訊雙方進行身份認證、協商加密算法、交換加密密鑰等。
TLS:(Transport Layer Security,傳輸層安全協議),用于兩個應用程序之間提供保密性和數據完整性。TLS 1.0是IETF(Internet Engineering Task Force,Internet工程任務組)制定的一種新的協議,它建立在SSL 3.0協議規范之上,是SSL 3.0的后續版本,可以理解為SSL 3.1,它是寫入了 RFC的。
該協議由兩層組成: TLS 記錄協議(TLS Record)和 TLS 握手協議(TLS Handshake)。
TLS處于的位置
進入正文
基于Retrofit實現HTTPS思路
由于Retrofit是基于OkHttp實現的,因此想通過Retrofit實現HTTPS需要給Retrofit設置一個OkHttp代理對象用于處理HTTPS的握手過程。代理代碼如下:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(SSLHelper.getSSLCertifcation(context))//為OkHttp對象設置SocketFactory用于雙向認證
.hostnameVerifier(new UnSafeHostnameVerifier())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://10.2.8.56:8443")
.addConverterFactory(GsonConverterFactory.create())//添加 json 轉換器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加 RxJava 適配器
.client(okHttpClient)//添加OkHttp代理對象
.build();
證書制作思路:
首先對于雙向證書驗證,也就是說,
客戶端持有服務端的公鑰證書,并持有自己的私鑰, 服務端持有客戶的公鑰證書,并持有自己私鑰 ,
建立連接的時候,客戶端利用服務端的公鑰證書來驗證服務器是否上是目標服務器;服務端利用客戶端的公鑰來驗證客戶端是否是目標客戶端。( 請參考RSA非對稱加密以及HASH校驗算法 )
服務端給客戶端發送數據時,需要將服務端的證書發給客戶端驗證,驗證通過才運行發送數據,同樣,客戶端請求服務器數據時,也需要將自己的證書發給服務端驗證,通過才允許執行請求。
下面我畫了一個圖,來幫助大家來理解雙向認證的過程,證書生成流程,以及各個文件的作用,大家可以對照具體步驟來看
相關格式說明
JKS: 數字證書庫。 JKS里有KeyEntry和CertEntry,在庫里的每個Entry都是靠別名(alias)來識別的。
P12: 是PKCS12的縮寫。同樣是一個 存儲私鑰的證書庫 ,由 .jks 文件導出的,用戶在PC平臺安裝, 用于標示用戶的身份 。
CER: 俗稱數字證書, 目的就是用于存儲公鑰證書 ,任何人都可以獲取這個文件 。
BKS: 由于Android平臺不識別 .keystore 和 .jks 格式的證書庫文件,因此Android平臺引入一種的證書庫格式,BKS。
有些人可能有疑問,為什么Tomcat只有一個server.keystore文件,而客戶端需要兩個庫文件?
因為有時客戶端可能需要訪問過個服務,而服務器的證書都不相同,因此客戶端需要制作一個 truststore 來存儲受信任的服務器的證書列表。因此為了規范創建一個 truststore.jks 用于存儲受信任的服務器證書,創建一個 client.jks 來存儲客戶端自己的私鑰。對于只涉及與一個服務端進行雙向認證的應用,將 server.cer 導入到 client.jks 中也可。
具體步驟如下:
1.生成客戶端keystore
keytool -genkeypair -alias client -keyalg RSA -validity 3650 -keypass 123456 -storepass 123456 -keystore client.jks
2.生成服務端keystore
keytool -genkeypair -alias server -keyalg RSA -validity 3650 -keypass 123456 -storepass 123456 -keystore server.keystore
//注意:CN必須與IP地址匹配,否則需要修改host
3.導出客戶端證書
keytool -export -alias client -file client.cer -keystore client.jks -storepass 123456
4.導出服務端證書
keytool -export -alias server -file server.cer -keystore server.keystore -storepass 123456
5.重點:證書交換
將客戶端證書導入服務端keystore中,再將服務端證書導入客戶端keystore中, 一個keystore可以導入多個證書,生成證書列表。
生成客戶端信任證書庫(由服務端證書生成的證書庫):
keytool -import -v -alias server -file server.cer -keystore truststore.jks -storepass 123456
將客戶端證書導入到服務器證書庫(使得服務器信任客戶端證書):
keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456
6.生成Android識別的BKS庫文件
用Portecle工具轉成bks格式,最新版本是1.10。
下載鏈接:https://sourceforge.net/projects/portecle/
運行protecle.jar將client.jks和truststore.jks分別轉換成client.bks和truststore.bks,然后放到android客戶端的assert目錄下
File -> open Keystore File -> 選擇證書庫文件 -> 輸入密碼 -> Tools -> change keystore type -> BKS -> save keystore as -> 保存即可
這個操作很簡單,如果不懂可自行百度。
我在Windows下生成BKS的時候會報錯失敗,后來我換到CentOS用OpenJDK1.7立馬成功了,如果在這步失敗的同學可以換到Linux或Mac下操作,
將生成的BKS拷貝回Windows即可。
7.配置Tomcat服務器
修改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:指定服務器密鑰庫,可以配置成絕對路徑,本例中是在Tomcat目錄中創建了一個名為key的文件夾,僅供參考。
- keystorePass:密鑰庫生成時的密碼
- truststoreFile:受信任密鑰庫,和密鑰庫相同即可
- truststorePass:受信任密鑰庫密碼
8.Android App編寫BKS讀取創建證書自定義的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 {
// 服務器端需要驗證的客戶端證書,其實就是客戶端的keystore
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);// 客戶端信任的服務器端證書
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實例進行網絡訪問
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")//填寫自己服務器IP
.addConverterFactory(GsonConverterFactory.create())//添加 json 轉換器
.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
}
}
結束語
由于雙向認證涉及的原理知識太多,有些地方我也是一筆帶過,本文想著重介紹證書的制作以及應用。在此奉勸各位,如果不了解 RSA非對稱加密,對稱加密以及HASH校驗算法 的同學,最好還是先看書學習一下。
了解原理對于進步來說是十分有幫助的,網上的資料魚龍混雜,不了解原理的話你根本無從分辨網上文章的正誤。
來自:http://www.jianshu.com/p/64172ccfb73b