Android程序员需要了解的https与中间人攻击
前言--阅读本文你将得到什么
1.了解https加密的基本原理与过程,了解https为什么是安全的
2.了解什么是中间人攻击,中间人攻击的基本原理
3.了解如何防止中间人攻击
https加密的基本原理
https要完成的目的
1.Client必须要能确定,它要访问的Server确实是正确的Server
2.Client和Server交流的信息不能被其它第三方窃听
3.当然,针对第1点,反过来,Server也可以要求,必须确认Client是它想通信的正确的Client,不过道理和1一样,这里不展开
通过HTTPS握手建立加密信道来保证上述要求
https三次握手
三次握手过程如下:
看了上述三次握手过程,可以知道,handshake主要完成的事情:
1.客户端&服务端通信,协商加密方式
2.客户端(Client)和服务端(Server)互相确认身份
3.双方安全地交换https通信使用的密钥(Session Key)
第一阶段:C&S协商加密方式
客户端向服务端发送ClientHello信息,信息主要包括客户端支持的加密方式、客户端支持的SSL版本等;服务端接收到ClientHello信息后,向客户端发送一个ServerHello信息,主要是告诉客户端它将使用什么加密方式和SSL版本。
第二阶段:身份校验
阶段2主要是,客户端&服务端互相校验对方身份
客户端与服务端之间验证身份是通过证书完成的
1.关于证书:
服务端向客户端下发自己的证书,通常是CA认证的证书。证书包括了很多信息,主要有“公钥信息”、“签名”、“组织机构地区等信息”、“证书颁发机构”,关联的中级证书(medium certificate)、根证书(root certificate)等。
X.509 应该是比较流行的 SSL 数字证书标准,包含(但不限于)以下的字段:
字段 | 值说明 |
---|---|
对象名称(Subject Name) | 用于识别该数字证书的信息 |
共有名称(Common Name) | 对于客户证书,通常是相应的域名 |
证书颁发者(Issuer Name) | 发布并签署该证书的实体的信息 |
签名算法(Signature Algorithm) | 签名所使用的算法 |
序列号(Serial Number) | 数字证书机构(Certificate Authority, CA)给证书的唯一整数,一个数字证书一个序列号 |
生效期(Not Valid Before) | (`・ω・´) |
失效期(Not Valid After) | (╯°口°)╯(┴—┴ |
公钥(Public Key) | 可公开的密钥 |
签名(Signature) | 通过签名算法计算证书内容后得到的数据,用于验证证书是否被篡改 |
主要就是签名,用于验证是否篡改过
2.客户端如何通过证书确定服务端的身份?
证明下面两点,(然后才可以使用证书上的公钥来加密生成Session key的随机数)
- 证明baidu.com这个证书确实是百度的
- 证明baidu.com这个证书没有被其他人攥改过
证书以证书链的形式组织,在颁发证书的时候首先要有根CA机构颁发的根证书,再由根CA机构颁发一个中级CA机构的证书,最后由中级CA机构颁发具体的SSL证书。
数字证书采用信任链验证。数字证书的信任锚(信任的起点)就是根证书颁发机构。根证书(root certificate)是一个无签名或自签名的证书。是用于识别根证书颁发机构(CA)的公钥证书。
验证的具体实现如下图所示: [图片上传中...(image-d0d1d9-1609743687408-1)]
- 从左往右,用户证书指向签署它的中级证书,并且,用户证书的摘要经由中级证书的私钥加密,密文作为该用户的证书签名(signature)记录在用户证书上。
- 中级证书相应的,指向签署它的根证书,同时,中级证书的摘要经由根证书的私钥加密,密文作为中级证书的签名记录在中级证书上
- 根证书在签署的时候,使用签发机构的私钥对证书的摘要进行加密,密文作为根证书的签名记录在根证书上。
如何验证证书
1.如何验证证书没有被篡改过
比如验证用户证书A,需要用到中级证书的公钥B解密前者的签名得到摘要 Digest1,我们的客户端也计算A证书的内容得到摘要 Digest2。对比这两个摘要就能知道前者是否被篡改。后者同理,使用他的证书签发者提供的公钥验证。当验证到到受信任的根证书时,就能确定这个证书是可信的。
2.为什么根证书是可信的
数字证书认证机构(Certificate Authority, CA)签署和管理的 CA 根证书,会被纳入到你的浏览器和操作系统的可信证书列表中,并由这个列表判断根证书是否可信。所以不要随便导入奇奇怪怪的根证书到你的操作系统中。
第三阶段:产生通信密钥
前面讲过,第1阶段,服务端告知了客户端后面要使用的加密方式(普遍都是对称加密,因为非对称加密成本太高,速度低下)。
第2阶段,客户端验证服务端证书是正确的,即,证书上标明的公钥拿来加密信息,加密后的信息,只有该服务端能解密,其它第三方无法解密。 那么在第三阶段,客户端产生了第三个随机数,这个随机数称为PMSc(a premaster secret,46 bytes ),使用服务端的公钥对PMSc加密,然后上送给服务端。服务端取到这个密文后,用自己的私钥解密,得到PMSc。
好了,接下来最重要的一步来了:根据之前协商好的加密方式,以及3个随机数,客户端、服务端各自产生出通信密钥,该密钥称为Master Secret,简称MS,也称Session Key。这个密钥虽然是各自产生的,但是产生后是一致的。
第四阶段:加密信道已经建立
客户端,服务端各自产生了通信密钥后,就用这个相同的MS对往后的所有通信信息进行加密。而这个密钥,第三方是不知道的,第三方尽管去窥探,但是他们看不懂信息,所以效果相当于,客户端&服务端在一个加密信道中通信。
从上面可以看出,https通信过程是对称与非对称加密混合的
中间人攻击与https抓包
一个针对SSL的中间人攻击过程如下: [图片上传中...(image-ae824e-1609743687408-0)]
中间人其实是做了一个偷梁换柱的动作,核心是如何欺骗客户端,从而让客户端能够放心的与中间人进行数据交互而没有任何察觉。我们来看Charles如何做到HTTPS抓包的,网上有很多fiddlers如何抓HTTPS包的教程,几步就搞定了,其中最核心的就是:
将私有CA签发的数字证书安装到手机中并且作为受信任证书保存
当私有的CA证书添加到系统信任证书后,就可以完成证书链验证过程
fiddler抓包过程,详情可见:https://www.cnblogs.com/afeng2010/p/10073446.html
android7.0之后用户CA限制
Android从7.0开始系统不再信任用户CA证书(应用targetSdkVersion >= 24时生效,如果targetSdkVersion < 24即使系统是7.0+依然会信任)。也就是说即使安装了用户CA证书,在Android 7.0+的机器上,targetSdkVersion >= 24的应用的HTTPS包就抓不到了。
Android 6.0(API 23)及更低版本应用的默认网络安全性配置如下:
<!-- 默认允许所有明文通信 -->
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<!-- 信任系统预装 CA 证书 -->
<certificates src="system" />
<!-- 信任用户添加的 CA 证书,Charles 和 Fiddler 抓包工具安装的证书属于此类 -->
<certificates src="user" />
</trust-anchors>
</base-config>
复制代码
而在 Android 7.0(API 24)到 Android 8.1(API 27)的默认网络安全性配置如下:
<!-- 默认允许所有明文通信 -->
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<!-- 信任系统预装 CA 证书 -->
<certificates src="system" />
</trust-anchors>
</base-config>
复制代码
而在 Android 9.0(API 28)及更高版本的默认网络安全性配置如下:
<!-- 默认禁止所有明文通信 -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<!-- 信任系统预装 CA 证书 -->
<certificates src="system" />
</trust-anchors>
</base-config>
复制代码
如果我们要抓自已APP的包,解决方式就是使用 Android 6.0 以下的网络安全性配置: 添加res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
复制代码
然后在清单文件中指向该文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
复制代码
如何防止中间人攻击
通过上面的讲解,大家可以知道
假设你的设备没有安装信任过来历不明的证书,那么不管在任何WIFI或者网络环境下,通常HTTPS通信都是安全的
并且在android7.0及以上,就算手机安装信任了证书,在一般情况下也是安全的,因为android7.0以上默认不信任用户证书
所以通常情况下,我们不需要做什么特别操作来防止中间人攻击
不过有部分金融类的APP需要额外校验来保证https的安全性
我们可以下载服务器端公钥证书,然后将公钥证书编译到Android应用中一般在assets文件夹保存,由应用在交互过程中去验证证书的合法性。
//使用内置证书
public static SSLSocketFactory getSSLSocketFactory_Certificate(Context context) {
try {
//获取X.509格式的内置证书
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
Collection<? extends Certificate> cers = certificateFactory.generateCertificates(context.getAssets().open("CACertificate.cer"));
int index = 0;
for (Certificate certificate : cers) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificate);
}
//用这个KeyStore去引导生成的TrustManager来提供验证
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
//用TrustManager生成SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
//SSLContext返回SSLSocketFactory
return sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
复制代码
总结
本文主要介绍了https的基本流程与原理,并介绍了中间人攻击的原理与预防,希望对大家有所帮助。