android Retrofit+OkHttp使用自制的證書實現https安全傳輸
相信大夥在專案中多少都遇到網路安全傳輸的問題,比方說涉及金額方面的訂單、機密安全資料、再比方說使用友盟分享或三方登入中的appsecret(官方建議從後臺獲取),這些問題困擾我們不得不使用https,但是網際網路企業最看重的就是效率,公司可能會因為人力或者資金方面的原因申請CA可能需要有困難(稽核時間太久了),所以告訴大家在android開發中,如何使用自制的證書來實現密文傳輸。
至於如何生成自制證書在這裡我就不敘述了(你直接交給後臺的人去弄吧,本身就是他的活),他生成好後會給你兩個檔案 CA.p12、XXX.p12(xxx是客戶端的私鑰) 還有解開這兩個加密檔案的密碼KEY_STORE_TRUST_PASSWORD(CA的密碼)、KEY_STORE_PASSWORD(客戶端的私鑰密碼)。(注:p12是祕鑰加密後的一種格式,當然也可以是別的格式,不過程式碼會有些許調整)
使用Retrofit+OkHttp無外乎就幾個動作
1.例項化Retrofit initRetrofitClient(host);
2.例項化Okhttp initOkHttpClient(); 然後將其配置到Retrofit 中 builder.client(okHttpClient)
3.建立介面物件 retrofit.create(cls);
以上是常規的http格式,要實現https只需要在其中配置幾行語句就能實現
首先把生成的CA.p12、XXX.p12匯入到android studio的raw目錄中(如果沒有資料夾,右鍵建立)
然後對okHttp進行如下配置
在這裡有用的程式碼就是在初始化okhttpBuilder後設置private static void initRetrofitClient(String host) { initOkHttpClient(); Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") .create(); retrofit = new Retrofit.Builder() .baseUrl(host) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(okHttpClient) .build(); } private static void initOkHttpClient() { if (okHttpClient == null) { // 因為BaseUrl不同所以這裡Retrofit不為靜態,但是OkHttpClient配置是一樣的,靜態建立一次即可 cacheFile = new File(BaseApp.getContext().getCacheDir(), "HttpCache"); // 指定快取路徑 Cache cache = new Cache(cacheFile, 1024 * 1024 * 200); // 指定快取大小200Mb // 雲端響應頭攔截器,用來配置快取策略 okHttpBuilder = new OkHttpClient.Builder(); okHttpBuilder.hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); okHttpClient = okHttpBuilder .readTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .addNetworkInterceptor(new StethoInterceptor())//chrome工具除錯的中介軟體 .addInterceptor(new HttpCacheInterceptor()) .addInterceptor(new LoggingInterceptor()) .cache(cache) .sslSocketFactory(getSocketFactory(BaseApp.getContext())) .build(); } }
okHttpBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
必須放在okhttp例項化之前
然後接下來就是在okhttp中配置
下面是獲取SSLSocketFactory例項的關鍵程式碼.sslSocketFactory(getSocketFactory(BaseApp.getContext()))
private static final String KEY_STORE_TYPE_BKS = "bks";//證書型別
private static final String KEY_STORE_TYPE_P12 = "PKCS12";//證書型別
private static final String KEY_STORE_PASSWORD = "CLIENT_PASSWORD";//(客戶端證書密碼)
private static final String KEY_STORE_TRUST_PASSWORD = "CA_PASSWRORD";//授信證書密碼(CA機構證書密碼)
public static SSLSocketFactory getSocketFactory(Context context) {
InputStream trust_input = context.getResources().openRawResource(R.raw.ca);//CA機構授信證書
InputStream client_input = context.getResources().openRawResource(R.raw.tvfan);//客戶端證書
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
trustStore.load(trust_input, KEY_STORE_TRUST_PASSWORD.toCharArray());
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
keyStore.load(client_input, KEY_STORE_PASSWORD.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
SSLSocketFactory factory = sslContext.getSocketFactory();
return factory;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
trust_input.close();
client_input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
這樣就能簡單的實現https協議 ,但是這種自制的證書並不能實現絕對的安全傳輸(不然CA機構豈不喝西北風了)!
親測成功 Good Luck!
如果你還是沒有搞定
1,請先判斷後臺的https協議有沒有搭建好 這裡給大家一個程式碼測試伺服器是否搭建正確(不然搞了半天,發現不是自己的問題那可就苦逼了)
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
public class HTTPSTest {
private static final String CLIENT_KEY_STORE = "E:/DOC/HTTPS/MyFiles/userCenter/tvfan.p12";
private static final String CLIENT_TRUST_STORE = "E:/DOC/HTTPS/MyFiles/userCenter/ca.p12";
private static final String CLIENT_KEY_STORE_PASSWORD = "CLIENT_PASSWORD"; //客戶端的密碼
private static final String CLIENT_TRUST_KEY_STORE_PASSWORD = "CA_PASSWORD";//ca的密碼
public static void main(String args[]) throws Exception {
certTest1();
}
public static void certTest1() throws Exception {
String httpsURL = "https://192.168.17.88/userCenter/"; //記得把路徑換了
System.setProperty("javax.net.ssl.keyStore", CLIENT_KEY_STORE);
System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
System.setProperty("javax.net.ssl.keyStorePassword", CLIENT_KEY_STORE_PASSWORD);
System.setProperty("javax.net.ssl.trustStore", CLIENT_TRUST_STORE);
System.setProperty("javax.net.ssl.trustStoreType", "pkcs12");
System.setProperty("javax.net.ssl.trustStorePassword", CLIENT_TRUST_KEY_STORE_PASSWORD);
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection) myurl.openConnection();
con.setHostnameVerifier(hv);
InputStream ins = con.getInputStream();
InputStreamReader isr = new InputStreamReader(ins);
BufferedReader in = new BufferedReader(isr);
String inputLine = null;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
}
private static HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return urlHostName.equals(session.getPeerHost());
}
};
}
2.如果上面的程式碼還不能通過測試, 那請先檢查你的兩個p12格式的資料是否正確!祕鑰和公鑰太多,可能加密的時候搞混了也不一定!
遇到問題慢慢查,多在stack overflow上找答案,早晚會弄出來的,我當時也折騰了幾個小時吧!
希望對你有幫助,手寫不易,若是幫到你了 點個贊吧!謝謝