Android中HTTPS通信客户端单向认证校验防止中间人攻击
引入HTTPS是为了解决HTTP所带来的三个问题:
- HTTP是明文传输,数据容易被窃取,因此要加密数据以防止数据中途窃取
- 认证服务器身份,确保数据发送到正确的服务器
- 维护数据的完整性,防止数据在传输中被改变,如中间人攻击
所以本章主要是讲述如何使用OkHttp
或HttpUrlConnection
来实现自制证书的访问
设置证书校验
绕过证书访问是不安全的访问方式,容易受到中间人攻击,所以客户端需要做双向证书校验,来保证客户端的合法性
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
//获取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;
}
调用接口
//获取这个SSLSocketFactory
public static SSLSocketFactory getSSLSocketFactory(InputStream cerIn) {
try {
SSLSocketFactory factory = setCertificates(cerIn);
return factory;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
OkHttp设置信任证书
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设置信任证书
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下,我们也可以将证书的内容导出来放到程序中
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-----";
然后再调用
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
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);
}
}
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/25/unidirectional-authentication-verification-for-https-communication-clients-in-android-to-prevent-man-in-the-middle-attacks/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
THE END
0
二维码
打赏
海报
Android中HTTPS通信客户端单向认证校验防止中间人攻击
引入HTTPS是为了解决HTTP所带来的三个问题:
HTTP是明文传输,数据容易被窃取,因此要加密数据以防止数据中途窃取
认证服务器身份,确保数据发送到正确的服务……
文章目录
关闭
共有 0 条评论