1.加密算法

对于搞编程的人来说,对加密肯定再熟悉不过了。弄懂各种加密算法的特点以及使用加密算法是每个程序猿必须具备的技能。读者有兴趣的话也可以深入了解加密算法底层的原理。
常见常用的安全领域下的加密算法有三种:

  • 哈希算法
  • 对称加密算法
  • 非对称加密算法

    哈希算法

    哈希算法是单向算法,不可逆。
    哈希算法主要的的特点有:

  • 能将任意长度的输入变换成固定长度的输出

  • 相同的输入一定得到相同的输出
  • 不同的输入大概率得到不同的输出

上述特点意味着两个不同的输入有一定可能会得到相同的输出,这就是哈希碰撞。哈希碰撞是一定有可能出现的,只是概率会非常非常小。因为输出的长度固定,输入的可以是任意长度,无限的输入对应有限的输出,必然会产生哈希碰撞。
常见的哈希算法有

  • MD5
  • SHA-1、SHA-256、SHA-512 | 哈希算法 | 输出长度 | | :—-: | :—-: | | MD5 | 16 字节 | | SHA-1 | 20 字节 | | SHA-256 | 32 字节 | | SHA-512 | 64 字节 |

哈希算法常见应用场景

  • 用户敏感信息加密。为了防止用户重要信息泄露,通常会将用户敏感信息(例如用户登录密码)经过哈希加密后,再进行数据库持久化存储。
  • 判断下载文件是否被篡改、是否完整。在 OTA(远程固件升级)的时候,为了防止文件被篡改或者不完整,会将文件进行哈希加密计算得到的值与正确值进行比对,如果相同则说明文件完整。

    对称加密算法

    对称加密算法可逆。在对称加密过程中,将原始数据分割成固定大小的块,经过秘钥和加密算法逐个加密后,发送给接收方。接收方收到密文后,使用同一个秘钥将密文解密成铭文读取。
    对称加密算法有以下几个特点:

  • 加密和解密必须使用相同的秘钥

  • 加密解密速度比较快,加密效率高,数据量比较大时也适合使用

常见常用的对称加密算法有:

算法 秘钥长度(位) 备注
DES 56/64 已破解,不在安全
3DES 112/168/128/192 计算秘钥时间太长,加密效率不高,基本上也不用
AES 128/192/256 最常用的对称加密算法
IDEA 128 常用的电子邮件加密算法

对称加密算法中还有两个关键的名词:

  • 工作模式。常用的工作模式有:ECB、CBC、CFB、OFB
  • 填充方式 Padding。常见的填充方式有:NoPadding、PKCS5Padding 和 PKCS7Padding

    非对称加密算法

    非对称加密算法是日常工作中使用频率最高的加密算法。非对称加密算法有一个密钥对:公钥和私钥。非对称加密算法中加解密使用的是不同的秘钥。用公钥加密必须得用私钥解密。
    也有一种场景是用私钥加密,公钥解密。私钥是保密的,公钥是公开的,用私钥加密相当于所有人都可以解密。它的意义在于让其他人确定消息是由私钥持有者发出的。用私钥加密的过程也称为签名,这一点在后面讲 https 原理的时候会详细介绍。
    常用的非对称算法有:

  • RSA

  • DSA
  • ECDSA

RSA 是目前应用最广泛的非对称秘钥加密算法。国内的支付宝就是通过 RSA 算法来进行签名验证。秘钥的长度决定着它的安全强度。目前主流可选秘钥长度为 1024、2048 和 4096 位。支付宝的官方文档上推荐的是 2048 位,虽然秘钥更长更安全,但是也意味着会产生更大的性能开销。

编码算法

编码算法中主要有 URL 编码和 Base64 编码。
URL 编码
URL 编码是浏览器发送数据给服务器时使用的编码,它通常附加在 URL 的参数部分。例如:

http://www.ijuvenile.com?name= %E5%BC%A0%E6%AD%A6

