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