Android Https的安全使用
在典型的 HTTPS 使用场景中,会使用一个包含公钥及与其匹配的私钥的证书配置服务器。作为 SSL 客户端与服务器握手的一部分,服务器将通过使用公钥加密签署其证书来证明自己具有私钥。
不过,任何人都可以生成他们自己的证书和私钥,因此,一个简单的握手只能说明服务器知道与证书公钥匹配的私钥,除此之外什么都证明不了。解决此问题的一个方法是让客户端拥有其信任的一个或多个证书集。如果证书不在此集合中,则不会信任服务器。
但这个简单的方法有几个缺点。服务器应能够随时间的推移升级到更强的**(“**旋转”),使用新的公钥替换证书中的公钥。遗憾的是,客户端应用现在必须根据服务器配置发生的变化进行更新。如果服务器不在应用开发者的控制下(例如,如果服务器是一个第三方网络服务),则很容易出现问题。如果应用必须与网络浏览器或电子邮件应用等任意服务器通信,那么,此方法也会带来问题。
为弥补这些缺点,通常使用来自知名颁发者(证书颁发机构(CA))发放的证书配置服务器。主机平台一般包含其信任的知名 CA 的列表。从 Android 4.2 开始,Android 目前包含在每个版本中更新的 100 多个 CA。CA 具有一个证书和一个私钥,这点与服务器相似。为服务器发放证书时,CA 使用其私钥签署服务器证书。然后,客户端可以验证该服务器是否具有平台已知的 CA 发放的证书。
不过,在解决一些问题的同时,使用 CA 也会引发其他问题。因为 CA 为许多服务器发放证书,因此,您仍需要某种方式来确保您与您需要的服务器通信。为解决这个问题,CA 发放的证书通过类似 gmail.com 等具体名称或 *.google.com 等通配型主机集识别服务器。
以下示例会让这些概念更具体。下面的代码段来自命令行,openssl
工具的 s_client
命令将查看维基百科( Wikipedia) 的服务器证书信息。它指定端口 443,因为此端口是 HTTPS的默认端口。此命令将 openssl
的输出发送到
s_clientopenssl x509
,后者将根据 X.509 标准 格式化与证书有关的信息。具体而言,此命令获取subject和issuer信息,分别包含服务器名称信息 和 可认证 CA 的颁发结构。如下:
$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org
issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
你会看到证书是由 RapidSSL CA 为与 *.wikipedia.org 匹配的服务器发放的。
1. 访问知名CA发放证书的Https服务器
URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//校验服务器证书
if (chain == null) {
throw new IllegalArgumentException("Check Server X509Certificates is null");
}
if (chain.length < 0) {
throw new IllegalArgumentException("Check Server X509Certificates is empty");
}
for (X509Certificate cert : chain) {
cert.checkValidity();
try {
String certName = "abc.crt"; //一般将下载的证书放到项目中的assets目录下
InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate serverCert = (X509Certificate) certificateFactory.generateCertificate(certInput);
cert.verify(serverCert.getPublicKey());
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, null);
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//校验服务器证书的域名是否相符(若不校验服务器证书域名则直接return true)
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
Boolean result = hv.verify("*.xxx.com", session);
return result;
}
};
URL url = new URL(https_url);
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
httpsURLConnection.setHostnameVerifier(hostnameVerifier);
// httpsURLConnection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //信任所有主机(注意:此处SSLSocketFactory与sslContext.getSocketFactory()返回的是不同的类)
InputStream in = httpsURLConnection.getInputStream();
//网络返回的数据处理...
}
KeyStore
,然后用后者创建和初始化
TrustManager
。
TrustManager
是系统用于从服务器验证证书的工具,可以使用一个或多个 CA 从
KeyStore
创建,而创建的
TrustManager
将仅信任这些 CA。
如果是新的
TrustManager
,此示例将初始化一个新的
SSLContext
,后者可以提供一个
SSLSocketFactory
,您可以通过
HttpsURLConnection
用它来替换默认的
SSLSocketFactory
。这样一来,网络请求时将使用您的 CA 验证证书,
系统能够验证您的服务器证书是否来自值得信任的颁发者。
/**
* 获取校验服务器端证书的SocketFactory
*
* @param context
* @param certName 保存在assets路径下的服务器端证书文件名,如abc.crt
* @return
*/
public static javax.net.ssl.SSLSocketFactory getSocketFactory(Context context, String certName) throws IOException, CertificateException,
KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
//以X.509格式获取证书
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certificateFactory.generateCertificate(certInput);
//生成一个包含服务器端证书的KeyStore
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("cert", cert);
//用包含服务器端证书的KeyStore生成一个TrustManager
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
trustManagerFactory.init(keyStore);
//生成一个使用我们TrustManager的SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
public static void test(final Context context) {
new Thread() {
@Override
public void run() {
super.run();
String https_url = "https://www.xxx.com";
try {
URL url = new URL(https_url);
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setSSLSocketFactory(getSocketFactory(context, "abc.cer"));
InputStream in = httpsURLConnection.getInputStream();
...
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
}.start();
}
private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
X509TrustManager[] trustAllCerts = new X509TrustManager[]{
new 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 X509Certificate[0];
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, null);
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//不校验服务器证书的域名
return true;
}
};
OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(hostnameVerifier)
.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts[0]).build();
Request request = new Request.Builder().url(https_url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
public class MyWebViewClient extends WebViewClient {
...
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
}
转载请注明本文地址:Android Https的安全使用
在典型的 HTTPS 使用场景中,会使用一个包含公钥及与其匹配的私钥的证书配置服务器。作为 SSL 客户端与服务器握手的一部分,服务器将通过使用公钥加密签署其证书来证明自己具有私钥。
不过,任何人都可以生成他们自己的证书和私钥,因此,一个简单的握手只能说明服务器知道与证书公钥匹配的私钥,除此之外什么都证明不了。解决此问题的一个方法是让客户端拥有其信任的一个或多个证书集。如果证书不在此集合中,则不会信任服务器。
但这个简单的方法有几个缺点。服务器应能够随时间的推移升级到更强的**(“**旋转”),使用新的公钥替换证书中的公钥。遗憾的是,客户端应用现在必须根据服务器配置发生的变化进行更新。如果服务器不在应用开发者的控制下(例如,如果服务器是一个第三方网络服务),则很容易出现问题。如果应用必须与网络浏览器或电子邮件应用等任意服务器通信,那么,此方法也会带来问题。
为弥补这些缺点,通常使用来自知名颁发者(证书颁发机构(CA))发放的证书配置服务器。主机平台一般包含其信任的知名 CA 的列表。从 Android 4.2 开始,Android 目前包含在每个版本中更新的 100 多个 CA。CA 具有一个证书和一个私钥,这点与服务器相似。为服务器发放证书时,CA 使用其私钥签署服务器证书。然后,客户端可以验证该服务器是否具有平台已知的 CA 发放的证书。
不过,在解决一些问题的同时,使用 CA 也会引发其他问题。因为 CA 为许多服务器发放证书,因此,您仍需要某种方式来确保您与您需要的服务器通信。为解决这个问题,CA 发放的证书通过类似 gmail.com 等具体名称或 *.google.com 等通配型主机集识别服务器。
以下示例会让这些概念更具体。下面的代码段来自命令行,openssl
工具的 s_client
命令将查看维基百科( Wikipedia) 的服务器证书信息。它指定端口 443,因为此端口是 HTTPS的默认端口。此命令将 openssl
的输出发送到
s_clientopenssl x509
,后者将根据 X.509 标准 格式化与证书有关的信息。具体而言,此命令获取subject和issuer信息,分别包含服务器名称信息 和 可认证 CA 的颁发结构。如下:
$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org
issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
你会看到证书是由 RapidSSL CA 为与 *.wikipedia.org 匹配的服务器发放的。
1. 访问知名CA发放证书的Https服务器
URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//校验服务器证书
if (chain == null) {
throw new IllegalArgumentException("Check Server X509Certificates is null");
}
if (chain.length < 0) {
throw new IllegalArgumentException("Check Server X509Certificates is empty");
}
for (X509Certificate cert : chain) {
cert.checkValidity();
try {
String certName = "abc.crt"; //一般将下载的证书放到项目中的assets目录下
InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate serverCert = (X509Certificate) certificateFactory.generateCertificate(certInput);
cert.verify(serverCert.getPublicKey());
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, null);
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//校验服务器证书的域名是否相符(若不校验服务器证书域名则直接return true)
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
Boolean result = hv.verify("*.xxx.com", session);
return result;
}
};
URL url = new URL(https_url);
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
httpsURLConnection.setHostnameVerifier(hostnameVerifier);
// httpsURLConnection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //信任所有主机(注意:此处SSLSocketFactory与sslContext.getSocketFactory()返回的是不同的类)
InputStream in = httpsURLConnection.getInputStream();
//网络返回的数据处理...
}
KeyStore
,然后用后者创建和初始化
TrustManager
。
TrustManager
是系统用于从服务器验证证书的工具,可以使用一个或多个 CA 从
KeyStore
创建,而创建的
TrustManager
将仅信任这些 CA。
如果是新的
TrustManager
,此示例将初始化一个新的
SSLContext
,后者可以提供一个
SSLSocketFactory
,您可以通过
HttpsURLConnection
用它来替换默认的
SSLSocketFactory
。这样一来,网络请求时将使用您的 CA 验证证书,
系统能够验证您的服务器证书是否来自值得信任的颁发者。
/**
* 获取校验服务器端证书的SocketFactory
*
* @param context
* @param certName 保存在assets路径下的服务器端证书文件名,如abc.crt
* @return
*/
public static javax.net.ssl.SSLSocketFactory getSocketFactory(Context context, String certName) throws IOException, CertificateException,
KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
//以X.509格式获取证书
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certificateFactory.generateCertificate(certInput);
//生成一个包含服务器端证书的KeyStore
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("cert", cert);
//用包含服务器端证书的KeyStore生成一个TrustManager
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
trustManagerFactory.init(keyStore);
//生成一个使用我们TrustManager的SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
public static void test(final Context context) {
new Thread() {
@Override
public void run() {
super.run();
String https_url = "https://www.xxx.com";
try {
URL url = new URL(https_url);
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setSSLSocketFactory(getSocketFactory(context, "abc.cer"));
InputStream in = httpsURLConnection.getInputStream();
...
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
}.start();
}
private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
X509TrustManager[] trustAllCerts = new X509TrustManager[]{
new 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 X509Certificate[0];
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, null);
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//不校验服务器证书的域名
return true;
}
};
OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(hostnameVerifier)
.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts[0]).build();
Request request = new Request.Builder().url(https_url).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
public class MyWebViewClient extends WebViewClient {
...
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
}