URL 编码规则
如果字符在 ASCII 码中存在,则保持不变
如果是其他字符,先转化成 UTF-8 编码,然后对每个字节以%xx 表示,xx 表示 16 进制 Hex 值
Base64 编码
Base64 编码是对二进制数据进行编码,表示成文本格式。
Base64 编码可以把任意长度的二进制数据变为纯文本,且编码后的数据只包含 A-Z、a-z、0-9、+、/、=这些字符。它的原理是把 3 字节的二进制数据按 6bit 一组,用 4 个 int 整数表示,然后查表,把 int 整数用索引对应到字符,得到编码后的字符串。
6bit 可以表示的范围是 0-63。上述的查表就是将 0-63 这些整数数字和 A-Z、a-z、0-9、+、/、=这些字符意义对应。字符 A-Z 对应索引 0-25,字符 a-z 对应索引 26-51,字符 0-9 对应索引 52-61,最后两个索引 62、63 分别用字符+和/表示。
这样子会存在一个问题,当字节数不是 3 的倍数的时候,最后余下的字节数怎么办?答案是最后补上一个或者两个 0x00。编码后,在结尾加一个 = 表示补充了 1 个 0x00,加两个 = 表示补充了 2 个 0x00,解码的时候,去掉末尾补充的一个或两个 0x00 即可。

2.那些证书

实际开发过程中,我们会遇到不同扩展名的文件,例如 pem、der、cer、csr、pfx、p12、key 等等。
讲这些不同扩展名文件的特点和区别之前,先说说另外一个词——约定大于配置。例如 pem 扩展名结尾的文件通常表示该文件是一个 PEM 编码的证书,der 扩展名结尾的证书通常表示该文件是一个 DER 编码的证书。将一个 DER 编码的证书扩展名改为 pem 也没错,只是不规范。命名不规范,程序猿两行泪。
上面提到了 PEM 编码和 DER 编码,它们是两种不同的编码格式:

  • PEM 编码。纯文本文件,以—-BEGIN XXX—-开头,—-END XXX—-结尾,内容为 Base64 编码格式
  • DER 编码。二进制文件格式,用电脑中记事本打开会是一堆乱码,不可读

X.509 是一种证书标准,主要定义了证书中应该包含哪些内容。我们熟悉的 SSL 证书就是遵循着这种证书标准。
X.509 证书标准定义的两种编码格式为 PEM 编码和 DER 编码,这两种编码方式可以使用 openssl 相互转换。
无论扩展名如何,只要是符合 X.509 标准的证书,其内部编码格式只能是 PEM 和 DER 这两种编码格式中的一种。
再来谈谈这些不同扩展名文件的特点和区别:

  • pem、der、crt、cer。证书文件,证书格式 PEM 和 DER 编码都有可能。通常情况下 pem 和 cer 用 PEM 编码,der 和 crt 用 DER 编码。证书中包含着公钥等信息
  • key。秘钥文件,并非证书,通常来存放一个公钥或者私钥。编码可能是 PEM,也有可能是 DER。
  • csr。证书签名请求,并不是一个证书。用户向权威证书颁发机构获取签名证书的申请,其核心内容包含一个 RSA 公钥和其他附带信息。
  • pfx。也写作 p12。全称 PKCS12,它是公钥加密标准系统中的一种。它用来将包含了公钥的证书和私钥以及其他相关信息打包进行交换。简单可以理解为:一份.pfx 文件 = 证书 + 私钥。通常情况下,pfx 文件都会有一个提取密码,用于提取文件中的重要信息。
  • jks。即 Java Key Stroage。可以利用 Java 自带的 keytool 生成 jks 扩展名的文件,也可以将 pfx 扩展名文件转化成 jks 的。

公钥文件和证书文件有什么区别
证书一般包含以下内容:

  • 证书拥有者的公钥
  • 证书的有效期
  • 主体标识符信息(如名称和电子邮件地址)
  • 颁发者的数字签名
  • 数字证书的序列号

而公钥文件中仅仅包含了一个 RSA 公钥,一般以——-BEGIN RSA PUBLIC/PRIVATE KEY——-开头,以——-END RSA PUBLIC/PRIVATE KEY——-。

3.HTTPS 单向和双向认证流程

