编码实践当中涉及的密码学知识

OpenSSL 是用于安全传输层TLS,Transport Layer Security)协议和安全套接字层SSL,Secure Sockets Layer)协议的的工具包,提供了 SSL 协议实现(SSLv2SSLv3TLSv1)、大量算法实现(对称/非对称/摘要)、大数运算、非对称算法密钥、ASN.1 编解码库、证书请求(PKCS10)编解码、数字证书编解码、CRL 编解码、OCSP 协议、数字证书验证、PKCS7 标准实现和 PKCS12 个人数字证书格式实现等功能。

然而一直以来 OpenSSL 代码质量,总是受到开发人员的各种诟病,因而出现了 WolfSSL 与 PolorSSL 等 OpenSSL 的开源替代产品,其中 PolarSSL 被 ARM 公司收购之后,现更名为 Mbed TLS,主要由 Trusted Firmware 组织进行维护,遵循相关的 ARM 规范,并为 Armv8-AArmv9-AArmv8-M 内核架构提供了安全可信的参考实现。本文主要主要讨论了 Base64Hash 编码、以及 OpenSSL 所涉及的相关加解密知识。

Base64 编码

Base64 是一种基于 64 个可打印字符来表示二进制数据的方法,因为 \(\log_2 64 = 6\),所以每 6 bits 为一个单元,对应某个可打印字符。例如 3 Bytes 相当于 24 bits,对应于 4 个 Base64 单元,即 3 个字节可由 4 个可打印字符来表示。Base64 编码常被用于表示传输存储一些二进制数据。

Base64 当中的可打印字符包括字母 A-Za-z,以及数字 0-9 共有 62 个字符,此外还包括 2 个可打印的符号,在不同的操作系统当中表现不同:

注意 1:标准 Base64 编码并不适用于在 HTTP 环境下通过 URL 传递较长的信息,这是由于 URL 编码器会将标准 Base64 当中的 /+ 字符转换为形如 %XX 的形式,由于标准 SQL 语句当中将 % 符号用作通配符,而这些 % 号在存入数据库时还需要再一次进行转换。为了解决这个问题,可以采用一种用于 URL 的改进 Base64 编码,其并不会在末尾填充 = 号,并将标准 Base64 中的 +/ 分别修改为 -_,从而免去了在 URL 编解码和数据库存储时所需要进行的额外转换,避免了编码信息长度在此过程中的增加,并且统一了数据库、Web 页面表单的标识符格式。

注意 2:除此之外,还有一种用于正则表达式的改进 Base64 编码变种,其将 +/ 分别修改为 !-,这是由于 +*[] 在正则表达式当中可能具有特殊含义。

Hash 散列函数

散列函数(Hash)又称为散列算法或者哈希函数,可以将任意长度的输入通过散列算法转换为固定长度的散列值输出,Hash 算法没有固定的公式,只要符合散列思想的算法都可以称为 Hash 算法。目前常见的散列算法可以参见下面的表格:

所有的散列函数算法都具备如下 2 个基本特征:

  1. 如果两个散列值是不相同的,那么这两个散列值的原始输入也是不相同的,具有这种性质的散列函数称为单向散列函数
  2. 散列函数的输入与输出不是一一对应关系,如果 2 个输出散列值相同,那么 2 个输入值可能相同也可能不同,这种情况称为散列碰撞

注意:利用散列算法所计算出来散列值的不可逆性(无法逆向演算回原本的数值),可以将其用于加密存储在数据库当中的密码字符串,从而有效的保护密码。

MD5 消息摘要算法

消息摘要算法(MD5,Message-Digest Algorithm)通过输入不定长度信息,输出四组 32 位数据,最后组合成为一个固定长度的 128 bits/16 Bytes 散列值,通常用于确保数据传输的完整性。该算法是由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于 1992 年公开,但是已于 1996 年在理论上被证实存在破解可能性,并最终于 2004 年被证实无法防止碰撞攻击,因而不再适用于安全性认证。

1
2
3
4
5
6
输入字符串:
Hank

输出 MD5 字符串:
16位 410F72AD50C76F9C
32位 BA02B1E3410F72AD50C76F9C144D6B34

注意:MD5 加密后的位数有 16 Bytes32 Bytes 两种,其中 16 Bytes 实际上是取 32 Bytes 字符串中间的第 9 ~ 24 位部分。

SHA 安全散列算法

