本文引用了《netty系列之对聊天进行加密》一文的部分内容,感谢原作者的分享。
在即时通讯网社区中,分享了很多篇基于Netty编写的IM聊天入门文章(比如《跟着源码学IM》系列、《基于Netty,从零开发IM》系列等),在这些文章中分享了各种IM通信算法原理和功能逻辑的实现。但是这样简单的IM聊天系统是比较容易被窃听的,如果想要在里面说点悄悄话是不太安全的。
怎么办呢?学过密码学的朋友可能就想到了一个解决办法,聊天的时候对消息加密,处理的时候再对消息进行解密。是的,道理就是这样。
但密码学本身的理论就很复杂,加上相关的知识和概念又太多太杂,对于IM入门者来说,想要快速理清这些概念并实现合适的加解密方案,是比较头疼的。
本文正好借此机会,以Netty编写的IM聊天加密为例,为入门者理清什么是PKI体系、什么是SSL、什么是OpenSSL、以及各类证书和它们间的关系等,并在文末附上简短的Netty代码实示例,希望能助你通俗易懂地快速理解这些知识和概念!
补充说明:本文为了让文章内容尽可能言简意赅、通俗易懂,尽量不深入探讨各个技术知识和概念,感兴趣的读者可以自行查阅相关资料进一步学习。
我们需要先了解一下公钥和私钥的加密标准体系PKI。
PKI的全称是Public Key Infrastructure,是指支持公钥管理体制的基础设施,提供鉴别、加密、完整性和不可否认性服务。
通俗讲:PKI是集机构、系统(硬件和软件)、人员、程序、策略和协议为一体,利用公钥概念和技术来实现和提供安全服务的、普适性的安全基础设施。
在公钥密码中,发送者用公钥(加密密钥)加密,接收者用私钥(解密密钥)解密。公钥一般是公开的,不再担心窃听,这解决了对称密码中的密钥配送问题。但是接收者依然无法判断收到的公钥是否合法(有可能是中间人假冒的)。
事实上,仅靠公钥密码本身,无法防御中间人攻击。于是,需要(认证机构)对公钥进行签名,从而确认公钥没有被篡改。加了数字签名的公钥称为公钥证书,一般简称证书。
有了证书来认证,可以有效防御中间人攻击,随之带来了一系列非技术性工作。
例如:谁来发证书?如何发证书?不同机构的证书怎么互认?纸质证书作废容易,数字证书如何作废?解决这些问题,需要制定统一的规则,即PKI体系。
PKI体系是通过颁发、管理公钥证书的方式为终端用户提供服务的系统,最核心的元素是证书。
围绕证书构成了PKI体系的要素:
总之:PKI是一个总称,既包括定义PKI的基础标准,也包括PKI的应用标准。
事实上PKI已经有两代了。
第一代的PKI标准主要是由美国RSA公司的公钥加密标准PKCS、国际电信联盟的ITU-T X.509、IETF的X.509、WAP和WPKI等标准组成。但是因为第一代PKI标准是基于抽象语法符号ASN.1进行编码的,实现起来比较复杂和困难,所以产生了第二代PKI标准。
第二代PKI标准是由微软、VeriSign和webMethods三家公司在2001年发布的基于XML的密钥管理规范也叫做XKMS。
事实上现在CA中心使用的最普遍的规范还是X.509系列和PKCS系列。
X.509系列主要由X.209、X.500和X.509组成,其中X.509是由国际电信联盟(ITU-T)制定的数字证书标准。在X.500基础上进行了功能增强,X.509是在1988年发布的。
X.509证书由用户公共密钥和用户标识符组成。此外还包括版本号、证书序列号、CA标识符、签名算法标识、签发者名称、证书有效期等信息。
而PKCS是美国RSA公司的公钥加密标准,包括了证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。它定义了一系列从PKCS#1到PKCS#15的标准。
其中最常用的是PKCS#7、PKCS#12和PKCS#10。PKCS#7 是消息请求语法,常用于数字签名与加密,PKCS#12是个人消息交换与打包语法主要用来生成公钥和私钥(题外话:iOS程序员对PKCS#12不陌生,在实现APNs离线消推送时就需要导出.p12证明,正是这个)。PKCS#10是证书请求语法。
SSL是网景公司(Netscape)设计,但IETF将SSL作了标准化,即RFC2246,并将其称为TLS(Transport Layer Security),其最新版本是RFC5246、版本1.2。
实际上:TLS是IETF在SSL3.0基础上设计的,相当于SSL的后续版本。所以我们通常都是SSL/TLS放一起说。
OpenSSL是一个开放源代码的软件库,应用程序可以使用这个包来进行安全通信,它包括代码、脚本、配置和过程的集合。例如:如果您正在编写一个需要复杂安全加密的软件,那么只有添加一个安全加密库才有意义,这样您就不必自己编写一大堆复杂的加解密函数(而且密码学本身很复杂,要写好它们并不容易)。
其主要库是以 C 语言所写成,实现了基本的加密功能,实现了 SSL 与 TLS 协议。
OpenSSL整个软件包大概可以分成三个主要功能部分:
OpenSSL可以从其官网上下载,地址是:https://www.openssl.org/source/,感兴趣的读者可以自行下载安装研究。
操作过证书的朋友可能会对各种证书类型眼花缭乱,典型的体现就是各种不同的证书扩展名上,一般来说会有DER、CRT、CER、PEM这几种证书的扩展名。
以下是最常见的几种:
下面的命令可以用来查看文本证书内容:
1 2 3 | openssl x509 -in cert.pem -text -nooutopenssl x509 -in cert.cer -text -nooutopenssl x509 -in cert.crt -text -noout |
下面的命令可以用来查看二进制证书内容:
1 | openssl x509 -in cert.der -inform der -text -noout |
下面是常见的PEM和DER相互转换。
PEM到DER的转换:
1 | openssl x509 -in cert.crt -outform der-out cert.der |
DER到PEM的转换:
1 | openssl x509 -in cert.crt -inform der -outform pem -out cert.pem |
补充说明:上述命令中用到的openssl程序,就是本文中提到的OpenSSL开源库提供的程序。
事实上这个标题是不对的,Netty中启动的server还是原来那个server,只是对发送的消息进行了加密解密处理。也就是说添加了一个专门进行SSL操作的Handler。
netty中代表ssl处理器的类叫做SslHandler,它是SslContext工程类的一个内部类,所以我们只需要创建好SslContext即可通过调用newHandler方法来返回SslHandler。
让服务器端支持SSL的代码:
1 2 3 | ChannelPipeline p = channel.pipeline();SslContext sslCtx = SslContextBuilder.forServer(...).build();p.addLast("ssl", sslCtx.newHandler(channel.alloc())); |
让客户端支持SSL的代码:
1 2 3 | ChannelPipeline p = channel.pipeline();SslContext sslCtx = SslContextBuilder.forClient().build();p.addLast("ssl", sslCtx.newHandler(channel.alloc(), host, port)); |
netty中SSL的实现有两种方式,默认情况下使用的是OpenSSL,如果OpenSSL不可以,那么将会使用JDK的实现。
要创建SslContext,可以调用SslContextBuilder.forServer或者SslContextBuilder.forClient方法。
这里以server为例,看下创建流程。
SslContextBuilder有多种forServer的方法,这里取最简单的一个进行分析:
1 2 3 | public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) { return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile);} |
该方法接收两个参数:
1)keyCertChainFile是一个PEM格式的X.509证书文件;
2)keyFile是一个PKCS#8的私钥文件。
同样的在client中支持SSL也需要创建一个handler。
客户端的SslContext创建代码如下:
1 2 | // 配置 SSL.final SslContext sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build(); |
上面的代码我们使用了一个InsecureTrustManagerFactory.INSTANCE作为trustManager。
什么是trustManager呢?
当客户端和服务器端进行SSL连接的时候,客户端需要验证服务器端发过来证书的正确性。
通常情况下,这个验证是到CA服务器中进行验证的,不过这样需要一个真实的CA证书环境,所以在测试中,我们使用InsecureTrustManagerFactory,这个类会默认接受所有的证书,忽略所有的证书异常。
当然:CA服务器也不是必须的,客户端校验的目的是查看证书中的公钥和发送方的公钥是不是一致的,那么对于不能联网的环境,或者自签名的环境中,我们只需要在客户端校验证书中的指纹是否一致即可。
netty中提供了一个FingerprintTrustManagerFactory类,可以对证书中的指纹进行校验。
该类中有个fingerprints数组,用来存储安全的授权过的指纹信息。通过对比传入的证书和指纹,如果一致则校验通过。
使用openssl从证书中提取指纹的步骤如下:
1 | openssl x509 -fingerprint -sha256 -in my_certificate.crt |
上面我们对Netty聊天用到的加密技术和相关概念进行了梳理,我来简单这些概念之间的关系。
这些概念之间的关系,简单来说就是:
而具体到Netty中的聊天加密,那就是应用了上述的PKI体系,基于SSL协议,在OpenSSL等开源库的帮助下实现的安全程序。
http://www.52im.net/thread-2663-1-1.html
[7] 基于Netty,从零开发IM
http://www.52im.net/thread-3963-1-1.html
[8] TCP/IP详解(全网唯一在线阅读版)
http://www.52im.net/topic-tcpipvol1.html?mobile=no
[9] 快速理解TCP协议一篇就够
http://www.52im.net/thread-1107-1-1.html
[10] Netty-4.1.x 源码(在线阅读版)
http://docs.52im.net/extend/docs/src/netty4_1/
[11] Netty-4.1.x API文档(在线版)
http://docs.52im.net/extend/docs/api/netty4_1/