http 与 https
https 跟 http 的最大区别在于 https 多加了一个保障通讯安全的层.
- https协议需要到ca申请证书,一般免费证书很少,需要交费.
- http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
SSL 证书
Transport Layer Security
Secure Sockets Layer 其实它就包含俩部分:
- 一个身份标识, 一个用来识别身份的东西, 有点类似警察叔叔通过护照或驾照查你的身份;
- 一个公共密钥, 这个用来给数据加密, 而且只有证书的持有者才能解密.
SSL 证书就俩个功能, 身份验证跟保障通讯过程中的数据安全.
另外还有一点很重要. 那就是一个证书可以给另外一个证书“签字”. 用 layman 的话说就是 Bob 用他自己的证书在别的证书上盖上 “同意” 两个红红的大字. 如果你信任 Bob (当然还有他的证书), 那么你也可以信任由他签发的证书. 在这个例子中, Bob 摇身一变, 成了证书颁发机构(Certificate Authority). 现在主流的浏览器都自带一大堆受信任证书颁发机构(trusted Certificate Authorities)(比如:Thawte, Verisign等).
浏览器是怎么使用证书的
笼统的讲, 当你打开下列连接的时候 “https://www.yoursite.com” :
- 服务器会给浏览器发一个证书.
- 浏览器会对比证书中的“common name”(有时也叫 “subject”) 跟服务器的域名是否一样. 例如, 一个从“www.yoursite.com” 网站发过来的证书就应该有一个内容是 “www.yoursite.com” 的 common name, 否则浏览器就会提示该证书有问题.
- 浏览器验证证书真伪, 有点像门卫通过证件上的全息图辨别你的证件是不是真的. 既然在现实生活中有人伪造别人的身份. 那么在网络世界也就有人造假, 比如用你的域名“www.yoursite.com” 来伪造一个安全证书. 浏览器在验证的时候, 会检查这个证书是否是它信任机构颁发的, 如果不是, 那么浏览器就会提示这个证书可能有问题. 当然, 用户可以选择无视警告, 继续使用.
- 一旦证书通过验证 (或是用户无视警告, 继续使用有问题的证书), 浏览器就开始利用证书中的公开密钥加密数据并传给服务器.
TLS (SSL)中的加密
一旦服务器发过来的证书通过验证, 客户端就会利用证书中包含的公共密钥加密某个指定的共享密钥, 然后发给服务器. 这个加密过的共享密钥只能用服务器的私有密钥才能解密(非对称加密), 别人无法解密出其中的内容. 服务器把解密出来的共享密钥保存起来, 供本次连接会话专用. 从现在开始, 服务器跟客户端之间的所有通讯信息都用这个共享密钥加密解密(对称加密).
漏洞描述
对于数字证书相关概念、Android 里 https 通信代码就不再复述了,直接讲问题。缺少相应的安全校验很容易导致中间人攻击,而漏洞的形式主要有以下3种:
- 自定义X509TrustManager。在使用HttpsURLConnection发起 HTTPS 请求的时候,提供了一个自定义的X509TrustManager,未实现安全校验逻辑,下面片段就是常见的容易犯错的代码片段。如果不提供自定义的X509TrustManager,代码运行起来可能会报异常(原因下文解释),初学者就很容易在不明真相的情况下提供了一个自定义的X509TrustManager,却忘记正确地实现相应的方法。本文重点介绍这种场景的处理方式。
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意客户端证书
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意服务端证书
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
- 自定义了HostnameVerifier。在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。如果回调内实现不恰当,默认接受所有域名,则有安全风险。代码示例。
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// Always return true,接受任意域名服务器
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
- 信任所有主机名。
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);