用了Https防抓包就真的安全了?带你领会其中奥秘!
HTTPS的定义
简而言之,HTTPS可以理解为HTTP+TLS,TLS是传输层加密协议,是HTTPS安全的核心,其前身是SSL 。通过对数据传输层进行加密,可以有效防止被第三方黑客、抓包软件截获后解密的行为,从而极大地保护了用户访问网络的安全性。
-
数据明文传输,易嗅探
-
数据完整性无验证,易篡改
-
网站身份无认证,易假冒
HTTPS工作原理
大家可能都听说过 HTTPS 协议之所以是安全的是因为 HTTPS 协议会对传输的数据进行加密,而加密过程是使用了非对称加密实现。
但其实,HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。
HTTPS 的整体过程分为证书验证和数据传输阶段,具体的交互过程如下:
QQ截图20220614214000.png证书验证阶段:
-
浏览器发起 HTTPS 请求。
-
服务端返回 HTTPS 证书。
-
客户端验证证书是否合法,如果不合法则提示告警。
数据传输阶段:
-
当证书验证合法后,在本地生成随机数。
-
通过公钥加密随机数,并把加密后的随机数传输到服务端。
-
服务端通过私钥对随机数进行解密。
-
服务端通过客户端传入的随机数构造对称加密算法,对返回结果内容进行加密后传输。
单向/双向验证
服务器是使用的HTTPS协议传输,并且买了权威机构的CA证书。但是客户端在发送请求时,未使用证书验证方式,这样就会存在中间人攻击的可能。所以说之前我的那种做法是不安全的。
单向验证
单向认证:保证server是真的,通道是安全的(对称密钥),校验证书,不可以抓包。
流程:
1.客户端向服务器端发送SSL协议版本号、加密算法种类、随机数等信息。
2.服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书。
3.客户端使用服务端返回的信息验证服务器的合法性,包括
-
证书是否过期
-
发行服务器证书的CA是否可靠
-
返回的公钥是否能正确解开返回证书中的数字签名
-
服务器证书上的域名是否和服务器的实际域名相匹配
-
验证通过后,将继续进行通信,否则,终止通信
4.客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择。
5.服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。
6.服务器将选择好的加密方案通过明文方式返回给客户端。
7.客户端接收到服务器端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的秘钥,使用服务器端返回的公钥进行加密,将加密后的随机码发送至服务器。
8.服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密秘钥。
9.在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。
准备
向我们亲爱的后台小伙伴要下他们申请的证书,.crt或者.pem的都可以,然后转成.cer证书,将.cer文件导入工程。
QQ截图20220614214018.png在AFHTTPSessionManager中按照上图进行写。注意AFHTTPSessionManager要用initWithBaseURL初始化,这样,单项验证就完了。**
双向验证
保证client和server是真的,通道是安全的(对称密钥)
流程
1.客户的浏览器向服务器传递客户端SSL协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。
2.服务器向客户端传送SSL协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。
3.客户利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括
-
证书是否过期
-
发行服务器证书的CA是否可靠
-
发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”
-
服务器证书上的域名是否和服务器的实际域名相匹配
-
如果合法性验证没有通过,通讯将断开,如果合法性验证通过,将继续进行第四步。
4.用户端随机产生一个用于后面通讯的“对称密码”,然后用服务器的公钥(服务器的公钥从步骤2终端服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器。
5.如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。
6.如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:
客户的证书使用日期是否有效
为客户提供证书的CA是否可靠
发行CA的公钥能否正确解开客户证书的发行CA的数字签名
检查客户的证书是否在证书废止列表(CRL)中。
检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。
时在SSL通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。
8.客户端向服务器端发出信息,指明后面的数据通讯将使用步骤7中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。
9.服务器向客户端发出信息,指明后面的数据通讯将使用的步骤7中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。
10.SSL的握手部分结束,SSL安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。
双向验证的实例代码:
QQ截图20220614214046.png QQ截图20220614214055.png
一般应用都是采用单向认证的,原因很简单,用户数目广泛,且无需做在通讯层做用户身份验证,一般都在应用逻辑成来保护用户的合法登入。但如果是企业对应对接,情况就不一样,可能会要求对客户端做身份验证。这时就需要做双向认证。
差异:从单向认证和双向认证的流程来看,单向认证只要求站点部署了SSL证书就行,任何用户都可以去访问(IP被限制除外等),只是服务器端提供了身份认证。而双向认证则是需要服务端需要客户端提供身份认证,只能是服务端允许的客户能去访问,安全性相对要高一些。
常用抓包工具
-
Charles
-
Fiddler
如何防止抓包
对于HTTPS API接口,如何防止抓包呢?既然问题出在证书信任问题上,那么解决方法就是在我们的APP中预置证书。在TLS/SSL握手时,用预置在本地的证书中的公钥校验服务器的数字签名,只有签名通过才能成功握手。由于数字签名是使用私钥生成的,而私钥只掌握在我们手上,中间人无法伪造一个有效的签名,因此攻击失败,无法抓包。
这样做虽然解决了抓包问题,但是也带来了另外一个问题:我们购买的证书都是有有效期的,到期前需要对证书进行更新。主要有两种方式:
1、提供预置证书更新接口。在当前证书快过期时,APP请求获取新的预置证书,这过渡时期,两个证书同时有效,直到安全完成证书切换。这种方式有一定的维护成本,且不易测试。 2、在APP中只预埋公钥,这样只要私钥不变,即使证书更新也不用更新该公钥。但是,这样不太符合周期性更新私钥的安全审计需求。一个折中的方法是,一次性预置多个公钥,只要任意一个公钥验证通过即可。考虑到我们的证书一般购买周期是3-5年,那么3个公钥,可以使用9-15年,同时,我们在此期间还可以发布新版本废弃老公钥,添加新公钥,这样可以使公钥一直更新下去。
防止抓包全过程
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());
总结
总的来说https把流量加密了,正常抓包,你看到的内容是一堆乱码。 https的加密没有安全问题,但它只是用来防止通信过程中被第三方获取明文。如果黑客能直接控制通信的双方(你的电脑,或服务器),那么黑客肯定能看到https明文的。
Android技术交流与资料学习:Android核心技术进阶手册、实战笔记、面试题纲资料
放抓包策略就是对抗hook,常见方法:
-
检测hook : 检测Xposed、Frida、Substrate等Hook框架
-
使用socket连接 : 使用Socket走TCP/UDP,防止被应用层抓包
-
传输数据加密 :协议字段加密传输,并隐藏秘钥,应用层加固
-
native层传输 : 将网络传输逻辑写到jni层实现,提高反编译门槛