1. HTTPS的定义
说道HTTPS,不得不提HTTP,HTTP最大的缺陷就是明文传输,数据传输过程中很容易被篡改,所以美国网景公司提出来HTTPS协议,相对HTTP,HTTPS多了一个S,这个S,其实就是SSL/TSL,SSL全称安全套接字层,TSL1.0(传输层安全协议)是SSL3.0的升级版,是用于服务器和客户端加密通信的,所以可以认为两者是同一种协议,SSL因为自身的不安全性,在Android8.0已经被弃用了,以上可以看出HTTPS=HTTP+SSL/TLS
2. 工作原理
1.HTTPS发起SSL连接,链接到服务器的443端口
2.服务器向客户端发送公钥和数字证书
3.客户端通过随机算法生成私钥,然后通过服务器公钥对该私钥加密,生成对称密钥
4.客户端向服务器发送对称密钥
5.服务器通过对称密钥对数据进行加密
6.客户端通过对称密钥来对数据解密
那问题来了,在第二步,如果有好事者截获了服务器对客户端发送的公钥,然后伪造成服务器与客户端通信,这可如何是好呢,如何判断该公钥是合法的呢,数字证书就排上用场了
3.数字证书
数字证书是由CA签发,全世界权威的CA一共100多个,数字证书里包含一对非对称密钥,公钥和私钥以及颁发给、颁发者等信息,里面的公钥对服务器端传输的公钥进行加密,生成密文,然后由客户端的数字证书里的私钥进行解密,从而获得服务器的公钥并确认该公钥是合法的
以浏览器的www.baidu.com为例,通过点击链接旁的锁标志来打开数字证书
证书路径展示的是证书链,根证书的权利最大,依次为根证书>A>B>C,如果www.baidu.com 信任了根证书,则意味着A、B、C都信任
通过详细信息可以看到公钥、公钥参数、证书策略等证书的详细信息
手机作为访问网络的客户端,当然也会有内置证书,以小米手机为例,通过设置-更多设置-系统安全-加密与凭据-信任的凭据来打开内置的CA证书
[外链图片转存失败(img-Xv296lYl-1569479579280)(index_files/7a2da499-fab1-49d4-b9b5-5005f368d556.png)]
系统证书是内置的,个人证书是自定义的,比如charles的抓包证书是放在个人证书下面的,那Android是如何发起HTTPS请求的呢?
4.抓包的原理
平常使用的抓包工具,无论是fidder和charles都能解析客户端和服务器的HTTPS数据,是如何做到的呢?其实抓包工具就充当了一个中间人代理的角色,参照2.https的工具原理,抓包的工作原理如下:
- 截获客户端向发起的HTTPS请求,佯装客户端,向真实的服务器发起请求
- 截获真实服务器的返回,佯装真实服务器,向客户端发送数据
- 获取了用来加密服务器公钥的非对称秘钥和用来加密数据的对称秘钥
5.Android 中使用 HTTPS以及如何防止抓包
Android中如何访问HTTPS呢,其实Retrofit、OkHttp均支持HTTPS的访问
项目中引入网络库,以**implementation ‘com.squareup.okhttp3:okhttp:4.2.0’**为例,
final OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
final Request request = new Request.Builder()
.url("https://www.baidu.com/robots.txt")
.build();
final Response execute = okHttpClient.newCall(request).execute();
final String bodyStr = execute.body().string();
Log.d(TAG, bodyStr);
那如果关闭客户端的CA证书,GlobalSign Root CA-R1,相当于不信任百度服务器的数字证书,会导致报错
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
通过手动将GlobalSign Root CA-R1.cer
放入项目中的assets文件夹,则可避免这一错误,如何引用项目中集成的证书呢?
通过
SSLContext sslContext;
try {
InputStream inputStream = getAssets().open("");
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{OkhttpU.trustManagerForCertificates(inputStream)}, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient okHttpClient = new OkHttpClient.Builder().sslSocketFactory(sslSocketFactory, OkhttpU.trustManagerForCertificates(inputStream)).build();
final Request request = new Request.Builder()
.url("https://www.baidu.com/robots.txt")
.build();
final Response execute = okHttpClient.newCall(request).execute();
final String bodyStr = execute.body().string();
Log.d(TAG, bodyStr);
} catch (Exception e) {
e.printStackTrace();
}
public class OkhttpU {
public static X509TrustManager trustManagerForCertificates(InputStream in)
throws GeneralSecurityException {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
if (certificates.isEmpty()) {
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
}
// Put the certificates a key store.
char[] password = "password".toCharArray(); // Any password will work.
KeyStore keyStore = newEmptyKeyStore(password);
int index = 0;
for (Certificate certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificate);
}
// Use it to build an X509 trust manager.
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
}
private static KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream in = null; // By convention, 'null' creates an empty key store.
keyStore.load(in, password);
return keyStore;
} catch (IOException e) {
throw new AssertionError(e);
}
}
}
就可以正常访问https了
综上,通过引入自定义证书,然后给OkHttp设置sslSocketFactory可以有效的防止抓包,但是.cer放到assets下很容易被反编译,可以通过jdk下的命令keytool -printcert -rfc -file srca.cer导出字符串,然后通过
OkHttpClientManager.getInstance()
.setCertificates(new Buffer()
.writeUtf8(CER_STRING) //CER_STRING是到处的string常量
.inputStream());