上周收到百度地图开发平台的一封邮件,说苹果AppStore要求:2017年1月1日起,所有上线苹果App Store的应用都必须启用 App Transport Security(ATS)安全功能。AppTransportSecurity(ATS)是苹果在iOS9中引入的一项隐私保护功能,屏蔽明文HTTP资源加载,连接必须经过更安全的HTTPS协议。(想想我们的一个客户端,用的百度地图好长时间没更新了,到时再不更新定位功能岂不是歇菜了?)
最近看新闻,说网站从Http迁移到Https也是一种趋势。
据此我们有理由相信:掌握点安全方面的编程还是有必要的!
本文档的目标就是为了让像我这样的人也能够实现完整编码,而不是仅仅摸下Hello World!(最终代码下载)
这里主要是介绍基于Netty框架,开发Socket、Http方面的程序,包括服务端和客户端,并支持SSL/TLS;以及如何使用OpenSSL、Keytool工具制作证书;同时,介绍下SSL/TLS协议、Wireshark抓包工具。此外,如果有可能,介绍一下Tomcat服务端和Netty客户端、Android客户端、iOS客户端之间的安全通信。
本次试验环境是在Mac、Window系统上操作完成,用Mac也仅是方便了使用OpenSSL工具来制作CA证书——在Ubuntu上应该也方便。编程语言采用Java——Netty框架就是Java写的,这个没办法,所以你得会点Java编程,要不本文档只有制作证书部分可以参考。
这里得提下Java的历史——那时哥还年轻。以前百姓总是说Java性能不行,总是不能来硬的——一涉及到服务性,就需要有指针可以到处插的C或C++来开天辟地。为啥这样?输入输出工作不到位咯,别人一个时辰接客38个,你却只接2个。只能说Java IO接口就是不好,开始通信模式还采用BIO模式,即同步阻塞——也就是一个一个的来,虽然显得彬彬有礼有秩序,但客户不爽不痛快。还好Java年轻力壮后劲足,不久来了个NIO非阻塞模式,后来又演变出AIO异步非阻塞模式,这下可好了,就算接客300头,腿不酸腰也不疼了——所以现在就算在天上人间这样的系统后台里看到她Java亮丽的身影和出色的功夫也不应该感到惊讶。
人道是好事多磨,Java固然有了质的飞跃,但是她自带的NIO类库和API,又繁又杂很麻烦,就算再强壮潇洒的白码猿黑码猿也会搞出病来——反正我就不敢惹。
于是,她应运而生。她强且大,灵活性能好而不失成熟稳定,是活跃的一线明星(接待过好多不同行业的商业客户),最重要的是她天生简单——无论北京人,还是海南人,见到她都像见到芙蓉姐一样感到自然和舒畅。因此,我们没有理由忽略她,她就是Netty,一款异步的事件驱动的网络应用框架和工具,用于快速开发可维护的高性能、高扩展性协议服务器和客户端。
现在先去逛一下Netty的窑子:https://github.com/netty/netty/ ,邂逅一下代码例子,看能否擦出什么火花。沟通始于聊天。里面有个叫“securechat”的,各位客官,不妨也瞧瞧:
(图1)看这芳名,大概可猜出几分的意思,里面有服务,有前台,也有SSL安全措施,但只是用了她自家便宜的安全措施,而不是客户定制的,这个后面再说,不影响这里氛围。
首先看 SecureChatServer 类,它开了两个舞池NioEventLoopGroup,一主一从————好像一个专门接客、一个专门干活,配置好通道Channel、安全措施,并绑好门窗端口,就开张营业坐等客人来了。
再看SecureChatServerHandler类,生意的好坏就看它了,一有客人接入,深情道声“欢迎光临,可开发票”,接下来与客人的卿卿我我,好像都离不开它了。
最后看SecureChatServerInitializer类,他比较纯真,就是吹填管道ChannelPipeline,需要什么安全措施,就往里面塞;跟客人定好什么对头暗号编码解码方法,也往里面塞;需要什么其它附加服务也往里面塞。
正经点来说主要关注四个类:Channel、ChannelHandler、ChannelPipeline、ChannelHandlerContext。
先看下它们关系图:
(图2)
(图3)
用Netty编写网络程序的时候,很少直接操纵Channel,而是通过ChannelHandler来间接操纵Channel。
ChannelHandler又分为 ChannelInboundHandler和ChannelOutboundHandler两大类。其中ChannelInboundHandler是用来接收并处理(解码)另一方发来的数据,ChannelOutboundHandler是用来处理数据(编码)并发给另一方。
Netty中可以注册多个Handler,ChannelInboundHandler按照注册的先后顺序执行;ChannelOutboundHandler按照注册的先后顺序逆序执行。
ChannelPipeline可以看成是一个ChandlerHandler的链表,当需要Channel进行某种处理的时候,Pipeline负责依次调用每一个Handler进行处理。每个Channel都有一个属于自己的Pipeline,调用Channel类的pipeline()方法可以获得该Channel的Pipeline,调用Pipeline的channel()方法可以获得该Pipeline所属的Channel。
ChannelPipeline并不是直接管理ChannelHandler,而是通过ChannelHandlerContext来间接管理。在Pipeline内部,Context组成了一个双向链表,注册的各个handler就依次给连起来,但收到另一方消息,依次顺着链表,取出合适的handler来处理。
(图4)
先看看网络分层模型:
(图5)
(图6)
可以看出HTTP协议就是建立在TCP协议之上。
说到协议,顺便说下网络包分析工具:Wireshark。这里需要运行一个HTTP服务端和一个客户端,或打开一个网页,以便抓包。
(图7)
SSL是安全套接层(Secure Socket Layer)的缩写;
TLS表示传输层安全(Transport LayerSecurity)的缩写。
SSL最初由网景公司提出,最初目的是为了保护web安全,现在用来提高传输层的安全。
TLS是IETF基于SSLv3制定的标准,两者基本一致,只有少许的差别。
使用SSL/TLS的通信,就是为了防止信息被第三方窃听、篡改、冒充。
SSLv3/TLS处于传输层和应用层之间,分为握手层和记录层。
握手过程可以分成两种类型:
1)SSL/TLS 单向认证,客户端会认证服务器端身份,而服务器端不会去对客户端身份进行验证。(想想在浏览器访问一些Https网站)
2)SSL/TLS 双向认证,就是双方都会互相认证,也就是两者之间将会交换证书。(想想银行客户端)
SSL/TLS所处位置:
说明:Server Key Exchange 跟所选加密算法有关,如果是RSA算法则不需要此步骤,若是DSA算法则需要。certificaterequest、certificate和certificate_verify在双向认证时才有,其中certificate_verify是客户端用自己的私钥签名前面握手消息的结果,给服务端认证客户端的证书certificate是否合法。
上面说了这么多,我想还是运行一下最终代码,抓包看看实际效果,也许更直观。
本人是在Mac上运行服务端,在虚拟机(window)上运行客户端和Wireshark。
(操作过程省去一万字)
1 使用keytool自签名制作证书:
keytool是JDK自带工具。
1.1 单向认证
1) 生成Netty服务端私钥和证书仓库命令:
keytool -genkey-alias sserver -keysize 2048 -validity 365 -keyalg RSA -dname "CN=server,OU=HR, O=ESHORE, L=GZ, ST=GD, C=CN" -keypass servers -storepass servers-keystore server_s.jks
2) 生成Netty服务端自签名证书:
keytool -export-alias sserver -keystore server_s.jks -storepass servers -file server_s.cer
3) 生成客户端的密钥对和证书仓库,命令如下:
keytool -genkey-alias sclient -keysize 2048 -validity 365 -keyalg RSA -dname "CN=client,OU=HR2, O=ESHORE, L=GZ, ST=GD, C=CN" -keypass clients -storepass clients-keystore client_s.jks
4) 将Netty服务端的证书导入到客户端的证书仓库中,命令如下:
keytool -import-trustcacerts -alias sserver -file server_s.cer -storepass clients -keystore client_s.jks
1.2 双向认证(接上面的单向认证)
1) 生成客户端的自签名证书:
keytool -export -alias sclient -keystore client_s.jks -storepass clients -file client_s.cer
2) 将客户端的自签名证书导入到服务端的信任证书仓库中:
keytool -import -trustcacerts -alias sclient -file client_s.cer -storepass servers -keystore server_s.jks
2 基于第三方CA认证
需要安装工具:OpenSSL
先准备必要的目录和文件:
mkdir -p ./demoCA/{private,newcerts}
touchdemoCA/index.txt
echo 01 >demoCA/serial
2.1 服务端证书制作:
这里根据两种算法:RSA和DSA 分别建立证书;如果不指明DSA,则属于RSA建立步骤。
步骤1:利用OpenSSL生成CA证书:
openssl req -new -x509 -keyout ca.key -out ca.crt -days 365
或分步:
openssl genrsa-des3 -out ./demoCA/private/cakey.pem 2048
openssl req -new -days 365 -key ./demoCA/private/cakey.pem -out careq.pem
openssl ca -selfsign -in careq.pem -out ./demoCA/cacert.pem
采用DRA算法建立:
/*生成1024位的密钥参数*/
openssl dsaparam-out DSAP.pem 1024
/*生成密钥并使用des3加密存储*/
openssl gendsa -out./demoCA/private/cakey_DSA.pem -des3 -passout pass:cakey DSAP.pem
openssl req -new -days 365 -key ./demoCA/private/cakey_DSA.pem -out careq_DSA.pem
/*执行下面命令前,需要把./demoCA/private/cakey_DSA.pem文件名改为cakey.pem */
openssl ca-selfsign -in careq_DSA.pem -out ./demoCA/cacert_DSA.pem
步骤2:生成Netty服务端私钥和证书仓库命令:
keytool -genkey -alias sserver -keysize 2048 -validity 365 -keyalg RSA -dname "CN=server,OU=HR, O=ESHORE, L=GZ, ST=GD, C=CN" -keypass serverkey -storepass servers -keystore server.jks
注意:上面命令中,由于-keypass和-storepass 密码设置不一样,导致服务端在接收到客户端连接请求时,报错:java.security.UnrecoverableKeyException: Cannot recover key
可以通过下面命令,把两个密码改为一样(都为servers),即可解决:
keytool -keypasswd -new servers -keystore server.jks -storepass servers -alias sserver -keypass serverkey
采用DRA算法建立:
keytool -genkey -alias sserver -keysize 1024 -validity 365 -keyalg DSA -dname "CN=server_DSA, OU=HR, O=ESHORE, L=GZ, ST=GD, C=CN" -keypass servers -storepass servers -keystore server_DSA.jks
步骤3:生成证书签名请求:
keytool -certreq-alias sserver -sigalg MD5withRSA -file server.csr -keypass serverkey -storepass servers -keystore server.jks
采用DRA算法建立:
keytool -certreq -alias sserver -sigalg SHA1withDSA -file server_DSA.csr -keypass servers -storepass servers -keystore server_DSA.jks
步骤4:用CA私钥进行签名:
openssl ca -inserver.csr -out server.crt
采用DRA算法建立:
openssl ca -inserver_DSA.csr -out server_DSA.crt -cert ./demoCA/cacert_DSA.pem -keyfile ./demoCA/private/cakey_DSA.pem
步骤5:导入信任的CA根证书到keystore
keytool -import -v -trustcacerts -alias ca_root -file ./demoCA/cacert.pem -storepass servers -keystore server.jks
采用DRA算法建立:
keytool -import -v -trustcacerts -alias ca_root -file ./demoCA/cacert_DSA.pem -storepass servers -keystore server_DSA.jks
步骤6:将CA签名后的server端证书导入keystore
keytool -import -v -alias sserver -file server.crt -keypass servers -storepass servers -keystore server.jks
采用DRA算法建立:
keytool -import -v -alias sserver -file server_DSA.crt -keypass servers -storepass servers -keystore server_DSA.jks
2.2 客户端证书制作:
步骤1:生成客户端密钥对:
keytool -genkey -alias sclient -keysize 2048 -validity 365 -keyalg RSA -dname "CN=client,OU=HR2, O=ESHORE, L=GZ, ST=GD, C=CN" -keypass clients -storepass clients-keystore client.jks
采用DRA算法建立:
keytool -genkey -alias sclient -keysize 1024 -validity 365 -keyalg DSA -dname"CN=client_DSA, OU=HR2, O=ESHORE, L=GZ, ST=GD, C=CN" -keypass clients -storepass clients -keystore client_DSA.jks
步骤2:生成证书签名请求:
keytool -certreq -alias sclient -sigalg MD5withRSA -file client.csr -keypass clients -storepass clients -keystore client.jks
采用DRA算法建立:
keytool -certreq -alias sclient -sigalg SHA1withDSA -file client_DSA.csr -keypass clients -storepass clients -keystore client_DSA.jks
步骤3:用CA私钥进行签名:
openssl ca -in client.csr -out client.crt
采用DRA算法建立:
openssl ca -in client_DSA.csr -out client_DSA.crt -cert ./demoCA/cacert_DSA.pem -keyfile ./demoCA/private/cakey_DSA.pem
步骤4:导入信任的CA根证书到keystore:
keytool -import -v -trustcacerts -alias ca_root -file ./demoCA/cacert.pem -storepass clients -keystore client.jks
采用DRA算法建立:
keytool -import -v -trustcacerts -alias ca_root -file ./demoCA/cacert_DSA.pem -storepass clients -keystore client_DSA.jks
步骤5:将CA签名后的client端证书导入keystore:
keytool -import -v -alias sclient -file client.crt -keypass clients -storepass clients -keystore client.jks
采用DRA算法建立:
keytool -import -v -alias sclient -file client_DSA.crt -keypass clients -storepass clients -keystore client_DSA.jks
证书制作完了,那在Tomcat中如何配置,使应用服务支持Https访问?其实也就是配置server.xml文件,下面是一个单向认证例子:
clientAuth="false"
disableUploadTimeout="true"
enableLookups="true"
keystoreFile="xxx/server.jks"
keystorePass="xxx"
maxSpareThreads="75"
maxThreads="200"
minSpareThreads="5"
port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
scheme="https"
secure="true" sslProtocol="TLS" />
keystoreFile就是制作的证书仓库文件(完整文件绝对路径),keystorePass是对应仓库密码。
如果想配置双向认证的,则在上面配置文件基础上再加上一项truststoreFile,并把clientAuth的值改为true,变为:
clientAuth="true"
disableUploadTimeout="true"
enableLookups="true"
keystoreFile="xxx/server.jks"
keystorePass="xxx"
truststoreFile="xxx/server.jks"
maxSpareThreads="75"
maxThreads="200"
minSpareThreads="5"
port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
scheme="https"
secure="true" sslProtocol="TLS" />
按上面建立仓库文件步骤,这里的truststoreFile是可以跟keystoreFile一样 的。
这里也可以启动一个Tomcat应用来演示一下,试试用Netty开发的客户端连接Tomcat的Https服务,再用浏览器打开该地址,看结果如何;单向认证、双向认证配置对应的结果又如何。
Android客户端的,参考:http://blog.csdn.net/yuxiaohui78/article/details/42117785
iOS客户端的,参考: https://github.com/fengling2300/networkTest
(未完待续...)
1、Netty系列之Netty安全性:
http://www.infoq.com/cn/articles/netty-security
2、Netty 各个类的讲解:
http://blog.csdn.net/z69183787/article/details/52623501
3、用Wireshark 图解 TCP 三次握手:
http://blog.jobbole.com/108194/?utm_source=blog.jobbole.com&utm_medium=relatedPosts
4、SSL协议详解:
http://kb.cnblogs.com/page/162080/
5、使用wireshark观察SSL/TLS握手过程--双向认证/单向认证:
http://blog.csdn.net/fw0124/article/details/40983787
6、HTTPS到底是个啥玩意儿:
http://blog.csdn.net/zgwangbo/article/details/50889623
7、图解SSL/TLS协议:
http://blog.csdn.net/fw0124/article/details/40875629
8、Handler的执行顺序:
https://my.oschina.net/jamaly/blog/272385
9、基于OpenSSL 的 CA 建立及证书签发:
http://rhythm-zju.blog.163.com/blog/static/310042008015115718637/
10、OpenSSL 非对称加密算法DSA命令详解:
http://www.linuxidc.com/Linux/2016-04/130493.htm