再版: Android WebView は https での SSL 双方向認証を実装します

APP アプリケーションのセキュリティ レポートWebView に HTTPS 検証のリスクがない問題を解決します

1. 概要
1. はじめ
に Https は、Http のセキュアバージョンであり、SSL+Http プロトコルで構築され、暗号化通信と本人認証が可能なネットワークプロトコルであり、Http プロトコルよりも安全です。

ここでいうセキュリティとはSSLによるもので、SSLの機能は以下の通りです。

a. ユーザーとサーバーを認証して、データが正しいクライアントとサーバーに送信されることを確認します。(証明書の検証)
b. データを暗号化して、送信中のデータの盗難を防ぎます。(暗号化)
c. データの整合性を維持し、送信中にデータが変更されないようにします。(ダイジェスト アルゴリズム)
HTTPS では、データを送信する前にクライアントとサーバーの間でハンドシェイクが必要で、ハンドシェイクが完了した後にのみデータ送信プロセスが行われます。このハンドシェイクは、前述の SSL の最初の機能であり、ユーザーとサーバーを認証するものであり、この認証方法は証明書によって実装されます。

上記は Https の簡単な紹介にすぎません。より包括的かつ具体的な概要については、Baidu を参照してください。

2. この記事の焦点
この記事の焦点は、SSL 証明書の双方向認証、つまりクライアントとサーバーのシェイク ハンドを実現することです。

含む:

a. クライアント証明書とサーバー証明書を生成します。
b. Https をサポートするサーバーを構築します。
c. Webview の双方向の証明書認証を実現します。
2. 証明書の生成
この記事では自己署名証明書を使用していますが、当局が発行した証明書を使用してみてはいかがでしょうか。彼らはお金が欲しいからです!もちろん企業側が申請してくれるのがベストです。以下はサーバー側の自己署名証明書を生成します。

証明書を生成するにはどうすればよいですか? JDK には keytool ツールがあり、JDK がインストールされていれば証明書を生成できる非常にシンプルなツールです。コマンドラインに直接移動します。

keytool -genkey -alias zx_server -keyalg RSA -keystore D:\key\zx_server.jks -validity 3600 -storepass 123456

上記のコマンドを使用して、D ディスクの「key」フォルダーにサーバー側の証明書ストア ファイル zx_server.jks を生成します。そのキー ストアのパスワードは 123456 です。

次に、zx_server.jks 証明書ライブラリを使用して、クライアントが使用できるサーバー側の証明書を生成します。

keytool -export -alias zx_server -file D:\key\zx_server.cer -keystore D:\key\zx_server.jks -storepass 123456

公開キーを含むサーバー側証明書 zx_server.cer が生成されます。

3. Tomcat をダウンロードし、自己署名証明書を使用して
最初に Tomcat をダウンロードするように Https を設定します。この記事では Tomcat 7 を使用します。アドレス: http://tomcat.apache.org/download-70.cgi

圧縮パッケージを受け取ったら、tomcat/config/server.xml ファイルを見つけてテキスト形式で開きます。

Servcie タグの下に、次のタグを追加します。

<Connector SSLEnabled="true" 
			acceptCount="100" 
			disableUploadTimeout="true" 
			enableLookups="true" 
			keystoreFile="D:/key/zx_server.jks" 
			keystorePass="123456" 
			maxSpareThreads="75" 
			maxThreads="200" 
			minSpareThreads="5" port="8443" 
			protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https" 
			secure="true" 
			sslProtocol="TLS"
			clientAuth="false" /> 

注: keystoreFile は生成されたばかりの zx_server.jks ファイルのパス (ここに独自のパスを入力します)、keystorePass の値はキーストアのパスワード 123456 です。

これで、HTTPS をサポートする Tomcat サーバーが構成されました。これは非常に簡単です。tomcat/bin/startup.bat を見つけ、ダブルクリックしてサーバーを直接起動します。

起動に成功したら、ブラウザを開いて https://localhost:8443/ と入力すると、証明書が信頼されていないという警告が表示されますが、無視して直接入力すると、Tomcat のデフォルトのホームページが表示されます。

4 番目に、webview は双方向認証を実装します。
このステップがこの記事の焦点です。上記の手順により、https サーバーがセットアップされました。次に、双方向認証を実装する必要があります。つまり、クライアントにも「jks ファイル」があり、サーバーには「cer ファイル」が必要です。これに対応して、抽象的に言えば、サーバー側を認証するのはクライアント側です。

上記の手順により、zx_server.jks および zx_server.cer ファイルが生成されました。上記の証明書の生成方法に従って、zx_client.jks および zx_client.cer という名前の 2 つのクライアント ファイルを生成します。次に、クライアント証明書を構成します。

1. サーバーの設定 サーバー
の設定は非常に簡単で、上記の 3 番目の手順で Tomcat を設定するときに追加したタグにいくつかの属性を追加します

<Connector SSLEnabled="true" 
			acceptCount="100" 
			disableUploadTimeout="true" 
			enableLookups="true" 
			keystoreFile="D:/key/zx_server.jks" 
			keystorePass="123456" 
			maxSpareThreads="75" 
			maxThreads="200" 
			minSpareThreads="5" port="8443" 
			protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https" 
			secure="true" 
			sslProtocol="TLS"
			clientAuth="true"
			truststoreFile="D:/key/zx_client.cer" /> 

上記のラベルの残りの部分は変更されず、ラベルの最後で clientAuth のみが true に設定され、追加の属性 truststoreFile が追加されます。この属性は生成したばかりの zx_client.cer ファイルである必要がありますが、次の場合にエラーが報告されます。追加後にサーバーを起動します。次に、新しく生成された jks ファイルに zx_client.cer ファイルを追加します。
keytool -import -alias D:\key\zx_client -file D:\key\zx_client.cer -keystore D:\key\zx_client_for_sever.jks

次に、Server.xml ファイルを変更し、truststoreFile 属性の値を、生成されたばかりの zx_client_for_sever.jks ファイルに変更します。

<Connector //其它属性不变
			clientAuth="true"
			truststoreFile="D:/key/zx_client_for_sever.jks" /> 

このとき、tomcatを再起動し、ブラウザで先ほどのアドレスにアクセスすると、「localhostがログイン証明書を受け入れていないか、ログイン証明書の有効期限が切れている可能性があります。」と表示されます。

これでブラウザからサーバーにアクセスできなくなりましたので、次にAndroid側でアクセスできるように設定してみましょう。


2. Webview https 証明書の双方向認証を実装するようにアプリを構成し、一般的な実装のアイデアについて説明します。

a. コード内の証明書に対して信頼認証を実行します
b. WebViewClient の shouldInterceptRequest メソッドを書き換え、WebView のリクエスト リクエストをインターセプトし、HttpsUrlConnection が SSL を設定する SocketFactory を取得し、この HttpsUrlConnection を使用してデータをインターセプトし、新しい WebResourceResponse を WebView に返します。

上記の考え方に従って開始する前に問題を解決してください。Android プラットフォームは bks 形式の証明書ファイルのみを認識できるため、jks ファイルを変換する必要があります。

変換方法は? 多くの方法がありますが、ここでは portecle を使用する方法を 1 つ紹介します。ダウンロード アドレス: https://sourceforge.net/projects/portecle/?source=typ_redirect

ダウンロード後、ファイルを解凍し、portecle.jar ファイルを見つけてダブルクリックして実行します。手順は基本的に、変換する jks ファイルを選択 -> パスワードを入力 -> 変換する形式を選択 -> 保存ファイルの一般的な手順は次のとおりです。

a.jksファイルを選択します

b. パスワードを入力します

c. 変換形式を選択し、パスワードを再入力します

d. 変換が成功したことを示すメッセージが表示されるので、そのまま保存します。

手順は非常に簡単ですが、一般的な手順は引き続き掲載されています。

zx_client.bks ファイルが生成されます。次に、上記で生成された zx_server.cer ファイルを見つけて、これら 2 つのファイルを Android アセット フォルダーに配置します。

次に、WebViewClient を書き換えてこれら 2 つの証明書を導入し、コードを直接貼り付けます。

package com.zx.webview_ssl;
 
import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Build;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
 
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
 
public class SslWebViewClient extends WebViewClient {
    
    
 
    private SSLContext sslContext;
 
    public SslWebViewClient() {
    
    
        try {
    
    
            TrustManager[] trustManagers = prepareTrustManager(MyApplication.mContext.getResources().getAssets().open("zx_server.cer"));
            KeyManager[] keyManagers = prepareKeyManager(MyApplication.mContext.getResources().getAssets().open("zx_client.bks"), "123456");
            sslContext = SSLContext.getInstance("TLS");
            X509TrustManager trustManager = null;
            if (null != trustManagers){
    
    
                trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
            }else {
    
    
                trustManager = new UnSafeTrustManager();
            }
            sslContext.init(keyManagers, new TrustManager[]{
    
    trustManager}, new SecureRandom());
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (KeyManagementException e) {
    
    
            e.printStackTrace();
        }
    }
 
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
    
    
        return processRequest(Uri.parse(url));
    }
 
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    
    
