Android實現https項目實戰

相信很多客戶端開發人員特別是Android開發人員對https如何在程序中使用存有疑惑,項目中有的說“什麼都不用校驗校驗都在服務端”、“ios都不用校驗,系統自帶處理機制”……之類的,不管是使用Volley、OkHttp還是其他網絡框架,不做校驗或使用不安全校驗確實能是客戶端正常訪問https的接口服務地址,但是如此以來給app特別是Android版本的應用程序帶來了極大安全隱患。
網上查找解決方案,千篇一律,思路都說的很好,具體怎麼實現呢?
下面是個人對Android實現https配置的經驗及代碼,分享學習,僅做參考,有問題請留言。

客戶端https認證方式

  1. 信任所有——不做處理,沒有起到校驗作用,有風險
  2. 單向認證——自定義信任規則,校驗服務端證書
  3. 單向認證——使用預埋證書,校驗服務端證書(自簽名證書)
  4. 雙向認證——使用bks證書和密碼管理客戶端證書(雙向認證),使用預埋證書,校驗服務端證書(自簽名證書)

    網上查到大多數代碼類似如下,此種方式即爲不做校驗處理,風險很高:

 /**
     * 爲了解決客戶端不信任服務器數字證書的問題,網絡上大部分的解決方案都是讓客戶端不對證書做任何檢查,
     * 這是一種有很大安全漏洞的辦法
     */
    public static X509TrustManager UnSafeTrustManager = new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        //TODO 此處應該加入自定義的校驗規則
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    };

 // 創建TLS類型的SSLContext對象, that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{manager}, null);
//下面以OkHttp設置https爲例,其他網絡框架一樣設置setSslSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslContext.getSocketFactory());
OkHttpClient okHttpClient = builder.build();

下面試我的實現方式:單向認證——自定義信任規則,校驗服務端證書+使用預埋證書校驗服務端證書公鑰有效性

/**
     * 自定義校驗規則:1、校驗服務端證書有效性;2、使用本地預埋證書校驗服務端證書公鑰(需要提前把服務端cer證書放入Android功能的assets目錄下,此種方式要考慮都證書有效期,如果過期需要app端及時替換證書——更具自身情況可以去掉預埋證書key校驗代碼)
     */
    private class SafeTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            LogUtils.d("checkServerTrusted 校驗服務端證書");
            if (chain == null) {
                throw new IllegalArgumentException("Check server X509Certificate is null");
            }
            if (chain.length < 0) {
                throw new IllegalArgumentException("Check server X509Certificate is empty");
            }

            //讀取本地預埋服務端證書獲取其publicKey與服務中攜帶的key比對
            InputStream is_local = null;
            try {
                is_local = new BufferedInputStream(getAssets().open("替換爲自己的證書名稱.cer"));//提前把證書放入assets中
            } catch (IOException e) {
                e.printStackTrace();
                LogUtils.e(e.getMessage());
            }
            X509Certificate serverCert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is_local);

            for (X509Certificate certificate : chain) {
                // 檢查證書是否過期,簽名是否通過等
                certificate.checkValidity();
                // 校驗服務端證書公鑰,與app預埋證書公鑰作比對
                try {
                    LogUtils.i("publicKey==" + serverCert.getPublicKey());
                    certificate.verify(serverCert.getPublicKey());
                } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                } catch (InvalidKeyException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                } catch (NoSuchProviderException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                } catch (SignatureException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                }
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }//自定義trustManager完成

 // 創建TLS類型的SSLContext對象, that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{new SafeTrustManager()}, null);
//下面以OkHttp設置https爲例,其他網絡框架一樣設置setSslSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslContext.getSocketFactory());
OkHttpClient okHttpClient = builder.build();

如此就實現了自定義的trustManager校驗規則,還是需要注意預埋證書是有有效期限制的,請慎用,一旦證書失效會導致服務請求不通——建議app添加版本更新接口,而且該接口不要使用強校驗的https請求,使用普通請求,如果證書失效至少能保證版本更新接口正常訪問便於用戶更新新的apk版本。

另外,配置完校驗規則,如果使用阿里聚安全等加固檢測網站檢測經常會報https hostname校驗的高危漏洞,所以最好再添加對服務域名的強校驗,而不是採用信任所有地址的方式(builder.hostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);//此寫法等於沒有做校驗)

下面是我的校驗方式:

/**
     * 驗證服務端證書域名是否匹配——有可能與服務請求地址不一致
     */
    private class SafeHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            //注意此處有可能與服務請求地址域名不一致,請於申請服務端證書的人員確認申請域名
            LogUtils.d("校驗服務域名");
            return hostname.equals("*ver.zp.com");
//            return true;
        }
    }

//以OkHttp爲例設置方式——使用服務域名強校驗
 builder.hostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
        builder.hostnameVerifier(new SafeHostnameVerifier());

總結爲下:
客戶端實現https配置:添加自定義校驗規則、校驗服務域名

更多關於Android https的問題及HttpUrlConnection使用https方式 請移步:
http://blog.csdn.net/u011084603/article/details/73873677

關於webView中設置Https的方法可移步:http://www.cnblogs.com/liyiran/p/7011317.html

獲取證書兩種方法

  1. 服務器組直接給。如測試 12306 網站的時候,進入網頁,12306 會提供根證書的下載
  2. 通過網頁獲取。 Chrome 瀏覽器,按 F12 選擇 Security 選擇 Certificate Error 的 View
    certificate 在彈出的證書,選擇詳細信息
    在詳細信息頁面,點擊複製到文件,一路下一步,然後選擇保存證書的地方,就把證書成功導出來了。

證書的讀取
從 assert 中讀取文件
InputStream is = getAssets().open(“root.cer”);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章