1. 程式人生 > >Android HTTPS SSL雙向驗證

Android HTTPS SSL雙向驗證

一、HTTPS和HTTP的區別

1、https協議需要到ca申請證書,一般免費證書很少,需要交費。

2、http是超文字傳輸協議,資訊是明文傳輸,https 則是具有安全性的ssl加密傳輸協議。

3、http和https使用的是完全不同的連線方式,用的埠也不一樣,前者是80,後者是443。

4、http的連線很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網路協議,比http協議安全。

二、SSL功能

1)客戶對伺服器的身份認證:
SSL伺服器允許客戶的瀏覽器使用標準的公鑰加密技術和一些可靠的認證中心(CA)的證書,來確認伺服器的合法性。

2)伺服器對客戶的身份認證:
也可通過公鑰技術和證書進行認證,也可通過使用者名稱,password來認證。

3)建立伺服器與客戶之間安全的資料通道:
SSL要求客戶與伺服器之間的所有傳送的資料都被髮送端加密、接收端解密,同時還檢查資料的完整性。

SSL協議位於TCP/IP協議與各種應用層協議之間,為資料通訊提供安全支援。SSL協議可分為兩層:

SSL記錄協議(SSL Record Protocol):它建立在可靠的傳輸協議(如TCP)之上,為高層協議提供資料封裝、壓縮、加密等基本功能的支援。

SSL握手協議(SSL Handshake Protocol):它建立在SSL記錄協議之上,用於在實際的資料傳輸開始前,通訊雙方進行身份認證、協商加密演算法、交換加密金鑰等。

三、生成金鑰庫和證書

可參考以下金鑰生成指令碼,根據實際情況做必要的修改,其中需要注意的是:服務端的金鑰庫引數“CN”必須與服務端的IP地址相同,否則會報錯,客戶端的任意。

1、生成伺服器證書庫

keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore D:\ssl\server.keystore -dname "CN=127.0.0.1,OU=rongyiwang,O=rongyiwang,L=Shanghai,ST=Shanghai,c=cn" -storepass 123456 -keypass 123456

2、生成客戶端證書庫

keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype
PKCS12 -keystore D:\ssl\client.p12 -dname "CN=client,OU=rongyiwang,O=rongyiwang,L=Shanghai,ST=Shanghai,c=cn" -storepass 123456 -keypass 123456 3、從客戶端證書庫中匯出客戶端證書 keytool -export -v -alias client -keystore D:\ssl\client.p12 -storetype PKCS12 -storepass 123456 -rfc -file D:\ssl\client.cer 4、從伺服器證書庫中匯出伺服器證書 keytool -export -v -alias server -keystore D:\ssl\server.keystore -storepass 123456 -rfc -file D:\ssl\server.cer 5、生成客戶端信任證書庫(由服務端證書生成的證書庫) keytool -import -v -alias server -file D:\ssl\server.cer -keystore D:\ssl\client.truststore -storepass 123456 6、將客戶端證書匯入到伺服器證書庫(使得伺服器信任客戶端證書) keytool -import -v -alias client -file D:\ssl\client.cer -keystore D:\ssl\server.keystore -storepass 123456 7、檢視證書庫中的全部證書 keytool -list -keystore D:\ssl\server.keystore -storepass 123456
通過上面的步驟生成的證書,客戶端需要用到的是client.p12(客戶端證書,用於請求的時候給伺服器來驗證身份之用)和client.truststore(客戶端證書庫,用於驗證伺服器端身份,防止釣魚)這兩個檔案.

四、Android端SSL認證請求

        一般客戶端驗證SSL有兩種方式,一種是通過SSLSocketFactory方式建立,需要設定域名及埠號(適應於HttpClient請求方式),一種是通過SSLContext方式建立(適用於HttpsURLConnection請求方式).

1、下面給出SslSocketFactory方式進行SSL認證的客戶端程式碼