安全散列算法(SHA,Secure Hash Algorithm)包含了一系列密码散列函数算法,可以计算出一个消息所对应的固定长度字符串(称为消息摘要),如果输入的消息不同,那么对应的消息摘要也会不同。SHA 家族算法由美国国家安全局(NSA)设计,并由美国国家标准与技术研究院(NIST)发布:

  • SHA-0:1993 年发布,当时称做安全散列标准(Secure Hash Standard),发布之后很快就被 NSA 撤回;
  • SHA-1:1995 年发布,在许多安全协议中广为使用(包括 TLS 和 SSH),被视为 MD5 的后继者,于 2017 年被荷兰密码学研究小组正式宣布攻破;
  • SHA-2:2001 年发布,包括 SHA-224SHA-256SHA-384SHA-512SHA-512/224SHA-512/256,虽然至今尚未出现对该版本算法的有效攻击,但是其算法本质上与 SHA-1 基本类似;
  • SHA-3:2015 年正式发布,由于出现了对于 MD5 的成功破解,以及 SHA-0 和 SHA-1 已经出现理论上的破解方法,于是 NIST 推出了该版本的加密散列算法;

CRC 循环冗余校验

循环冗余校验(CRC,Cyclic Redundancy Check)可以根据文件等数据,利用除法及余数的原理,产生简短的固定位数校验码,也属于一种散列函数算法,主要用于校验数据传输完成之后可能出现的错误,能够高比例的纠正数据传输过程当中的错误,并在极短的时间内完成数据校验计算,该方法由 W.Wesley Peterson 于 1961 年发表。

对称与非对称加密

对称加密算法(Symmetric-key Algorithm)又称为私钥加密共享密钥加密,属于密码学当中的一类加密算法。这类算法在加密和解密时使用相同的密钥(或者两个可以简单相互推算的密钥),这组密钥将会多个成员之间的共同秘密,以便维持专属的通信联系。与公开密钥加密相比,要求双方获取相同的密钥是对称密钥加密的主要缺陷之一。常见的对称加密算法有 AESChaCha203DESSalsa20DESBlowfishIDEARC5RC6Camellia 等等。

非对称式加密算法(Asymmetric Cryptography)也称为公钥加密算法(Public-key Cryptography),同样属于密码学当中的一种算法,其加解密过程需要公开密钥(用于加密)和私有密钥(用于解密)2 个密钥,使用公钥明文加密之后所得到的密文,只能使用对应的私钥才能解密并且得到原来的明文,最初用于加密的公钥并不能用作解密。由于加密与解密需要 2 个不同的密钥,所以被称为非对称加密;

  • 加密:将数据转换为不可直接阅读的形式(即密文);
  • 解密:将密文转换成能够直接阅读的数据(即明文);

注意:相比较于加密与解密都要使用同一个密钥的对称式加密非对称式加密的公钥可以任意向外发布,但是私钥必须自行严格秘密保管,绝不能透过任何途径向外提供。此外,对称加密的速度远远快于非对称加密

DES 数据加密标准

数据加密标准(DES,Data Encryption Standard),是一种在过去主要使用的对称加密算法,1977 年被认定为联邦资料处理标准(FIPS),但是现在已经并非一种安全的加密方法。而 DES 算法的继任者三重数据加密3DES,Triple DES),本质上相当于是对每个数据块运用 3 次 DES 加密,它并非一种全新的块密码算法,并且在理论上也存在破解方法。

注意:DES 和 3DES 标准目前已经逐渐被高级加密标准(AES)所取代。

AES 高级加密标准

高级加密标准(AES,Advanced Encryption Standard),属于美国联邦政府采用的一种区块加密标准,该标准用于替换原先的 DES,由美国国家标准与技术研究院(NIST)于 2001 年 11 月 26 日发布,并于 2002 年 5 月 26 日成为有效标准,目前已经成为对称密钥加密里最为流行的算法之一。

  • 明文:没有经过加密的原始数据;
  • 密文:已经经过 AES 加密函数处理之后的数据;
  • 密钥:用于加密明文的密码,对称加密算法当中用于加解密的密钥相同,密钥由接收者与发送者协商产生(为了防止密钥泄漏,需要避免直接在网络上进行传输,通常经过非对称加密算法加密密钥,然后再透过网络传输给对方);
  • AES 加密函数:将明文密钥作为 AES 加密函数的输入参数,这样 AES 加密函数就会返回密文,即 密文 = AES 加密函数(明文,密钥)
  • AES 解密函数:将密文密钥作为 AES 解密函数的输入参数,这样 AES 解密函数就会返回明文,即 明文 = AES 解密函数(密文,密钥)