如果有人问你 HTTPS 的实现原理,一开始你可以回答:用非对称加密算法传递对称加密算法的秘钥和加密方式。然后在阐述 HTTPS 单向认证和双向认证的流程。
HTTPS 单向认证

  1. 客户端向指定域名的服务器发送 HTTPS 请求,请求内容包括
  • 客户端支持的 SSL/TLS 协议版本列表
  • 支持的对称加密算法列表
  • 随机数 A
  1. 服务器收到请求后,回应客户端,内容包括
  • 双方都支持的 SSL/TLS 的最高版本,如果客户端的 SSL/TLS 服务端都不支持,则直接不允许访问
  • 双方都支持的最安全的对称加密算法
  • 公钥证书
  • 服务器端生成的随机数 B
  1. 客户端收到公钥证书检查证书的合法性,主要包括
  • 检查证书是否过期
  • 检查证书是否吊销
  • 证书是否可信。客户端会有一个信任库,里面保存了该客户端信任的 CA 证书,如果收到的证书签发机构不在信任库中,则客户端会提示用户证书不可信。
    • 若客户端是浏览器,浏览器开始查找操作系统中已内置的受信任的证书颁发机构 CA,与服务器发来的证书中的颁发者 CA 比较,用于检查证书是否为合法机构颁发。
    • 若客户端为程序。以 JAVA 客户端为例,需要配置信任库文件,以判断证书是否可信。如果没有设置,则默认使用 JDK 自带的证书库。
      如果验证了证书合法,则取出公钥,对服务器发来的证书中的签名进行解密得到 X,使用相同的 hash 算法计算服务器发来证书的 hash 值得到 Y,比对 X 和 Y,若相同则确定证书是服务器发来的。
  • 检查收到的证书中的域名与请求的域名是否一致
    • 若客户端是浏览器,则会出现警告,用户可以跳过,也可以进行访问
    • 若客户端是程序,这一项配置可以不检查。
      证书验证通过后,客户端生成随机数 C,对随机数 C 用公钥加密,发送给服务器。
  1. 服务器的最后回应
    服务器用私钥解密,得到随机数 C。此时,服务器和客户端都拿到了随机数 A,B,C,双方通过这三个随机数使用 DH 密钥交换算法得到相同的对称加密秘钥,这个密钥作为后续数据传输时对称加密使用的密钥。服务器回应客户端,握手结束,可以采用对称加密传输数据了。

    DH 秘钥交换算法思想:不通过网络传输密钥,而是双方协商一个共同的密钥生成方式,DH 算法通过数学定律保证了双方各自计算出的密钥是相同的。

用一张流程图更清晰地展示 HTTPS 单向认证流程:
证书那些事儿 - 图1
HTTPS 双向认证
单向认证中,客户端会验证服务端,服务端对来访的客户端身份不做任何限制。如果服务端的服务只要要特定的客户端访问,那么就可以采用双向认证。双向认证在实际环境下用的较少。
HTTPS 双向认证在单向认证的基础上,增加了:

  1. 上述第二步中,服务器回应给客户端的消息中,会要求客户端提供客户端的证书
  2. 上述第三步中,客户端在验证完服务端之后,会发送给服务端自己的证书文件
  3. 服务端收到客户端的证书后,确认这个证书是否在自己的信任库中,如果验证不通过则会拒绝连接,验证通过则继续后续交互。

值得注意一点的是,使用单向认证还是双向认证,是服务端决定的。

4.openssl 简单使用

openssl 是一个开源项目,其组成主要包括以下三个组件:

  • openssl:多用途的命令行工具
  • libcrypto:加密算法库
  • libssl:加密模块应用库,实现了 SSL 和 TLS

    openssl 生成公私钥秘钥对文件

    首先需要使用 genrsa 标准命令生成私钥,再使用 rsa 命令从私钥中提取公钥

    openssl genrsa -out private_key.key 2048

在做 RSA 加解密时,不同语言使用的公私钥文件采用了不同的标准生成的私钥文件。例如 python 使用的是 PKCS1 标准,JAVA 使用的是 PKCS8 标准。openssl 可以将证书在任何标准格式之间转化。
JAVA 需要使用的话私钥必须要经过 PKCS#8 编码

openssl pkcs8 -topk8 -inform PEM -in private_key.key -outform PEM -out pkcs8_private_key.key -nocrypt

