Android中Https通信客户端单向认证校验防止中间人攻击

引入HTTPS是为了解决HTTP所带来的三个问题:

  • HTTP是明文传输,数据容易被窃取,因此要加密数据以防止数据中途窃取
  • 认证服务器身份,确保数据发送到正确的服务器
  • 维护数据的完整性,防止数据在传输中被改变,如中间人攻击

所以本章主要是讲述如何使用OkHttpHttpUrlConnection来实现自制证书的访问

设置证书校验

绕过证书访问是不安全的访问方式,容易受到中间人攻击,所以客户端需要做双向证书校验,来保证客户端的合法性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static SSLSocketFactory setCertificates(InputStream... certificates) {
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));
if (certificate != null) {
certificate.close();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

设置域名校验

域名校验需要自己设置一个trustHosts数组,当前访问域名匹配这个数组中的一条,即可返回true,否则返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//获取HostnameVerifier
public static HostnameVerifier getHostnameVerifier(final JSONArray trustHosts) {
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String host, SSLSession sslSession) {
if (trustHosts == null || trustHosts.length() == 0) {
return false;
}
try {
for (int i = 0; i < trustHosts.length(); i++) {
String trustHost = trustHosts.getString(i);
if (host.equalsIgnoreCase(trustHost)) {
return true;
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return false;
}
};
return hostnameVerifier;
}

调用接口

1
2
3
4
5
6
7
8
9
//获取这个SSLSocketFactory
public static SSLSocketFactory getSSLSocketFactory(InputStream cerIn) {
try {
SSLSocketFactory factory = setCertificates(cerIn);
return factory;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

OkHttp设置信任证书

1
2
3
4
5
6
JSONArray trustHosts = new JSONArray();
jsonArray.put("192.168.1.11");
jsonArray.put("192.168.1.12");
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(SSLSocketCert.getSSLSocketFactory(context.getAssets().open("jetty.crt")));
builder.hostnameVerifier(SSLSocketCert.getHostnameVerifier(trustHosts));

HttpURLConnection设置信任证书

1
2
3
4
5
6
7
JSONArray trustHosts = new JSONArray();
jsonArray.put("192.168.1.11");
jsonArray.put("192.168.1.12");
URL url = new URL(fileUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
((HttpsURLConnection) conn).setSSLSocketFactory(SSLSocketCert.getSSLSocketFactory(context.getAssets().open("jetty.crt")));
((HttpsURLConnection) conn).setHostnameVerifier(SSLSocketCert.getHostnameVerifier(trustHosts));

使用证书字符串

以上是将证书放到Android工程的assets下,我们也可以将证书的内容导出来放到程序中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private String CER_STR = "-----BEGIN CERTIFICATE-----\n" +
"MIICmjCCAgOgAwIBAgIIbyZr5/jKH6QwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ04xKTAn\n" +
"BgNVBAoTIFNpbm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRTUkNBMB4X\n" +
"DTA5MDUyNTA2NTYwMFoXDTI5MDUyMDA2NTYwMFowRzELMAkGA1UEBhMCQ04xKTAnBgNVBAoTIFNp\n" +
"bm9yYWlsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRTUkNBMIGfMA0GCSqGSIb3\n" +
"DQEBAQUAA4GNADCBiQKBgQDMpbNeb34p0GvLkZ6t72/OOba4mX2K/eZRWFfnuk8e5jKDH+9BgCb2\n" +
"9bSotqPqTbxXWPxIOz8EjyUO3bfR5pQ8ovNTOlks2rS5BdMhoi4sUjCKi5ELiqtyww/XgY5iFqv6\n" +
"D4Pw9QvOUcdRVSbPWo1DwMmH75It6pk/rARIFHEjWwIDAQABo4GOMIGLMB8GA1UdIwQYMBaAFHle\n" +
"tne34lKDQ+3HUYhMY4UsAENYMAwGA1UdEwQFMAMBAf8wLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDov\n" +
"LzE5Mi4xNjguOS4xNDkvY3JsMS5jcmwwCwYDVR0PBAQDAgH+MB0GA1UdDgQWBBR5XrZ3t+JSg0Pt\n" +
"x1GITGOFLABDWDANBgkqhkiG9w0BAQUFAAOBgQDGrAm2U/of1LbOnG2bnnQtgcVaBXiVJF8LKPaV\n" +
"23XQ96HU8xfgSZMJS6U00WHAI7zp0q208RSUft9wDq9ee///VOhzR6Tebg9QfyPSohkBrhXQenvQ\n" +
"og555S+C3eJAAVeNCTeMS3N/M5hzBRJAoffn3qoYdAO1Q8bTguOi+2849A==\n" +
"-----END CERTIFICATE-----";

然后再调用

1
2
3
4
5
6
JSONArray trustHosts= new JSONArray();
jsonArray.put("192.168.1.11");
jsonArray.put("192.168.1.12");
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(SSLSocketCert.getSSLSocketFactory(new Buffer().writeUtf8(CER_STR).inputStream()));
builder.hostnameVerifier(SSLSocketCert.getHostnameVerifier(trustHosts));

SSLSocketCert.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import android.net.http.SslCertificate;
import android.os.Bundle;

import org.json.JSONArray;
import org.json.JSONException;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

public class SSLSocketCert {

//获取这个SSLSocketFactory
public static SSLSocketFactory getSSLSocketFactory(InputStream cerIn) {
try {
SSLSocketFactory factory = setCertificates(cerIn);
return factory;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 载入证书
*/
private static SSLSocketFactory setCertificates(InputStream... certificates) {
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));
if (certificate != null) {
certificate.close();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

//获取HostnameVerifier
public static HostnameVerifier getHostnameVerifier(final JSONArray trustHosts) {
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String host, SSLSession sslSession) {
if (trustHosts == null || trustHosts.length() == 0) {
return false;
}
try {
for (int i = 0; i < trustHosts.length(); i++) {
String trustHost = trustHosts.getString(i);
if (host.equalsIgnoreCase(trustHost)) {
return true;
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return false;
}
};
return hostnameVerifier;
}

/**
* SSL证书错误,手动校验https证书
*
* @param cert https证书
* @return true通过,false失败
*/
public static String getSSLCertFromServer(SslCertificate cert) {
Bundle bundle = SslCertificate.saveState(cert);
if (bundle != null) {
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes != null) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(new ByteArrayInputStream(bytes));
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] key = sha256.digest(ca.getEncoded());
return bytesToHex(key);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}

public static String getSSLCertSHA256FromCert(InputStream cerIn) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(cerIn);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] key = sha256.digest(ca.getEncoded());
String sha256Str = bytesToHex(key);
return sha256Str;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

/**
* 十六进制转字节数组
*
* @param hex
* @return
*/
public static byte[] hexToBinary(String hex) {
String string = "0123456789ABCDEF";
char[] ch = hex.toCharArray();
int len = ch.length / 2;
byte byts[] = new byte[len];
for (int i = 0; i < len; i++) {
byts[i] = (byte) ((((byte) string.indexOf(Character
.toUpperCase(ch[i * 2])) & 0xFF) << 4)
| ((byte) string.indexOf(Character.toUpperCase(ch[i * 2 + 1])) & 0xFF));
}
return byts;
}

/**
* 字节数组转十六进制字符串
*
* @param bytes
* @return
*/
public static String bytesToHex(byte[] bytes) {
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :