前言
HTTP协议是明文传输的,当我们在网络上进行购物电子交易时,电子网银转账时,这种方式就显得很不安全了。HTTPS是在HTTP协议的基础上加入了SSL协议,SSL协议加在了传输层和应用层之间。
https原理
TLS/SSL协议位于网络OSI七层模型的会话层。
所以不光是 HTTP 协议,其他运行在应用层的 SMTP和 Telnet 等协议均可配合 SSL 协议使用。
SSL协议的工作过程
主要分为2阶段,如下图:
- 证书验证阶段:
- 浏览器发起 HTTPS 请求。
- 服务端返回 HTTPS 证书。
- 客户端验证证书是否合法。
- 数据传输阶段:
- 当证书验证合法后,在本地生成随机数。
- 通过公钥加密随机数,并把加密后的随机数传输到服务端。
- 服务端通过私钥对随机数进行解密。
- 服务端通过客户端传入的随机数构造对称加密算法,对返回结果内容进行加密后传输。
数据传输阶段怎么保证输入安全性
首先先了解下两种常用的加载方式
-
共享密钥加密。加密和解密同用一个密钥的方式,客户端和服务器得拥有同一个密钥,客户端使用密钥A加密,服务器使用密钥A解密
-
公开密钥加密。公开密钥就避免了共享密钥的传输问题,因为顾名思义,他的密钥是公开的,他使用的是非对称加密,一个公开密钥,一个私有密钥,公钥公开,大家都可以拿到,私钥只有服务有一份。客户端使用公钥对内容进行加密,服务器使用私钥才能解密。(Ps:这里有个前提,没有私钥的情况下要对加密进行解密,不是不可能,但是可以认为几乎不可能。)
共享密钥加密加密速度快,密钥较短,但不安全;公开密钥加密安全,但加密速度慢。因此,https使用“公开密钥加密” 的方式加密 “随机数” 为 “共享密钥”并同步服务端。保证效率的同时,也保证了传输安全性。
代理服务器攻击方式一:
- 客户端发起https请求后,被代理服务器拦截,然后他再向我们真正的服务器发相同的https请求
- 这时候服务器不知道你是谁,给了代理服务器公开密钥证书,然后代理服务器再把证书转发给客户端。
- 客户端校验没毛病,然后用公钥加密的随机字符串发给代码服务器
- 这个时候代理服务器懵逼了,因为他没有私钥,解密不到随机字符串到底是什么,好吧,他虽然看不懂,但是还是把这一坨东西原封不动的给了服务器。
- 服务器一看,没毛病,再把加密信息给代理服务器,
- 代理服务器,因为不知道刚才的随机字符串是什么,而这个时候客户端和服务器已经不再使用公钥加密,而是使用了共享密钥加密,而关键的关键就是密钥就是刚才的随机字符串。代理服务器再次懵逼.
通过https的数据传输加密方式, 代理服务器只能算蠢萌的中转人。这也就是使用了HTTPS之后,charles截取到的数据都是乱码的原因。
如果给charles装上一个自己的CA证书
代理服务器攻击方式二:
- 客户端https请求后,被代理服务器拦截,代理服务器也有CA颁发的证书,他把自己的合法证书发给客户端
- 客户端用证书里面的公钥解密签名之后,得到了各种信息,一看信息都对上了,没毛病信任。客户端在校验了代理服务器自己的证书后也建立了合法的通信。
- 然后代理服务器宰相真正的服务器https请求,服务器下发真正的证书,代理服务器假装自己是客户端,信任了证书,然后校验通过,发用真正公钥加密后的随机数给服务器,服务器当然能解开,也就是代理服务器作为客户端和真正的服务器建立了合法的通信。
这个时候,代理服务器作为中间人,因为他有自己的私钥和真正的公钥,可以欺上瞒下,俩边的信息都从它这里过了一次,信息被他偷窥到。攻击成功。
客户端如何防下代理服务器攻击方式二的攻击,唯一的方式就是在证书验证阶段,验证出中间代理给的CA颁发的证书是不合法的。https证书的结构组成和浏览器如何验证证书的合法性?
成立证书颁发机构,也就是CA
- CA会自己给自己签发一个证书,这个证书叫做根(Root)证书;
- 然后CA会通过根证书来签发中间证书,授权中间证书颁发机构签发证书的权限;
- 最后中间证书颁发机构通过中间证书向用户签发用户证书
为什么需要多这么一层中间证书颁发机构,目的是为了保护根证书,减少根证书被破解的风险。以Csdn证书为例子:
那么问题来了,浏览器验证证书的合法性呢?
- 浏览器(WebTrust)信任的证书颁发机构CA的根证书,可以通过OCSP(在线证书状态协议)维护,当然也可以信任自定义根证。
- 用户证书被中间证书信任,而中间证书被根证书信任,根证书又被浏览器信任,这样一个完整的证书链使得浏览器可以在根证书库内一次检索用户证书、中间证书和根证书,如果能匹配到根证书,那么这一信任链上的所有证书都是合法的。
我们在部署证书时,仅导入了用户证书,会怎么样?
- PC浏览器内置绝大部分CA的证书链,会自动补全
- 大部分的手机浏览器并不会内置证书链,所以无法识别用户证书,也就会把网站标记为不安全
所以切记,部署证书是一定要安装证书链。
如果用户证书被吊销,浏览器还会显示安全吗?
- 证书颁发机构有个叫做证书作废列表的东西。证书签发以后,如果出现私钥泄露或丢失、证书所有者信息变更、不再需要继续使用证书等情况,证书颁发机构将会对证书进行作废,并将证书的序列号登记在证书作废列表CRL中。
- 浏览器通过OCSP查询证书颁发机构CA最新的CRL,可以确定某个证书是否有效。但是,浏览器并不可能在每个访问者访问每个网站时都向CA查询一次,那这工作量也太大了,所以通常浏览器会定期查询CA最新的CRL,这也就会在一定程度上出现,证书被吊销了,浏览器依旧会信任的情况,但是这个是时间不会太久
如果用户证书没有问题,反而证书颁发机构CA由于安全防护出现问题导致一些机密内容泄露,那么这个CA就不应该被信任,各大浏览器就会将这个CA的根证书从根证书库中删除。这样,该证书签发的所有中间证书和用户证书都将不被信任。
SSL证书
证书包含签发方信息,拥有者信息,公钥,签名(数字签名)等。
- 签名是指签发方(中间证书或者Root证书)私钥对证书进行加密运算得来的
- 通过使用签发方(中间证书或者Root证书)公钥解密可以验证证书真伪.
证书的种类
- 自签证书,也就是颁发者是自己,使用自己的私钥来对证书的信息进行签名。我认为根证书就是自签证书,客户端一般自带根证书,并且信任根证书签发的任何证书。
- 机构颁发证书,就是通过机构来签发的,可能是根级机构也可能是二级机构,使用机构的私钥来对证书的信息进行签名。
证书的生成
我们可以通过openssl或者jdk提供的keytool来生成证书以及证书对应的私钥。以openssl为例子:
Root Ca生成
# 先生成私钥,如下语句会生成RSA私钥
openssl genrsa -out root.key 1024
# 使用私钥+ 拥有者信息 生成自签root证书(公钥 + 拥有者信息 + 签名)
openssl req -new -x509 -key root.key -out root.crt
## rootCa拥有者信息
Country Name (2 letter code) []:cn
State or Province Name (full name) []:zj
Locality Name (eg, city) []:hz
Organization Name (eg, company) []:mm
Organizational Unit Name (eg, section) []:mm
Common Name (eg, fully qualified host name) []:mm.com
Email Address []:
# 根据root.crt生成root.pem
openssl x509 -in root.crt -outform PEM -out root.pem
关于证书的编码格式-outform可选择PEM和DER类型
- DER是纯二进制TLV格式的编码协议
- PEM是X.509进行DER编码之后,对二进制数据进行了BASE64编码,然后加上文件头尾即可
可以把我们生成的root.pem把文件形式打开,复制内容至《PEM分析工具》
生成二级机构证书
# 先生成私钥,如下语句会生成RSA私钥
openssl genrsa -out second.key 1024
# 生成证书请求,用于向root机构申请证书
openssl req -new -key second.key -out second.csr
# 证书的请求内容如下
Country Name (2 letter code) []:cn
State or Province Name (full name) []:zj
Locality Name (eg, city) []:hz
Organization Name (eg, company) []:my
Organizational Unit Name (eg, section) []:my
Common Name (eg, fully qualified host name) []:my.com
Email Address []:
A challenge password []:123456
# 根证书机构 接受CSR请求并使用根证书私钥签名生成证书
openssl x509 -req -in second.csr -out second.crt -CA root.crt -CAkey root.key -CAcreateserial
# 根据root.crt生成root.pem
openssl x509 -in second.crt -outform PEM -out second.pem
通过《CSR分析工具》得
通过root签名的second.pem二级机构证书
生成机构颁发的用户证书
# 先生成私钥,如下语句会生成RSA私钥
openssl genrsa -out server.key 1024
# 生成证书请求,用于向二级机构申请证书
openssl req -new -key server.key -out server.csr
# 证书的请求内容如下
Country Name (2 letter code) []:cn
State or Province Name (full name) []:zj
Locality Name (eg, city) []:hz
Organization Name (eg, company) []:test
Organizational Unit Name (eg, section) []:test
Common Name (eg, fully qualified host name) []:test.com
Email Address []:
A challenge password []:123456
# 根证书机构 接受CSR请求并使用二级证书私钥签名生成证书
openssl x509 -req -in server.csr -out server.crt -CA second.crt -CAkey second.key -CAcreateserial
# 根据root.crt生成root.pem
openssl x509 -in server.crt -outform PEM -out server.pem
最终同上得
基于Netty的SSL使用
《Netty概述》中提供的https例子:
public final class HttpsHelloWorldServer {
static final int PORT = Integer.parseInt(System.getProperty("port", "8443"));
public static void main(String[] args) throws Exception {
// 1.基于netty自带的证书生成方案
// SelfSignedCertificate ssc = new SelfSignedCertificate();
// SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
// 基于一级的解决方案
//File privateKey = new File("/Users/xxx/cc/root.pkcs8");
//File certificate = new File("/Users/xxx/cc/root.crt");
//SslContext sslCtx = SslContextBuilder.forServer(certificate, privateKey).build();
// 使用三级的解决方案
File privateKey = new File("/Users/xxx/cc/server.pkcs8");
File certificate = new File("/Users/xxx/cc/chain.crt");
File rootFile = new File("/Users/xxx/cc/root.crt");
SslContext sslCtx = SslContextBuilder.forServer(certificate, privateKey).trustManager(rootFile).protocols("TLSv1.2").clientAuth(ClientAuth.NONE).build();
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(sslCtx.newHandler(ch.alloc()));
p.addLast(new HttpServerCodec());
p.addLast(new HttpServerExpectContinueHandler());
p.addLast(new HttpHelloWorldServerHandler());
}
});
Channel ch = b.bind(PORT).sync().channel();
System.err.println("Open your web browser and navigate to https://127.0.0.1:" + PORT + '/');
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
上述提供了三种方式配置ssl证书。
- 1,2的区别在于引用第三方证书
# netty中只支持pkcs8的密钥,用以下命令转换
openssl pkcs8 -topk8 -in root.key -out root.pkcs8 -nocrypt
- 1,2,3都必须手动信任证书,其中三信任root就行
双击root.crt并设置信任
再访问浏览器,仍然是不安全但会提示信任证书
- 3区别于二的方式
# 合成证书链只需要将文件按序合并即可。
cat server.crt second.crt root.crt> chain.crt
如果第三级中将chain.crt换成server.crt,netty启动时将无法识别server.crt里面的证书。因此,现在的https证书都包含着一个链
证书相关扩展名
扩展名是比较容易误导人的地方,除了.der或.pem后缀外,还有以下常见后缀:
- crt:常见于*nix系统,大多是PEM编码,也可能是DER编码
- cer:常见于Windows系统,大多是DER编码,也可能是PEM编码
- key:用户存放密钥信息,和证书一样,可能是DER编码,也可能是PEM编码;可参照PKCS#1(RFC8017)查看其具体字段和定义;但密钥明文存储方式有所危险,一般可以使用PKCS#8格式(RFC5958)进行密钥的加密,即设置一个提取密钥
- csr:证书签发请求,其实与证书内容相似,但不包含签发方信息,签发方根据CSR并添加自身的签发信息,从而生成证书文件,详情可参照(PKCS#10 RFC2314)
- pfx/p12:实际上就是将证书和私钥一并打包成一个文件,并且设置“提取密码”
- jks/keystore/truststore: 一般常见于JAVA相关应用,实际上也是和p12类似,将证书和私钥一并打包并设置“提取密码”,至于keystore和truststore只是概念上的区别,keystore一般用户表示用户或服务器证书,而truststore一般表示CA证书。
主要参考
《想不通HTTPS如何校验证书合法性来看》
《浏览器是如何验证HTTPS证书合法性的》
《您的中间证书被强制吊销了吗?》
《SSL证书生成与使用(基于Netty)》
《openssl的介绍和使用》
《自定义根证书颁发机构 CA 生成自签名证书》
《TLS初探(2)证书简介》