Android Https雙向驗(yàn)證

對于網(wǎng)絡(luò)安全,我們的重視程度遠(yuǎn)遠(yuǎn)及不上歐美等國家,部署Https的網(wǎng)站遠(yuǎn)遠(yuǎn)少于他們;但隨著互聯(lián)網(wǎng)不斷普及,國民對數(shù)據(jù)安全的需求也越來越高,Https逐漸在國內(nèi)普及開來。
最近公司項(xiàng)目也開始轉(zhuǎn)為Https,故簡單記錄一下Android Https的一些設(shè)置。

生成相關(guān)證書

  1. 從后臺那里拿到P12格式的證書文件,如:server.pfx;
  2. 生成服務(wù)器cer證書

安裝openssl

我采用的是直接下載安裝包,下載地址;也可以選擇下載源碼自己編譯openssl。

生成證書server.cer

安裝之后配置環(huán)境變量,用管理員權(quán)限打開或者使用win10的PowerShell,輸入

openssl pkcs12 -in server.pfx -out server.cer

然后輸入證書的密碼和PEM密碼,成功之后生成證書server.cer

生成客戶端信任證書庫client.truststore

由于安卓端的證書類型必須是BKS類型,需要這樣做:

  1. 下載這個jar:bcprov-ext-jdk15on-159.jar
  2. 將jar文件放在 Java 主目錄下的 jre/lib/ext目錄下
  3. 修改jre/lib/security/java.security這個文件:在List of providers 注釋的地方添加這一行security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider
  4. 然后重啟終端,輸入下面代碼生成client.truststore,密碼自己改:
keytool -import -v -alias server -file server.cer -keystore client.truststore -storepass 123456 -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider

如果不添加jar文件,會報Class not found錯誤。
但是,某些情況下(大概是證書的格式或來源不符合要求)還是會報錯:所輸入的不是 X.509 證書

證書格式轉(zhuǎn)換工具XCA

  1. 下載安裝:地址
  2. 打開程序,點(diǎn)擊:File>New DataBase新建庫文件,選擇保存位置,輸入密碼,即成功創(chuàng)建庫。
  3. 導(dǎo)入證書server.cer:點(diǎn)擊:Import>Certificate,選擇之前生成的證書server.cer,導(dǎo)入。
  4. 導(dǎo)出p7b格式證書:


    導(dǎo)出p7b格式證書1

選擇p7b格式,點(diǎn)擊ok導(dǎo)出xxxx.p7b的證書


導(dǎo)出p7b格式證書1
  1. 打開xxxx.p7b證書文件,找到證書,右鍵>所有任務(wù)>導(dǎo)出


    導(dǎo)出X.509格式證書
  2. 導(dǎo)出X.509格式證書,選擇之前server.cer文件覆蓋保存,確認(rèn)生成server.cer證書


    導(dǎo)出X.509格式證書

7.最后,重新執(zhí)行命令,會提示是否信任此證書? [否]:,輸入生成client.truststore:

keytool -import -v -alias server -file server.cer -keystore client.truststore -storepass 123456 -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider

Android端SSL認(rèn)證請求

我們需要兩個證書:

  1. server.pfx:客戶端證書,用于請求的時候給服務(wù)器來驗(yàn)證身份
  2. client.truststore:客戶端證書庫,用于驗(yàn)證服務(wù)器端身份,防止釣魚

SSLSocketFactory方式進(jìn)行SSL認(rèn)證

將兩個證書添加到android項(xiàng)目的assets目標(biāo)下面,建立SSL驗(yàn)證工具,代碼如下:

public class SSLHelper {
    private static final String KEY_STORE_TYPE_BKS = "bks";
    private static final String KEY_STORE_TYPE_P12 = "PKCS12";
    public static final String KEY_STORE_CLIENT_PATH = "server.pfx";//P12文件
    private static final String KEY_STORE_TRUST_PATH = "client.truststore";//truststore文件
    public static final String KEY_STORE_PASSWORD = "123456";//P12文件密碼
    private static final String KEY_STORE_TRUST_PASSWORD = "123456";//truststore文件密碼

    public static SSLSocketFactory getSSLSocketFactory(Context context) {
        SSLSocketFactory factory = null;

        try {
            // 服務(wù)器端需要驗(yàn)證的客戶端證書
            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
            // 客戶端信任的服務(wù)器端證書
            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 e) {
                    e.printStackTrace();
                }
                try {
                    tsIn.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //信任管理器
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
              TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);
            //密鑰管理器
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
            keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());
            //初始化SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(), 
                            trustManagerFactory.getTrustManagers(), null);
            factory =  sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        }

        return factory;
    }

}

OkHttpClient配置

OkHttpClient client = new OkHttpClient.Builder()
  .sslSocketFactory(SSLHelper.getSSLSocketFactory(getApplication()))

主機(jī)名驗(yàn)證

如果出現(xiàn)主機(jī)名驗(yàn)證錯誤hostname error,需要添加hostnameVerifier

OkHttpClient client = new OkHttpClient.Builder()
  .sslSocketFactory(SSLHelper.getSSLSocketFactory(getApplication()))
  .hostnameVerifier(new UnSafeHostnameVerifier());
public class UnSafeHostnameVerifier implements HostnameVerifier {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
}

至此,Https雙向驗(yàn)證的配置全部完成了。

本文基于筆者項(xiàng)目實(shí)踐,關(guān)于SSL認(rèn)證筆者還沒有了解透徹,如有錯誤,請多多指出。

參考

Android HTTPS SSL雙向驗(yàn)證

keytool 錯誤: java.lang.Exception: 所輸入的不是 X.509 證書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容