        return processRequest(request.getUrl());
    }
 
    private WebResourceResponse processRequest(Uri uri) {
    
    
        try {
    
    
            //设置连接
            URL url = new URL(uri.toString());
            HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
            //为request设置SSL Socket Factory
            urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
 
            urlConnection.setHostnameVerifier(new HostnameVerifier() {
    
    
                @Override
                public boolean verify(String hostname, SSLSession session) {
    
    
                    return true;
                }
            });
 
            //获取请求的内容、contentType、encoding
            InputStream inputStream = urlConnection.getInputStream();
            String contentType = urlConnection.getContentType();
            String encoding = urlConnection.getContentEncoding();
            if (null != contentType){
    
    
                String mimeType = contentType;
                if (contentType.contains(";")){
    
    
                    mimeType = contentType.split(";")[0].trim();
                }
                //返回新的response
                return new WebResourceResponse(mimeType, encoding, inputStream);
            }
 
        } catch (MalformedURLException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
 
    private TrustManager[] prepareTrustManager(InputStream... certificates) {
    
    
        if (certificates == null || certificates.length <= 0){
    
    
            return null;
        }
        try {
    
    
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
    
    
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
    
    
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e){
    
    
 
                }
            }
            TrustManagerFactory trustManagerFactory = null;
            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            return trustManagers;
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (CertificateException e) {
    
    
            e.printStackTrace();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return null;
 
    }
 
    private KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
    
    
        try {
    
    
            if (bksFile == null || password == null){
    
    
                return null;
            }
            KeyStore clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bksFile, password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());
            return keyManagerFactory.getKeyManagers();
        } catch (KeyStoreException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
    
    
            e.printStackTrace();
        } catch (CertificateException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return null;
    }
 
    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
    
    
        for (TrustManager trustManager : trustManagers) {
    
    
            if (trustManager instanceof X509TrustManager) {
    
    
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }
 
    public static class MyTrustManager implements X509TrustManager{
    
    
        private X509TrustManager defaultTrustManager;
        private X509TrustManager localTrustManager;
 
        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
    
    
            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            var4.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }
 
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    
 
        }
 
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
    
            try {
    
    
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce) {
    
    
                localTrustManager.checkServerTrusted(chain, authType);
            }
        }
 
        @Override
        public X509Certificate[] getAcceptedIssuers() {
    
    
            return new X509Certificate[0];
        }
    }
 
    public static class UnSafeTrustManager implements X509TrustManager {
    
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
    
    
        }
 
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
    
    
        }
 
        @Override
        public X509Certificate[] getAcceptedIssuers() {
    
    
            return new java.security.cert.X509Certificate[]{
    
    };
        }
    }
 
 
}

コードは非常に長いです。核となる部分は、それぞれ 41 行目と 42 行目で呼び出される prepareTrustManager メソッドと prepareKeyManager メソッドであることを簡単に説明します。これら 2 つのメソッドは、それぞれ zx_client.cer 証明書と zx_client.bks 証明書を初期化し、2 つの証明書を実行します。接続を確立し、79 行目で HttpsUrlConnection の SSLSocketFactory を設定します。この SSLSocketFactory は、50 行目で初期化された SSLContext から取得されます。この時点のリクエストは安全であり、最終的に 98 行目で新しい応答を返します。このようにして、基本的には上記の考え方が実現され、WebView の SSL 双方向認証が実現されます。

最後に、携帯電話のランニング効果を見てみましょう。

Tomcat のホームページに正常にアクセスできたことがわかります。

5. 最後に、
一部の銀行アプリや金融アプリなど、双方向認証を必要とするアプリケーションはごく少数です。以上で、https での Webview の SSL 双方向認証を実現することができます。

ソースコード: https://github.com/xunzzz/WebView_SSL

参考:

http://www.cnblogs.com/devinzhang/archive/2012/02/28/2371631.html
http://blog.csdn.net/kpioneer123/article/details/51491739
http://blog.csdn.net/ lmj623565791/記事/詳細/48129405

おすすめ

転載: blog.csdn.net/u013290250/article/details/121379479