HTTPS(Hypertext Transfer Protocol Secure)是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信,但利用TLS来加密数据包。HTTPS开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
1. HTTP协议所存在的不足
HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,造成一些问题:
- 通信使用明文可能会被窃听
按TCP/IP协议族的工作机制,互联网上的任何角落都存在通信内容被窃听的风险。而HTTP协议本身不具备加密的功能,所传输的都是明文。即使已经经过过加密处理的通信,也会被窥视到通信内容,这点和未加密的通信是相同的。只是说如果通信经过加密,就有可能让人无法破解报文信息的含义,但加密处理后的报文信息本身还是会被看到的。 不验证通信方的身份可能遭遇伪装
在HTTP协议通信时,由于不存在确认通信方的处理步骤,因此任何人都可以发起请求。另外,服务器只要接收到请求,不管对方是谁都会返回一个响应。因此不确认通信方,存在以下隐患:- 无法确定请求发送至目标的Web服务器是否是按真实意图返回响应的那台服务器。有可能是已伪装的 Web 服务器;
- 无法确定响应返回到的客户端是否是按真实意图接收响应的那个客户端。有可能是已伪装的客户端;
- 无法确定正在通信的对方是否具备访问权限。因为某些Web服务器上保存着重要的信息,只想发给特定用户通信的权限;
- 无法判定请求是来自何方、出自谁手;
- 即使是无意义的请求也会照单全收,无法阻止海量请求下的DoS攻击;
无法证明报文完整性,可能已遭篡改
所谓完整性是指信息的准确度。若无法证明其完整性,通常也就意味着无法判断信息是否准确。HTTP协议无法证明通信的报文完整性,在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉。
2. HTTPS和HTTP的区别
- https协议需要到ca申请证书,一般免费证书很少,需要交费。
- http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
HTTPS相对HTTP提供了更安全的数据传输保障,主要体现在三个方面:
- 内容加密。客户端到服务器的内容都是以加密形式传输,中间者无法直接查看明文内容。
- 身份认证。通过校验保证客户端访问的是自己的服务器。
- 数据完整性。防止内容被第三方冒充或者篡改。
3. HTTPS的关键技术
1. 加密技术
一般的加密技术主要分为两种:
- 对称加密:加密与解密的密钥相同。以DES算法为代表;
- 非对称加密:加密与解密的密钥不相同。以RSA算法为代表;
对称加密强度非常高,一般破解不了,但存在一个很大的问题就是无法安全地生成和保管密钥,存在很大的安全隐患。
非对称加密消耗的CPU资源非常大,效率很低,会严重影响HTTPS的性能和速度。
HTTPS采用对称加密和非对称加密两者并用的混合加密机制,在交换密钥环节使用非对称加密方式,之后的建立通信交换报文阶段则使用对称加密方式。
2. 数字证书
数字证书为实现双方安全通信提供了电子认证。在因特网、公司内部网或外部网中,使用数字证书实现身份识别和电子信息加密。如果不验证公钥的可靠性,至少会存在如下的两个问题:中间人攻击和信息抵赖。
数字证书中含有密钥对(公钥和私钥)所有者的识别信息,通过验证识别信息的真伪实现对证书持有者身份的认证。
数字证书有如下的作用:
- 保密性 - 只有收件人才能阅读信息。
- 认证性 - 确认信息发送者的身份。
- 完整性 - 信息在传递过程中不会被篡改。
- 不可抵赖性 - 发送者不能否认已发送的信息。
- 保证请求者与服务者的数据交换的安全性
4. Https协议原理
1. 协议的实现
宏观上,TLS以记录协议(record protocol)实现。记录协议负责在传输连接上交换所有的底层消息,并可以配置加密。每一条TLS记录以一个短标头起始。标头包含记录内容的类型(或子协议)、协议版本和长度。消息数据紧跟在标头之后,如下图所示:
TLS的主规格说明书定义了四个核心子协议:
- 握手协议(handshake protocol);
- 密钥规格变更协议(change cipher spec protocol);
- 应用数据协议(application data protocol);
- 警报协议(alert protocol);
2. 握手协议
握手是TLS协议中最精密复杂的部分。在这个过程中,通信双方协商连接参数,并且完成身份验证。根据使用的功能的不同,整个过程通常需要交换6~10条消息。根据配置和支持的协议扩展的不同,交换过程可能有许多变种。在使用中经常可以观察到以下三种流程:
- 完整的握手,对服务器进行身份验证(单向验证,最常见);
- 对客户端和服务器都进行身份验证的握手(双向验证);
- 恢复之前的会话采用的简短握手;
- 单项认证保证了我们自己的客户端只能访问我们自己的服务器,但并不能保证我们自己的服务器只能被我们自己的客户端访问(第三方客户端忽略证书校验即可)。
- 双向认证则保证了我们的客户端只能访问我们自己的服务器,同时我们的服务器也只能被我们自己的客户端访问。因此双向认证可以说相比单项认证安全性足足提高一个等级。
单向验证:
- 交换各自支持的功能,对需要的连接参数达成一致;
- 验证出示的证书,或使用其他方式进行身份验证;
- 对将用于保护会话的共享主密钥达成一致;
- 验证握手消息是否被第三方团体修改;
结合Retrofit绑定证书:
1 构造SSLSocketFactory
public static SSLSocketFactory getSSLSocketFactoryForOneWay(InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance(CLIENT_TRUST_MANAGER, CLIENT_TRUST_PROVIDER);
KeyStore keyStore = KeyStore.getInstance(CLIENT_TRUST_KEYSTORE);
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
e.printStackTrace();
}
}
SSLContext sslContext = SSLContext.getInstance(CLIENT_AGREEMENT);
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;
}
2 为OKHttpClient设置SslSocketFactory以及hostnameVerifier
InputStream certificate = Utils.getContext().getResources().openRawResource(R.raw.srca);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(interceptor)
.addInterceptor(new HttpHeaderInterceptor())
.addNetworkInterceptor(new HttpCacheInterceptor())
.sslSocketFactory(SslContextFactory.getSSLSocketFactoryForOneWay(certificate))
.hostnameVerifier(new SafeHostnameVerifier())
.cache(cache)
.build();
双向验证:
同单向验证流程相比,双向验证多了如下两条消息:CertificateRequest与CertificateVerify,其余流程大致相同。
1 SSLSocketFactory
/**
* 双向认证
*
* @return SSLSocketFactory
*/
public static SSLSocketFactory getSSLSocketFactoryForTwoWay() {
try {
InputStream certificate = Utils.getContext().getResources().openRawResource(R.raw.capk);
// CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", "BC");
KeyStore keyStore = KeyStore.getInstance(CLIENT_TRUST_KEY);
keyStore.load(certificate, SELF_CERT_PWD.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, SELF_CERT_PWD.toCharArray());
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
e.printStackTrace();
}
//初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance(CLIENT_TRUST_KEYSTORE);
clientKeyStore.load(Utils.getContext().getResources().openRawResource(R.raw.cabks), TRUST_CA_PWD.toCharArray());
SSLContext sslContext = SSLContext.getInstance(CLIENT_AGREEMENT);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.
getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(clientKeyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, SELF_CERT_PWD.toCharArray());
sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2 配置OKHttpClient
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(interceptor)
.addInterceptor(new HttpHeaderInterceptor())
.addNetworkInterceptor(new HttpCacheInterceptor())
.sslSocketFactory(SslContextFactory.getSSLSocketFactoryForTwoWay())
.hostnameVerifier(new SafeHostnameVerifier())
.cache(cache)
.build();