使用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所示:

如果在這個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