结构上 AES 将明文划分为一系列长度相等的数据组,每次只加密一组数据,直至加密完整个明文。在 AES 标准规范中,分组长度只能是 128 bits 位,即每个分组为 16 Byte 字节,而密钥的长度可以使用 128 bits192 bits 或者 256 bits 位,密钥的长度不同,推荐加密轮数也不同,具体请参见下面表格:

AES 密钥长度(32 bits) 分组长度(32 bits) 加密轮数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14

RSA 非对称加密算法

RSA 是一种非对称加密算法,由 Ron Rivest、Adi Shamir、Leonard Adleman 三位麻省理工学院的学者于 1977 年共同提出,RSA 就是由 3 位的姓氏首字母组成。对于极大整数做因数分解的难度决定了 RSA 算法的可靠性(假如找到一种快速因数分解的算法,那么 RSA 的可靠性就会极度下降,但找到这样算法的可能性微乎其微),RSA 诞生近半个世纪后的今天,仅有较短的 RSA 密钥才有可能会被暴力破解。除此之外,世界上还没有任何可以攻击 RSA 算法可靠性的方式,只要其密钥的长度足够长,采用 RSA 加密后的信息基本不可能被破解。

下面是一个基于 RSA 算法的典型双向非对称加密握手步骤:

  1. AB 分别基于 RSA 生成各自的公钥与私钥,然后 AB 分别将自己的公钥交给 CA  服务器,从而获得公钥证书
  2. AB 进行通信时,首先双方会交换公钥证书进行身份认证,然后由 A 生成随机字符串作为对称密钥,并使用 B公钥进行加密,然后将其发送给 B
  3. B 接收之后使用自己的私钥提取该对称密钥,从而完成双向非对称加密的握手步骤;

使用 OpenSSL 进行签名与验证

签名文件

使用 OpenSSL 签名一个文件需要使用到私钥,可以通过下面的命令创建 OpenSSL 公私钥对:

1
2
3
4
5
# 生成保存私钥的文件 private.pem
openssl genrsa -aes128 -passout pass:<phrase> -out private.pem 4096

# 根据私钥生成保存公钥的文件 public.pem
openssl rsa -in private.pem -passin pass:<phrase> -pubout -out public.pem

上述命令中的 <phrase> 密码短语用于加密存储在 private.pem 文件里的私钥,出于安全的原因,建议使用 4096 bits 位的私钥,具体原因可以参见 《RSA Key Sizes: 2048 or 4096 bits?》一文。当命令执行完毕之后,私钥将会被保存在当前目录下的 private.pem 文件,而公钥则会被保存在 public.pem 文件。

【示例】:将 <phrase> 密码短语设置为 Hank,然后使用 OpenSSL 命令生成私钥文件:

1
2
3
4
5
6
7
8
➜  sudo openssl genrsa -aes128 -passout pass:Hank -out private.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.................++++
..................................................................................................................................++++
e is 65537 (0x010001)

➜ ls
private.pem

【示例】:然后,再通过上面得到的 private.pem 文件,来生成对应的公钥文件 public.pem

1
2
3
4
5
➜  sudo openssl rsa -in private.pem -passin pass:Hank -pubout -out public.pem
writing RSA key

➜ ls
private.pem public.pem

Base64 编码

拥有了公钥私钥之后,接下来就可以使用 OpenSSL 签名一个文件,这个过程当中将会要求提供密码短语 <phrase>。由于过去 OpenSSL 签名的默认输出格式为二进制,如果希望在互联网上进行传输,则需要采用 Base64 对签名文件进行编码转换:

1
2
3
4
5
# 基于私钥生成签名文件
openssl dgst -sha256 -sign <private-key> -out signature.sha256 <target-file>

# 将签名文件转换为 Base64 格式
openssl base64 -in signature.sha256 -out <base64-signature>

上述语句中的 <private-key> 是包含私钥的文件,<target-file> 为待签名的文件,而 <base64-signature> 为 Base64 格式的数字签名的文件名,操作完成之后签名文件将会保存为 /opt 目录下的 signature.sha256 文件。

【示例】:使用前一步生成的私钥文件 private.pem 对当前目录下的 uinika.c 文件进行签名,得到一个二进制签名文件 signature.sha256

1
2
3
4
➜   sudo openssl dgst -sha256 -sign private.pem -out signature.sha256 uinika.c
Enter pass phrase for private.pem: Hank
➜ ls
private.pem public.pem signature.sha256 uinika.c

【示例】:将 /opt 目录下的二进制文件 signature.sha256 转换为 Base64 编码的 signature.base64 文件:

1
2
3
➜   sudo openssl base64 -in signature.sha256 -out signature.base64
➜ ls
private.pem public.pem signature.base64 signature.sha256 uinika.c

注意:目前新版本的 OpenSSL 签名文件,已经可以正常在互联网上传输,不需要再进行额外的 Base64 编码。

验证签名

将签名从 Base64 编码重新转换回二进制文件,并使用前面得到的公钥来验证该签名文件:

1
2
3
4
5
# 将签名文件从 Base64 编码还原为二进制格式
openssl base64 -d -in <base64-signature> -out signature.sha256

# 使用公钥验证签名文件
openssl dgst -sha256 -verify <public-key> -signature signature.sha256 <target-file>

上述命令当中的 <base64-signature> 是包含 Base64 签名的文件,而 <public-key> 是包含公钥的文件,<target-file> 则是待验证的二进制签名文件。验证成功之后,OpenSSL 将会提示 Verified OK,否则就会提示 verification Failure

【示例】: 把 Base64 编码的签名文件 signature.base64 还原为二进制格式的签名文件 signature.sha256

1
2
3
➜   sudo openssl base64 -d -in signature.base64 -out signature.sha256
➜ ls
private.pem public.pem signature.base64 signature.sha256 uinika.c

【示例】:通过目标文件 uinika.c 和公钥文件 public.pem 校验还原得到的 signature.sha256 是否有效:

1
2
➜   sudo openssl dgst -sha256 -verify public.pem -signature signature.sha256 uinika.c
Verified OK

安全套接层 SSL / 传输层安全 TLS

传输层安全性协议(TLS,Transport Layer Security)以及其前身安全套接层SSL,Secure Sockets Layer)都属于一种安全协议,目的是为互联网通信提供安全以及数据完整性保障。网景公司(Netscape)于 1994 年推出第 1 版网页浏览器时发布了 HTTPS 协议,并以 SSL 进行加密,后来 IETF 将 SSL 进行标准化,于 1999 年公布了 TLS 1.0,随后又于 2006 年公布了 TLS 1.1,2008 年公布了 TLS 1.2,以及在 2018 年公布的 TLS 1.3,目前该协议已经成为互联网加密通信的工业标准。

TLS 协议采用主从式架构模型,用于在网络上的 2 个应用程序之间创建安全的连接,防止在交换资料时受到窃听以及篡改。TLS 协议与高层的 HTTPFTPTelnet 等应用层协议并不会产生耦合,这些应用层协议透明的运行在 TLS 之上,由 TLS 进行创建加密通道需要的协商与认证,应用层协议传送的数据通过 TLS 协议时都会被加密,从而保证通信的私密性。

TLS 协议必须在客户端服务器分别配置之后才能使用,一旦客户端和服务器都同意使用 TLS 协议,那么它们会通过一个握手过程,协商出一个带有状态的连接用于传输数据,如下就是握手建立的具体步骤:

  1. 客户端连接到支持 TLS 协议的服务器,要求创建安全连接并且列出了受支持的密码包(包括加密和散列算法等);
  2. 服务器从该列表中确定好密码包,并且通知客户端
  3. 服务器返回其数字证书,该证书通常包含有服务器的名称受信任的证书颁发机构(CA)服务器的公钥
  4. 客户端确认其颁发证书的有效性;
  5. 为生成用于安全连接的会话密钥,客户端使用服务器的公钥加密随机生成的密钥,并将其发送至服务器,此时只有服务器才能使用自己的私钥进行解密;
  6. 利用随机数,双方生成用于加密和解密的对称密钥,从而完成 TLS 协议的握手过程;

注意:握手完毕之后的连接是安全的,直到该连接被关闭为止;如果上述任何一个步骤失败,TLS 的握手过程就会失败,并且断开所有连接;

公钥数字签名

公钥数字签名也称为数字签名(Digital Signature),其实质就是逆向使用非对称加密体系,即签名者将信息用私钥加密(非对称加密体系下,私钥通常用于解密),然后将公钥发布出去,验证者使用公钥将加密信息解密并且比对消息(非对称加密体系下,公钥通常用于加密)。

概而言之,非对称加密体系下,通常使用公钥加密,私钥解密。而在数字签名当中,则是使用私钥加密(生成签名),公钥解密(验证签名)

我们可以直接对数据进行签名(即使用私钥加密,其目的是为了签名,而非保密),验证者用公钥正确解密消息,如果和原始数据保持一致,则签名验证成功。通常情况下,我们只会对数据的 Hash 值进行签名(但是计算数据 Hash 值并非数字签名的必要步骤),因为 Hash 值的长度远远小于原始数据的长度,从而使得签名(非对称加密)的效率大幅度提高。

注意:工程实践当中,通常会组合使用加密签名技术,例如前面提到的 TLS 协议就同时组合了加密签名

CA 数字证书认证

数字证书认证(CA,Certificate Authority)是用于实现多方安全通信所提供电子认证,其本质上是由证书签证机构 CA 签发的对于用户公钥的认证。这里的 CA 即指代负责发放与管理数字证书的权威机构,也指代其所承担的位于非对称加密体系当中的公钥合法性校验任务,主要具有如下的作用与特点:

  • 如果用户需要获取 CA 证书,就应当向 CA 机构提出申请,CA 机构判别出申请者的身份之后,就会为其分配一个公钥,并将该公钥与其身份信息绑定为一个整体,并为该整体进行签名,这个签名之后的整体就称为证书,最后会将该证书发还给申请者;
  • CA 既是证书的签发机构,也是公钥基础设施的核心。它为每个使用公开密钥的用户发放一个数字证书,用于核验证书当中罗列的用户,是否合法的拥有证书当中列出的公开密钥。与此同时,CA 机构的数字签名使得攻击者不能伪造和篡改证书;
  • 如果某个用户想鉴别另一个证书的真伪,就需要使用 CA 的公钥对该证书上的签名进行验证,验证通过之后,该证书才会被认为有效;

数字证书的内容包括有 CA 机构的相关信息公钥用户信息公钥CA 机构的签名与有效期 等,目前数字证书的格式与验证方法普遍遵循 X.509 公钥证书格式标准。

X.509 公钥证书格式标准

X.509 是密码学当中公钥证书的格式标准,应用于 TLS/SSL 在内的众多网络协议当中。X.509 证书当中包含有公钥身份信息(比如网络主机名,组织或个体名称等)、签名信息(即可以是证书签发机构的 CA 签名,也可以是自签名)。

注意:在 X.509 当中,组织机构通过发起证书签名请求(CSR,Certificate Signing Request)来获得一份签名证书。

X.509 拥有如下几种常用的扩展名:

  • .pem:隐私增强型电子邮件格式,通常为 Base64 格式;
  • .cer.crt.der:通常为 DER 二进制格式;
  • .p7b.p7cPKCS#7 格式,不包含数据,只包含证书或者 CRL;
  • .p12PKCS#12 格式,包含证书的同时可能还包含有私钥;
  • .pfxPFX,在 PKCS#12 出现之前使用的格式;

注意PKCS#7 是签名或者加密数据的格式标准,由于证书是可以进行校验的签名数据,所以采用签名数据(SignedData)结构进行表述;而 PKCS#12 则是由 PFX 格式发展而来,用于交换公有或者私有对象的标准格式。

国密算法

国密算法主要是经由《密码管理局》《密码行业标准化技术委员会》认定的国产密码算法,当前使用较为广泛的主要是以下 5 种:

  • 祖冲之序列密码:中国自主研发的流密码算法,是 运用于移动 4G 网络当中的国际标准密码算法,该算法包括祖冲之算法(ZUC)、加密算法(128-EEA3)、完整性算法(128-EIA3)三个部分;
  • SM2 椭圆曲线公钥密码算法:即 ECC 椭圆曲线密码机制,但在签名和密钥交换方面不同于 ECDSAECDH 等国际标准,该标准包括总则数字签名算法密钥交换协议公钥加密算法四个部分;
  • SM3 密码杂凑算法:给出了杂凑函数算法(哈希、散列)的计算方法与步骤,适用于商业密码应用当中的数字签名验证;该算法对输入长度小于 \(2^{64}\) 次方的数据,经过填充与迭代压缩,生成长度为 256 bits 位的杂凑值,该算法由填充迭代过程消息扩展压缩函数四部分构成。
  • SM4 分组密码算法:常用于无线局域网通信,该算法的分组长度和密钥长度均为 128 bits 位,加密算法与密钥扩展算法都采用 32 轮非线性迭代结构,其中加解密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥加密轮密钥的逆序;
  • SM9 标识密码算法:该算法不需要申请数字证书,适用于互联网安全、物联网安全等,典型的应用是采用手机号码、邮件地址作为公钥,实现数据加密、身份认证、通话加密、通道加密等安全应用,并具有使用方便,易于部署的特点;

注意密码行业标准化技术委员会(CSTC,Cryptography Standardization Technical Committee)是一家国内从事密码标准化工作的非法人技术组织,归属密码管理局领导和管理,主要从事密码技术、产品、系统和管理等方面的标准化工作。