Https的通信过程
两种加密
加密方式分两种,对称加密和非对称加密。这两种方式都有自己的优劣势, https中这两种方式都采用了。 我们约定S是服务端,C是客户端,客户端需要从服务端获取信息;
对称加密
这种加密方式比较简单,就是双方都持有密匙。S和C都持有密匙, S通过密匙加密明文传递给C,C获取加密后的信息,用密匙解密信息。
- 优势: 加密速度快
- 劣势: 密匙的传递是个问题,容易被截取,密匙一旦被截取后, 就能轻易破解信息。
常见的对称加密算法有DES、3DES、TDEA、Blowfish、RC5和IDEA。
非对称加密
非对称加密中,S和C端都有自己的公钥和私钥。公钥是公开的,私钥是私有的,私钥需要保密的。 这套公钥和私钥的有个两种加密解密流程:
- 用公钥加密的信息,用私钥才能解密。因为私钥是私有的, 这种流程用于信息的加密解密;
- 用私钥加密信息,用公钥来解密。因为公钥是共有的,这种流程用于认证。
在https中信息传递的密匙的传递是采用非对称加密传递的.
C端需要把信息传递给S端, 需要分几步.
- C端请求S端,S端把自己的公钥传递给C端。
- C用S的公钥把信息加密后传递给S. S用自己的私钥解密获取信息。
常用的非对称加密算法有RSA、Elgamal、Rabin、D-H、ECC(椭圆曲线加密算法)等。
问:既然对称加密和非对称加密都需要保密好自己的私钥, 那有什么区别呢?
对称加密中,私钥不仅需要自己知道也需要解密方知道。 这样私钥就有一个传递的流程, 这个流程就会有很大风险。 而非对称加密只需要自己保密好自己的私钥就好了。 公钥大家都知道,不需要保密,就少了一个私钥传递的过程。 少了很大的风险。
HTTPS中的SSL/TLS协议
HTTPS = HTTP + SSL/TLS协议
- SSL的全称是Secure Sockets
Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。SSL协议在1994年被Netscape发明,后来各个浏览器均支持SSL,其最新的版本是3.0; - TLS的全称是Transport Layer Security,即安全传输层协议,最新版本的TLS建立在SSL
3.0协议规范之上.在理解HTTPS时候,可以把SSL和TLS看做是同一个协议。
HTTPS加密方式
HTTPS为了兼顾安全与效率,同时使用了对称加密和非对称加密。
- 数据是被对称加密传输的,对称加密过程需要客户端的一个密钥,为了确保能把该密钥安全传输到服务器端;
- 采用非对称加密对该密钥进行加密传输,总的来说,对数据进行对称加密,对称加密所要使用的密钥通过非对称加密传输。
HTTPS通信流程
一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。
1. 第一次HTTP请求:
1.客户端向服务器发起HTTPS请求,连接到服务器的443端口。
2.服务器端配置存放着权威CA机构颁发的证书,CA证书是用CA机构的私钥加密过的,里面包含服务器端提交给CA机构的公钥,网站域名、有效时长等等信息。服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人,此公钥包含在了加密过的CA证书里。
3.服务器端将自己配置在服务器上且加过密的CA证书发送给客户端。
4.客户端收到服务器端的CA证书之后,世界上的网站是无限多的,而CA机构总共就那么几家。任何正版操作系统都会将所有主流CA机构的公钥内置到操作系统当中,所以我们不用额外获取,解密时只需遍历系统中所有内置的CA机构的公钥,只要有任何一个公钥能够正常解密出数据,就说明它是合法的。客户端用CA机构的公钥对证书进行解密,获取到服务器端的公钥,对公钥进行检查,验证其合法性,如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。
2. 第二次HTTP请求:
1.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
2.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
然后服务器将加密后的密文发送给客户端。
3.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
数字证书
为什么需要数字证书
在https中需要证书,证书的作用是为了防止"中间人攻击"的。 如果有个中间人M拦截客户端请求,然后M向客户端提供自己的公钥,M再向服务端请求公钥,作为"中介者" 这样客户端和服务端都不知道,信息已经被拦截获取了。这时候就需要证明服务端的公钥是正确的.
怎么证明呢?
就需要权威第三方机构来公正了.这个第三方机构就是CA. 也就是说CA是专门对公钥进行认证,进行担保的,也就是专门给公钥做担保的担保公司。 全球知名的CA也就100多个,这些CA都是全球都认可的,比如VeriSign、GlobalSign等,国内知名的CA有WoSign。
数字证书怎么起作用
不论什么平台,设备的操作系统中都会内置100多个全球公认的CA,说具体点就是设备中存储了这些知名CA的公钥。当客户端接收到服务器的数字证书的时候,会进行如下验证:
1.首先客户端会用设备中内置的CA的公钥尝试解密数字证书,如果所有内置的CA的公钥都无法解密该数字证书,说明该数字证书不是由一个全球知名的CA签发的,这样客户端就无法信任该服务器的数字证书。
2.如果有一个CA的公钥能够成功解密该数字证书,说明该数字证书就是由该CA的私钥签发的,因为被私钥加密的密文只能被与其成对的公钥解密。
3.除此之外,还需要检查客户端当前访问的服务器的域名是与数字证书中提供的“颁发给”这一项吻合,还要检查数字证书是否过期等。
证书链
一般CA不会直接去使用自己的私钥去签名某网站的证书, 一般CA会签发一个子证书, 然后用这子证书去签网站的证书. 有可能有多个子证书. 如果父证书是可以被信任的,那么这个子证书就是可以被信任的.
Android使用Https
这里只讨论单向通信的情况并且是服务器自签名的证书,权威机构颁发过证书的https使用和普通http使用法师没有什么区别。
Android中使用Https一般有两种写法,
- 第一种是重写证书信任管理器类,也就是就是实现了接口X509TrustManager的类。
- 第二种证书锁定,直接用预埋的证书来生成TrustManger
第一种是重写证书信任管理器类
接口X509TrustManager有下述三个公有的方法需要我们实现:
⑴ void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
该方法检查客户端的证书,若不信任该证书则抛出异常。由于我们不需要对客户端进行认证,因此我们只需要执行默认的信任管理器的这个方法。JSSE中,默认的信任管理器类为TrustManager。
⑵ void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
该方法检查服务器的证书,若不信任该证书同样抛出异常。通过自己实现该方法,可以使之信任我们指定的任何证书。在实现该方法时,也可以简单的不做任何处理,即一个空的函数体,由于不会抛出异常,它就会信任任何证书。
⑶ X509Certificate[] getAcceptedIssuers() 返回受信任的X509证书数组。
主机域名验证
public static class SafeHostnameVerifier implements HostnameVerifier{
private String hostName;
public SafeHostnameVerifier(String hostName){
this.hostName = hostName;
}
@Override
public boolean verify(String hostname, SSLSession session) {
// 使用Java自带的HttpsURLConnection
// HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
// return hostnameVerifier.verify(this.hostName,session);
if (hostname.equals(this.hostName)){
return true;
}else {
return false;
}
}
}
这里是简单实现,完整的写法可以参看Okhttp的OkHostnameVerifier
证书信任管理
将assets文件夹中的服务器证书文件流转化成证书的方法
//拿到自己的证书
X509Certificate getX509Certificate(Context context) throws IOException, CertificateException {
InputStream in = context.getAssets().open("srca.cer");
CertificateFactory instance = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) instance.generateCertificate(in);
return certificate;
}
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class MyX509TrustManager implements X509TrustManager {
//如果需要对证书进行校验,需要这里去实现,如果不实现的话是不安全
X509Certificate mX509Certificate;
public MyX509TrustManager(X509Certificate mX509Certificate) {
this.mX509Certificate = mX509Certificate;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509Certificate certificate:chain){
//检查证书是否有效
certificate.checkValidity();
try {
certificate.verify(mX509Certificate.getPublicKey());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
这里需要注意下错误的写法
public static class UnSafeHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
// 没有验证服务器端的域名
return true;
}
}
private static class UnSafeTrustManager implements X509TrustManager
{
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
// 没有实现服务器端证书验证
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new java.security.cert.X509Certificate[0]{};
}
}
使用方式
- Java自带的HttpsURLConnection
URL url = new URL(path);
//1.改成s
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
//2.SSLContext 初始化
SSLContext tls = SSLContext.getInstance("TLS");
MyX509TrustManager myX509TrustManager = new MyX509TrustManager(getX509Certificate(context));
TrustManager[] trustManagers={myX509TrustManager};
tls.init(null,trustManagers,new SecureRandom());
//3.ssl工厂
SSLSocketFactory factory = tls.getSocketFactory();
//4.添加一个主机名称校验器
conn.setHostnameVerifier((new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
conn.setSSLSocketFactory(factory);
- OkHttp使用方式
//1.SSLContext 初始化
SSLContext tls = SSLContext.getInstance("TLS");
MyX509TrustManager myX509TrustManager = new MyX509TrustManager(getX509Certificate(context));
TrustManager[] trustManagers={myX509TrustManager};
tls.init(null,trustManagers,new SecureRandom());
//3.ssl工厂
SSLSocketFactory factory = tls.getSocketFactory();
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true) //设置出现错误进行重新连接。
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.sslSocketFactory(factory)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.build();
第二种证书锁定,直接用预埋的证书来生成TrustManger
获取本地的服务器端自签名证书
public static SSLSocketFactory getSSlFactory(Context context) {
// 从assets中加载证书,取到证书的输入流
InputStream is = getApplicationContext().getAssets().open("srca.cer");
// 证书工厂
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(is);
// 加载证书到密钥库中
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null);
keyStore.setCertificateEntry("cert", ca);
// 加载密钥库到信任管理器
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
// 用 TrustManager 初始化一个 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
return sslContext.getSocketFactory();
}
若server使用的证书是由认证的CA机构颁发的,则如此配置即可(OkHttp等均已支持),或者根本不需要配置,就和普通http一样使用即可
private X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
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];
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
return sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
参考文章:
写一篇最好懂的HTTPS讲解
Android Https心得
android https简介和证书认证
android 使用https请求
深入理解 Android Https
Android安全开发之安全使用HTTPS
Android 使用 HTTPS
android HTTPS认证
Android使用https
Android 上 Https 双向通信— 深入理解KeyManager 和 TrustManagers