private static final String KEY_STORE_TYPE_BKS = "bks";//證書型別 固定值
    private static final String KEY_STORE_TYPE_P12 = "PKCS12";//證書型別 固定值

    private static final String KEY_STORE_CLIENT_PATH = "client.p12";//客戶端要給伺服器端認證的證書
    private static final String KEY_STORE_TRUST_PATH = "client.truststore";//客戶端驗證伺服器端的證書庫
    private static final String KEY_STORE_PASSWORD = "123456";// 客戶端證書密碼
    private static final String KEY_STORE_TRUST_PASSWORD = "123456";//客戶端證書庫密碼

    /**
     * 獲取SslSocketFactory
     *
     * @param context 上下文
     * @return SSLSocketFactory
     */
    public static SSLSocketFactory getSslSocketFactory(Context context) {
        try {
            // 伺服器端需要驗證的客戶端證書
            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
            // 客戶端信任的伺服器端證書
            KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);

            InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
            InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
            try {
                keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
                trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    ksIn.close();
                } catch (Exception ignore) {
                }
                try {
                    tsIn.close();
                } catch (Exception ignore) {
                }
            }
            return new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore);
        } catch (KeyManagementException | UnrecoverableKeyException | KeyStoreException | FileNotFoundException | NoSuchAlgorithmException | ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 獲取SSL認證需要的HttpClient
     *
     * @param context 上下文
     * @param port    埠號
     * @return HttpClient
     */
    public static HttpClient getSslSocketFactoryHttp(Context context, int port) {
        HttpClient httpsClient = new DefaultHttpClient();
        SSLSocketFactory sslSocketFactory = getSslSocketFactory(context);
        if (sslSocketFactory != null) {
            Scheme sch = new Scheme("https", sslSocketFactory, port);
            httpsClient.getConnectionManager().getSchemeRegistry().register(sch);
        }
        return httpsClient;
    }
2、下面給出SSLContext方式進行SSL認證的客戶端程式碼
private static final String KEY_STORE_TYPE_BKS = "bks";//證書型別 固定值
    private static final String KEY_STORE_TYPE_P12 = "PKCS12";//證書型別 固定值

    private static final String KEY_STORE_CLIENT_PATH = "client.p12";//客戶端要給伺服器端認證的證書
    private static final String KEY_STORE_TRUST_PATH = "client.truststore";//客戶端驗證伺服器端的證書庫
    private static final String KEY_STORE_PASSWORD = "123456";// 客戶端證書密碼
    private static final String KEY_STORE_TRUST_PASSWORD = "123456";//客戶端證書庫密碼
    
    /**
     * 獲取SSLContext
     *
     * @param context 上下文
     * @return SSLContext
     */
    private static SSLContext getSSLContext(Context context) {
        try {
            // 伺服器端需要驗證的客戶端證書
            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
            // 客戶端信任的伺服器端證書
            KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);

            InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
            InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
            try {
                keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
                trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    ksIn.close();
                } catch (Exception ignore) {
                }
                try {
                    tsIn.close();
                } catch (Exception ignore) {
                }
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
            keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
            return sslContext;
        } catch (Exception e) {
            Log.e("tag", e.getMessage(), e);
        }
        return null;
    }
    
    /**
     * 獲取SSL認證需要的HttpClient
     *
     * @param context 上下文
     * @return OkHttpClient
     */
    public static OkHttpClient getSSLContextHttp(Context context) {
        OkHttpClient client = new OkHttpClient();
        SSLContext sslContext = getSSLContext(context);
        if (sslContext != null) {
            client.setSslSocketFactory(sslContext.getSocketFactory());
        }
        return client;
    }
    
    /**
     * 獲取HttpsURLConnection
     *
     * @param context 上下文
     * @param url     連線url
     * @param method  請求方式
     * @return HttpsURLConnection
     */
    public static HttpsURLConnection getHttpsURLConnection(Context context, String url, String method) {
        URL u;
        HttpsURLConnection connection = null;
        try {
            SSLContext sslContext = getSSLContext(context);
            if (sslContext != null) {
                u = new URL(url);
                connection = (HttpsURLConnection) u.openConnection();
                connection.setRequestMethod(method);//"POST" "GET"
                connection.setDoOutput(true);
                connection.setDoInput(true);
                connection.setUseCaches(false);
                connection.setRequestProperty("Content-Type", "binary/octet-stream");
                connection.setSSLSocketFactory(sslContext.getSocketFactory());
                connection.setConnectTimeout(30000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }
這裡還沒有給出Webview進行HTTPS請求的方式,正在研究中,後續有了再來更新,這裡需要注意一下的就是SSLContext的預設埠是443埠,這點需要注意一下