初识OpenSSL与简单的密码学知识~
一次通信
以Web服务为例, 当我们从应用程序向远端服务器发送一个请求的时候, 通过创建一个套接字经由随机端口发送, 经由路由设备层层转发, 服务器端监听套接字接收到之后, 应用程序将从磁盘上取得的结果通过协议栈封装成可以相应的报文格式重新托付给相同的套接字, 作为响应返回给客户端.
简单来描述就是这样. 不过协议报文在我们封装完在互联网上传输时, 大多数的应用层协议以及底层的TCP层都会有问题. 在过程中我们的数据包仅仅是加了头部啥的, 他们都是明文的. 数据是原封不动的. 这样只要是网络上能够收到这个数据包的人, 他们都可以存一个副本, 接着查看内容, 一览无余.
TCP的四层一次又一次的封装数据包, 那么为什么要封装这么多次呢, 为什么不能一步到位呢?
分层设计的目的, 由于我们互联网通信的问题很大很复杂, 而分层就是为了把这个大问题分解成小问题, 接着每个问题单独解决. 更重要的是, 上下层之间互相服务, 他们之间通过接口进行通信. 这样的又一个好处是,如果某一层出现问题, 那么我们直接修改那一层的代码就可以了, 其他的层根本不需要知道这些. 只要提供能力没啥区别就行了. 这就类似于我们局域网通信, 不论通过双绞线或者无线调制通信, 和IP包是没有什么关系的. 底层怎么变换, 上层对于服务的接口不变, 就没有什么影响.
这样太不安全啦! 那么为什么做不到加密传输呢, 为啥一定要明文呢? 我们现在使用的这些协议, 都是上个世纪的产物啊..当时能够把数据发送过去就已经很不容易了. 更别提有人要搞破坏了. 但是对于网络安全话题,美国国家安全署(NIST)定义了安全的三个方面: 保密性, 完整性, 可用性. 也就是数据要保密, 未被授权的第三方不能获取到,即使被存下来也不能被获取到内容, 数据自己的完整性, 系统的完整性, 真正授权的第三方要能够收倒且获取到内容.
使用我们现在的协议栈在互联网上进行通信的时候, 他们面临各种各样的攻击. 对于这种安全攻击, 主要有两种:
- 被动攻击: 窃听
- 主动攻击: 伪装, 重放, 消息篡改, 拒绝服务
那么为了我们的安全, 我们就要尽可能的验证, 防止篡改, 拒绝接受伪装balabala, 就是要加密, 或者 数字签名 还有访问控制 以及想办法确保 数据完整性 还有所谓的 密钥认证交换机制 , 还有比如 进行流量填充 还有 路由控制, 不走不安全的路由.
为了实现这些安全机制, 就要有安全服务来支持, 这些服务主要包括:
- 认证
- 访问控制
- 数据保密性
- 连接保密性
- 无连接保密性
- 选择域保密性
- 流量保密性
- 数据完整性
- 不可否认性
那么为了保证这些安全服务, 就要有密码算法和协议, 常见的加密算法:
对称加密 公钥加密 单向加密
认证协议
说到密码, 这个东西其实并不是程序员所研究的东西, 通常都是一些研究机构, 数学科学家, 军方在研究. 而各国都有自己秘而不宣的加密算法, 至于我们现在在生产环境中所使用的这些, 至少他们对于信息获取没有什么阻碍.
Linux上的通信安全工具
OpenSSL 和 gpg , 后者是对pgp协议的实现. 接下来我们就说说常见的加密技术吧.
对称加密
首先是对称加密技术: 其实就是说,加密和解密使用的是同一个密钥. 这里需要说的一件事是, 其实加密算法倒不是最重要的(不是说不重要啊), 算法可以公开, 但是问题在于即使知道了算法,也很难揣测出密钥是什么. 所以加密的关键是这个密钥, 比如AES-128,AES-256大家都知道算法的思想是什么,但是就是没法逆推. 那怎么解密呢, 我们有密钥啊, 对称加密使用的是一样的密钥. 这样带来的缺陷是 如果通信有n组用户, 那么就会存在n组密钥. 这样错误率就会出增大.
在早期的: DES(Data Encryption Standard), 56位的密钥. 但是在2003年前后就被攻破了, 可以非常容易的进行逆推. 所以DES就进行了改进, 变成了3DES. 后来继续征集新的安全标准, 于是出现了AES, 也就是Advance ES, 这个标准支持128.192,258,384,512bits的长度. 类似的还有BlowFish, TwoFish, IDEA, RC6, CAST5(相信你都没听说过吧..现在互联网上使用最多的也就是3DES和AES了)
特性:
- 加密解密使用的密钥相同
- 将原始数据分割成关于固定大小的块, 逐个进行加密
缺陷:
- 密钥过多
- 密钥分发有问题
公钥加密
公钥加密极大地推动了现在互联网安全. 公钥加密中, 密钥是成对出现的. 公钥(publiic key)可以公开给所有人, 私钥(private key)是自己留存的, 必须保证安全性. 但是密钥长度非常长, 通常都是1024,2048位为起点的.长则4096, 甚至还有8192位的都有. 使用公钥加密的数据, 仅仅能通过与之配对的私钥解密, 反之亦然.
常见的公钥加密有三种功能: 身份认证(数字签名), 密钥交换, 数据加密
常见的算法有: RSA[数字签名和加密都能实现], DSA(DSS)[仅能用于数字签名], ELGamal
一般情况下, 我们很少使用公钥加密来进行数据加密, 因为太长了, 光加密就需要很长时间. 基本上慢对称加密的三个数量级. 但事实上, 我们可以用来加密密钥. 这样就解决了之前对称加密的密钥分发问题. 发送方使用接收方的公钥进行密钥加密, 接着接收方使用自己的私钥进行解密. 最后就可以通过这个对称密钥进行加密通信了.
那么数字签名是怎么实现的呢? 数字签名的作用是让接收方知道发送方的身份. 过程如下: 发送方在发送前使用单向计算得到自己要发送数据的特征码, 接着使用自己的私钥加密这个特征码附在数据的尾部发送过去, 接收方先尝试使用发送方的公钥进行特征码解密, 如果能解开, 就说明是发送方的发的,但是数据是否被人篡改过呢,使用同样的单向算法进行特征码计算, 最后再进行对比. 这样就可以了.
但是这样就安全了吗? 不. 这一个过程依靠于最开始的公钥上.如果在通信线路上潜伏的人截取了最一开始的公钥, 而把自己的公钥发送过去的话, 后面就可想而知了.
所以引入CA, 也就是证书签署机构. 在一开始扔过去的公钥不是公钥本身, 而是自己的证书.证书中有他的公钥.首先找到有公信力的组织, 证明身份, 接着机构就会扔给他证书, 证书上会有自己的戳. 所以接收方先对证书进行完整性验证, 接着验证证书机构, 最后才会接收到公钥.
如果使用公钥加密, 我们只要维护一对儿密钥就行啦.
单向加密(签名)
只能加密, 不能解密. 因为主要目的是为了提取数据指纹, 用来保证数据的完整性.
特性: 定长输出(无论数据是什么长度的, 加密后的都是一样长度的), 雪崩效应(源数据的微小改变都会引起结果的巨大改变)
常见的加密算法有: MD5, SHA1, SHA256, SHA512
OK, 现在我们来说一种糅合了所有方法的通信过程, 让第三方无计可施!
首先, 发信人使用单向加密算法提取发送数据的特征码, 接着用自己的私钥加密这段特征码并附在数据的最末尾. 接着发信人使用对称密钥加密整段数据, 包括特征码在内的全部数据 (此时收信人没有解密该数据的能力–没有密钥) 接着发信人使用收信人的公钥加密自己的对称密钥并附在这段数据的最后面. 就是这样, 发送过去.
收信人收到了之后, 就可以先用自己的私钥去解密这段密钥, 如果能解密就得到了配对的对称密钥, 这样就可以继续得到数据和尾部加了密的特征码. 所以, 接着使用对方的公钥得到特征码, 再使用同样的单向加密算法进行计算, 比对指纹. 这样就完成了一次数据收发.
虽然看起来已经很理想了, 但是这样的密钥交换还是不安全啊.
所以我们又引入了一种密钥交换协议: IKE ( Internet Key Exchange ).
另外一种常见的算法是: DH ( Deffie-Hellman ) 这个算法很独特, 密钥不需要在互联网上发送.怎么实现的呢?简单的说一下:
首先, 双方为了实现密钥交换, 需要选择一个大的质数(p)和一个生成器(g) , 这个信息是公开的 通信过程也都是明文. 接着双方各生成一个保留数据(隐私, 只有自己知道, 假定为x,y) 接着发送方发送p^x%g的结果, 接收方也发送p^y%g结果. 接着双方各自进行y,x的次方运算. 结果一样! 这个结果,就是密钥.
证书和CA
一些概念:
PKI: public key Insfrastructure
签证机构: CA
注册机构: RA
证书吊销列表: CRL (revoke list)
证书存取库
X.509: 定义了证书的结构和认证协议标准 它规定一个证书需要包含下面的要素:
- 版本号
- 序列号
- 签名算法ID
- 发行者的名称
- 有效期限
- 主体名称(拥有者)
- 主体公钥
- 发行者的唯一标识
- 主体的唯一标识
- 扩展
- 发行者签名
在上面 的例子中, 我们说关于证书的一些, 其实并不能确保这些证书就是可以信任的. 所以我们还是要验证对方的证书是否可信. 首先,接收方找到证书发行者的签名. 接着,在本地我找到CA的证书使用公钥进行解密, 如果可以, 那么姑且认为这个证书可能是可信的. 接着还是, 经过了完整性验证, 才会认为是可信的.
在我们的机器上有两种类型的证书: 主机证书和个人证书 .
SSL/TLS
之前说过SSL/TLS的一些原理啥的. 但是没有怎么说说历史, 现在列在下面:
- 1995, SSL 2.0, Netscape
- 1996, SSL 3.0
- 1999, TLS 1.0 (Transport Layer Security)
- 2006, TLS 1.1 RFC 4346
- 2008, TLS 1.2
- 2015, TLS 1.3
现在nginx等已经支持TLS1.3, 而各大现代浏览器也推出TLS1.3的实验性支持. 对于SSL/TLS的简单理解, 可以参考之前的 我对SSl/TLS的理解 (已更新)
OpenSSL
OpenSSL是什么? OpenSSL开源项目, 主要包含了三个组件:
- openssl: 多用途的命令行工具
- libcrypto: 公共加密库
- libssl: 库, 实现了SSL/TLS的功能.
OK, 现在就来看一下我们的主角.openssl命令行客户端
1 | Standard commands |
支持的命令和参数都超级多! 三种命令: 标准命令, 消息摘要加密命令以及加密命令. 我们使用前两个的场合多一些.
来说一下加密命令enc吧, 比如说就用fstab为例:
1 | [root@WWW ~]# openssl enc -e -des3 -a -salt -in fstab -out fstab.ciphertext |
接下来介绍单向加密的工具: md5sum, sha1sum, sha224sum, sha256sum, … , openssl dgst
1 | [root@WWW ~]# md5sum fstab |
再认识一个MAC, 我们已经认识到很多MAC了啊hhh, 这个MAC是单向加密的一种延伸应用, 用于实现 网络传输中的数据完整性. 所使用的机制就是CBC-MAC或者HMAC(常用): 使用md5或者sha1算法. 这个玩意在集群中有很大的用处, 通过预共享密钥来证明彼此之间的身份.
还可以用来生成用户密码:
1 | [root@WWW ~]# openssl passwd -1 -salt 12345 |
还可以生成随机数:
1 | [root@WWW ~]# openssl rand -base64 10 |
还可以进行公钥加密/数字签名.
稍微扯一下随机数生成器.只要是软件的随机都是有规律可寻的, 不是完全的随机. 哪些才叫做随机呢?比如你敲击键盘两次的时间间隔, 磁盘IO中断的时间间隔等等. 操作系统维护了一个内存空间专门用来这些值存进去, 当程序需要随机数的时候, 就从这里取就行了. 这个空间, 我们把它叫做熵池 这个熵池已经作为一个device存在了. 所以有专门的虚拟设备random和urandom. 都位于/dev下. 他们的主要区别就是random只从熵池中取随机数, 如果没有了就堵塞了. 但是urandom在熵池空了的情况下就会使用软件 的方式来生成伪随机数. 这样并不安全, 但不会堵塞.
接下来看一下怎么产生一对密钥:
1 | [root@WWW ~]# openssl genrsa -out rsa.pri |