1. 程式人生 > >android Retrofit+OkHttp使用自制的證書實現https安全傳輸

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進行如下配置

 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後設置
okHttpBuilder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
必須放在okhttp例項化之前

然後接下來就是在okhttp中配置

.sslSocketFactory(getSocketFactory(BaseApp.getContext()))
下面是獲取SSLSocketFactory例項的關鍵程式碼
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上找答案,早晚會弄出來的,我當時也折騰了幾個小時吧!

希望對你有幫助,手寫不易,若是幫到你了 點個贊吧!謝謝大笑