目录
1.概述
2.加密算法
3.数字证书
4.根证书
5.证书链
6.证书的指纹和签名
7.证书的验证
8.整个通信流程概述
9.在Android中添加可信证书
-
概述
应用层网络请求协议有两种非常常用的协议,HTTP和HTTPS,其中HTTP请求属于明文传输,在传输的过程中容易被人截取,而HTTPS在HTTP的基础上加入了SSL/TLS层(安全套接层)的安全的超文本传输协议。将传输的内容进行加密。
-
加密算法
说到加密算法,有两种常用的加密方式,分别是对称加密和非对称加密:
1.对称加密:加密使用的密钥和解密使用的密钥是相同的,也就是说密钥,加密算法是公开的,密钥是加密者和解密者绝对保密的。
2.非对称加密:加密解密使用的密钥是不同的,HTTPS在数字证书验证的时候,采用的RSA密码体制就是一种非对称加密。
RSA是一种公钥密码体制,现在使用的非常广泛,这个密码体制分为三个部分,公钥,私钥,和加密算法,其中公钥和加密算法是公布的,私钥是自己保密的。这种机制最大的特点就是通过公钥加密的密文只有对应的私钥才能够解密。同样,通过私钥加密的密文也只有对应的公钥才能够解密。
-
数字证书
数字证书是 HTTPS实现安全传输的基础,它是由权威CA机构颁发的,证书的主要内容有:公钥,ISSUER(证书的颁发机构),Subject(证书的持有者),证书有效期,签名算法,指纹及指纹算法。
在网络中,要想实现自己的数据能够安全传输,就必须向权威的CA机构申请数字证书。
下面看一下证书的内容:
-
根证书
CA寄过后除了给别人颁发证书以外,也有自己的证书,这个证书是机构颁发给自己的,为了区分所以称作根证书,根证书也有自己的公钥和私钥,根证书的公钥和私钥称为根公钥和根私钥,根公钥和加密算法是对外公布的,而根私钥是CA机构自己的,是保密的。这个根证书在验证证书的过程中起核心的作用。
在浏览器中可以看到打开的步骤,打开步骤:浏览器 - 工具 - Internet选项-内容-证书,其中可以找到颁发者和颁发给相同的证书,就是根证书。根证书的具体作用见下文。
- 证书链是什么
以百度为例,在浏览器上访问 “www.baidu.com” 域名,地址连左侧有一个小锁的标志,点击就能查看百度的数字证书,如下图所示(使用的是Edge浏览器)
在图片的顶部,我们看到这样一个层次关系:
GlobalSign Root CA -> GlobalSign Organization Validation CA -> baidu.com
这个层次可以抽象为三个级别:
- end-user:即 baidu.com,该证书包含百度的公钥,访问者就是使用该公钥将数据加密后再传输给百度,即在 HTTPS 中使用的证书
- intermediates:即上文提到的 签发人 Issuer,用来认证公钥持有者身份的证书,负责确认 HTTPS 使用的 end-user 证书确实是来源于百度。这类 intermediates 证书可以有很多级,也就是说 签发人 Issuer 可能会有有很多级
- root:可以理解为 最高级别的签发人 Issuer,负责认证 intermediates 身份的合法性
这其实代表了一个信任链条,最终的目的就是为了保证 end-user 证书是可信的,该证书的公钥也就是可信的。
结合实际的使用场景对证书链进行一个归纳:
- 为了获取 end-user 的公钥,需要获取 end-user 的证书,因为公钥就保存在该证书中
- 为了证明获取到的 end-user 证书是可信的,就要看该证书是否被 intermediate 权威机构认证,等价于是否有权威机构的数字签名
- 有了权威机构的数字签名,而权威机构就是可信的吗?需要继续往上验证,即查看是否存在上一级权威认证机构的数字签名
- 信任链条的最终是Root CA,他采用自签名,对他的签名只能无条件的信任
还有一个小问题,Root 根证书从何而来呢?除了自行下载安装之外,浏览器、操作系统等都会内置一些 Root 根证书,称之为 Rrusted Root Certificates。比如 Apple MacOS 官网就记录了操作系统中内置的可信任根证书列表。
-
证书的指纹和签名
在上面的证书中可以看到有一个叫指纹的字符串,指纹可以理解为整数身份的唯一代表,是用来保证证书的完整性的。确保证书没有被修改过,证书在发布之前,CA机构对证书的内容用指纹算法计算得到一个hash值,这个hash值就是指纹。为什么用hash值作为证书的身份代表呢?首先,hash值具有不可逆性,也就是说无法通过hash值得到原来的信息内容,其次,hash值具有唯一性,即hash计算可以保证不同的内容一定会得到不同的hash值。
签名是在信息后面加上一段数字串,可以证明该信息没有被修改过。数字证书在发布的时候,CA机构将证书的指纹和指纹算法通过自己的私钥教秘得到的就是证书签名了。
签名和指纹的作用就是在验证证书的时候,首先通过机构的根公钥去解密证书的数字签名,解密成功以后得到证书的指纹和指纹算法,指纹是一个hash值,代表着证书的原始内容,然后通过指纹算法计算证书内容得到另外一个hash值,如果这两个hash值相同,则代表证书没有被篡改过。
-
证书的验证
下面基于一个简单的图例,去分析HTTPS的数字证书的验证过程
假设这是一个浏览器的HTTPS请求
一:首先浏览器通过URL网址去请求服务端,服务端接收到请求后,就会给浏览器发送一个自己的CA数字证书
二:浏览器接收到证书以后,就要开始进行验证工作了。首先从证书中获取证书的颁发机构,然后从浏览器系统中去寻找此颁发机构的根证书。上面我们也看到,世界上权威CA机构的根证书都是预先嵌入到浏览器系统中的,如果在浏览器系统中没有找到对应的根证书,就代表此机构不是受信任的,那么就会警告无法确认证书的真假。
如果是受信任的CA机构,我们就从根证书中获取到根公钥,用根公钥去解密证书的数字签名,成功解密以后得到证书的指纹和指纹算法,指纹是证书内容通过指纹算法计算得到的一个hash值,这里我们称之为h1,h1代表证书的原始内容;然后用指纹算法对当前接收到的证书内容再进行一次hash计算得到另一个值h2,h2则代表当前证书的内容,如果此时h1和h2是相等的,就代表证书没有被修改过。如果证书被篡改过,h2和h1是不可能相同的,因为hash值具有唯一性,不同内容通过hash计算得到的值是不可能相同的。
在证书没有被修改过的基础上,再检查证书上的使用者的URL和我们请求的URL是否相等,如果相等,那么就可以证明当前浏览器链接的网站也是正确的,而不是一些钓鱼网之类的。
但如果浏览器的连接被某个中间人截取了,中间人也可以发一个由权威的CA机构颁发的证书给浏览器,然后也可以通过证书没有被篡改的验证,但是在证书没有被篡改的前提下,通过对比证书上的URL和我们请求的URL是否相同,就可以判断当前接收到的证书是否为服务器发的证书。可以这么理解,因为URL具有唯一性,所以中间人的证书的上的URL和我们的证书的URL是不可能相同的,如果中间人修改了自己证书上的URL,但是通过不了证书没有被篡改的验证,所以中间人的证书也是欺骗不了我们的。
到这里我们认证了三点信息:
1.证书是否为受信任的权威机构颁发的
2.证书是否被篡改
3.证书是否为服务器发过来的,而不是第三方发的
三:基于上面的三点信息认证都没有问题的情况下,下一步我们有一个重要的任务就是,如何将一个对称加密算法的秘钥安全地发给服务器。
首先随机生成一个字符串S作为我们的秘钥,然后通过证书公钥加密成密文,将密文发送给服务器。因为此密文是用公钥加密的,这是一个非对称加密,我们知道,这个密文只有私钥的持有者才能进行解密,在这里私钥的持有者当然是服务器了,所以说任何第三方截取到密文也是没用的,因为没有对应的私钥也解析不出来。
还有一个关键步骤,发送密文的时候也会对消息内容进行签名操作。签名上面讲解过,就是对密文内容进行hash计算得到的hash值再通过公钥或私钥加密得到的一段数字串,这个签名和消息内容一起发送出去。接收方收到消息以后,通过私钥或公钥解析出密文和签名的hash值,同时也会对接收的消息内容进行同样的hash计算得到另一个hash值,通过比对两个hash值是否相同来判断密文是否有修改过
四:通过了上面的步骤以后,此时客户端和服务端都持有了对称加密算法的同一个秘钥,然后兄弟俩就可以愉快地安全通信了
-
Https通信流程
-
在Android中添加可信证书
android系统已经预置了150多个证书,服务端用的证书是从android认可的证书颁发机构购买的证书,默认情况下,是信任它们的,因此可以直接访问而无需在客户端设置什么。
购买证书毕竟是花钱的,使用自签名证书就是另外一种常见的方式了。所谓的自签名证书就是没有通过受信任的证书颁发机构,自己给自己颁发的证书。最典型的就是12306火车购票,使用的证书就不是受信任的证书颁发机构颁发的,而是旗下SRCA颁发的证书。
使用自签名证书,因此客户端不信任服务器,会抛出异常:javax.net.ssl.SSLHandshakeException:。为此,我们需要自定义信任处理器(TrustManager)来替代系统默认的信任处理器,这样我们才能正常的使用自定义的证书或者非android认可的证书颁发机构颁发的证书。
分为以下两种情况:一种是安全性要求不高的情况下,客户端无需内置证书;另外一种则是客户端内置证书。
第一种情况下,需要自定义TrustManager时重写checkServerTrusted()方法,并在该方法中校验证书,不做验证的话空实现即可。但是如不对服务器证书做任何验证,存在严重安全漏洞,会被拦截请求,伪造任意证书,做中间人攻击。
推荐第二种方式:客户端内置证书
使用自签名证书大致要经过以下几步:
将证书添加到工程中
自定义信任管理器TrustManager
用自定义TrustManager代替系统默认的信任管理器
添加证书到工程
比如现在我们有个证书media.bks,首先将其放在res/raw目录下,当然你可以放在assets目录下。
自定义TrustManager
这里需要实现本地证书的加载
protected static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {
if (context == null) {
throw new NullPointerException("context == null");
}
//CertificateFactory用来证书生成
CertificateFactory certificateFactory;
try {
certificateFactory = CertificateFactory.getInstance("X.509");
//Create a KeyStore containing our trusted CAs
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
for (int i = 0; i < certificates.length; i++) {
//读取本地证书
InputStream is = context.getResources().openRawResource(certificates[i]);
keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(is));
if (is != null) {
is.close();
}
}
//Create a TrustManager that trusts the CAs in our keyStore
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
//Create an SSLContext that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
用自定义TrustManager代替系统默认的信任管理器
private void onHttpCertficates(Context context, OkHttpClient.Builder builder) {
int[] certficates = new int[]{R.raw.media};
builder.socketFactory(getSSLSocketFactory(context, certficates));
}
其实不难发现,使用非android认证证书颁发机构颁发的证书的关键在于:修改android中SSLContext自带的TrustManager以便能让我们的签名通过验证。
部分参考:https://blog.csdn.net/liuxingrong666/article/details/83869161
https://blog.csdn.net/zhouxinxin250/article/details/81164921
//www.greatytc.com/p/fcd0572c4765