在相同目录下,根据私钥生成对应的公钥

openssl rsa -pubout -in private_key.key -out public_key.key

可以发现默认生成的秘钥格式为 PEM 编码格式,PEM 编码格式可以和 DER 编码格式互换。

openssl rsa -in private_key.key -outform der -out private_key_der.key openssl rsa -in private_key_der.key -inform der -out pem -out private_key_pem.key 证书那些事儿 - 图2

openssl 生成自签名证书

我们先说说自签的 SSL 证书和 CA 认证的 SSL 证书的区别:

  • 自签 SSL 证书免费,CA 证书一般收费。阿里云上可以申请 10 个免费的 SSL 证书,有效期为一年,但免费的证书只支持单域名。
  • 自签 SSL 证书不受客户端操作系统信任,所以自签 SSL 证书要想使用在 HTTPS 网站上时,客户端必须在操作系统中手动导入证书,否则浏览器会提示不安全。操作系统中默认内置 CA 认证的 SSL 证书,客户端不需要导入证书就可以访问 HTTPS 网站。

openssl 生成一个 RSA 私钥

openssl genrsa -des3 -out server.key 2048

会要求输入安全密码,此秘钥用于加密 key 文件。这里随便输入一个 123456。
如果不需要安全密码,可以使用下面这个命令

openssl rsa -in server.key -out server-nopassword.key

生成证书请求文件

openssl req -new -key server.key -out server.csr

按照提示一步一步输入个人信息即可。注意有一个 common name,可以写名字或者域名。如果为了 HTTPS 请求,这个必须和域名一样,为什么要一样可以参考 HTTPS 单向认证中客户端检查证书的合法性原理。
对上一步生成的证书请求进行签名生成公钥证书

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

生成好的自签名证书
证书那些事儿 - 图3
.key:私钥文件
.csr:证书请求文件
.crt:公钥证书
默认生成的证书和文件都是 PEM 编码格式,可以使用 openssl 命令转化成 DER 编码格式的。开发者就可以拿到这些证书和文件进行相关配置,来实现网站的 HTTPS 访问,自签名的 SSL 证书一般不会用在生产环境下。本文第六结会详细讲到如何申请 CA 机构颁发的浏览器受信任的 SSL 证书。

5.keytool 生成自签名证书

JDK 中的 keytool 是一个证书管理工具,可以快速的生成自签名证书。能达到的最终效果和上面的用 openssl 生成自签名证书效果一致。

keytool -genkey -alias server -keypass 123456 -keyalg RSA -keysize 2048 -keystore server.jks -validity 365

命名解释:

  • -alias server(别名,可以随便起名字)
  • -keypass 12345(别名密码)
  • -keyalg RSA(生证书的算法名称)
  • -keysize 2048(密钥长度)
  • -validity 365(证书有效期,天单位)
  • -keystore server.jks(指定生成证书的位置和证书名称)
  • -storepass 123456(获取 keystore 信息的密码)

按照提示一步一步输入个人信息即可。
生成公钥证书

keytool -alias server -keypass 123456 -exportcert -keystore server.jks -file server.cer

6.物联网场景下消息传输建立 SSL 安全连接

MQTT 是当下适用最为广泛的物联网通讯协议。同 HTTP 一样,MQTT 也是应用层协议。
EMQX 是业界相对成熟的 MQTT Broker。本章节主要讲解如何基于 EMQX 建立 SSL 安全连接。
8883是 EMQX 默认的 MQTT SSL 安全连接端口。
证书那些事儿 - 图4
用章节 4-2 中的方式生成自签名证书,将证书放置在/etc/emqx/mycert 目录下。EMQX 有默认的自签名证书,在 cert 文件目录下,也可以使用。
证书那些事儿 - 图5

server.crt:公钥证书 server-nopassword.key:不带密码的私钥文件

修改 emqx.conf 配置文件,然后重启 emqx

listener.ssl.external.keyfile = /etc/emqx/mycert/server-nopassword.key listener.ssl.external.certfile = /etc/emqx/mycert/server.crt

使用 Mqttfx 工具连接测试,会指定公钥证书的路径
证书那些事儿 - 图6