密码学初探-基于RUST的密码系统与算法简析
Introduction
密码学(Cryptography)一般可分为古典密码学和现代密码学。
其中,古典密码学,作为一种实用性艺术存在,其编码和破译通常依赖于设计者和敌手的创造力与技巧,并没有对密码学原件进行清晰的定义。古典密码学主要包含以下几个方面:
- 单表替换加密(Monoalphabetic Cipher)
- 多表替换加密(Polyalphabetic Cipher)
- 奇奇怪怪的加密方式
而现代密码学则起源于 20 世纪中后期出现的大量相关理论,1949 年香农(C. E. Shannon)发表了题为《保密系统的通信理论》的经典论文标志着现代密码学的开始。现代密码学主要包含以下几个方面:
- 对称加密(Symmetric Cryptography),以 DES,AES,RC4 为代表。
- 非对称加密(Asymmetric Cryptography),以 RSA,ElGamal,椭圆曲线加密为代表。
- 哈希函数(Hash Function),以 MD5,SHA-1,SHA-512 等为代表。
- 数字签名(Digital Signature),以 RSA 签名,ElGamal 签名,DSA 签名为代表。
其中,对称加密体制主要分为两种方式:
- 分组密码(Block Cipher),又称为块密码。
- 序列密码(Stream Cipher),又称为流密码。
一般来说,密码设计者的根本目标是保障信息及信息系统的
- 机密性(Confidentiality)
- 完整性(Integrity)
- 可用性(Availability)
- 认证性(Authentication)
- 不可否认性(Non-repudiation)
其中,前三者被称为信息安全的 CIA 三要素 。
本文主要介绍了仿射密码,流密码(RC4,LFSR+JK),分组密码(DES,AES),非对称加密(rsa)和密码协议(Diffie_Hellman)。 项目详细代码已于Github开源[1]。
仿射密码
原理
仿射密码的加密函数是 E(x) = (ax + b) (mod m),其中
- x 表示明文按照某种编码得到的数字
- a 和 m 互质
- m 是编码系统中字母的数目。
解密函数是 D(x) = a−1(x − b) (mod m),其中 a−1 是 a 在 ℤm 群的乘法逆元。
下面我们以 E(x) = (5x + 8) mod 26 函数为例子进行介绍,加密字符串为 AFFINE CIPHER
,这里我们直接采用字母表26个字母作为编码系统
明文 | A | F | F | I | N | E | C | I | P | H | E | R |
---|---|---|---|---|---|---|---|---|---|---|---|---|
x | 0 | 5 | 5 | 8 | 13 | 4 | 2 | 8 | 15 | 7 | 4 | 17 |
y = 5x + 8 | 8 | 33 | 33 | 48 | 73 | 28 | 18 | 48 | 83 | 43 | 28 | 93 |
y mod 26 | 8 | 7 | 7 | 22 | 21 | 2 | 18 | 22 | 5 | 17 | 2 | 15 |
密文 | I | H | H | W | V | C | S | W | F | R | C | P |
其对应的加密结果是 IHHWVCSWFRCP
。
对于解密过程,正常解密者具有a与b,可以计算得到 a−1 为 21,所以其解密函数是D(x) = 21(x − 8) (mod 26) ,解密如下
密文 | I | H | H | W | V | C | S | W | F | R | C | P |
---|---|---|---|---|---|---|---|---|---|---|---|---|
y | 8 | 7 | 7 | 22 | 21 | 2 | 18 | 22 | 5 | 17 | 2 | 15 |
x = 21(y − 8) | 0 | -21 | -21 | 294 | 273 | -126 | 210 | 294 | -63 | 189 | -126 | 147 |
x mod 26 | 0 | 5 | 5 | 8 | 13 | 4 | 2 | 8 | 15 | 7 | 4 | 17 |
明文 | A | F | F | I | N | E | C | I | P | H | E | R |
可以看出其特点在于只有 26 个英文字母。

Rust实现
1 |
|

破解
首先,我们可以看到的是,仿射密码对于任意两个不同的字母,其最后得到的密文必然不一样,所以其也具有最通用的特点。当密文长度足够长时,我们可以使用频率分析的方法来解决。
其次,我们可以考虑如何攻击该密码。可以看出当a = 1 时,仿射加密是凯撒加密。而一般来说,我们利用仿射密码时,其字符集都用的是字母表,一般只有26个字母,而不大于26的与26互素的个数一共有
ϕ(26) = ϕ(2) × ϕ(13) = 12
算上b的偏移可能,一共有可能的密钥空间大小也就是
12 × 26 = 312
一般来说,对于该种密码,我们至少得是在已知部分明文的情况下才可以攻击。下面进行简单的分析。
这种密码由两种参数来控制,如果我们知道其中任意一个参数,那我们便可以很容易地快速枚举另外一个参数得到答案。
但是,假设我们已经知道采用的字母集,这里假设为26个字母,我们还有另外一种解密方式,我们只需要知道两个加密后的字母 y1, y2 即可进行解密。那么我们还可以知道
$$ \begin{align} y_1 &= (ax_1+b)\pmod{26} \\ y_2 &= (ax_2+b)\pmod{26} \end{align} $$
两式相减,可得
$$ \begin{align} y_1-y_2 &= a(x_1-x_2)\pmod{26} \end{align} $$
这里 y1, y2 已知,如果我们知道密文对应的两个不一样的字符 x1 与 x2 ,那么我们就可以很容易得到 a ,进而就可以得到 b 了。
流密码
流密码一般逐字节或者逐比特处理信息。一般来说
- 流密码的密钥长度会与明文的长度相同。
- 流密码的密钥派生自一个较短的密钥,派生算法通常为一个伪随机数生成算法。
需要注意的是,流加密目前来说都是对称加密。
伪随机数生成算法生成的序列的随机性越强,明文中的统计特征被覆盖的更好。
流密码加解密非常简单,在已知明文的情况下,可以非常容易地获取密钥流。
流密码的关键在于设计好的伪随机数生成器。一般来说,伪随机数生成器的基本构造模块为反馈移位寄存器。当然,也有一些特殊设计的流密码,比如 RC4。
反馈移位寄存器
一般的,一个 n 级反馈移位寄存器如下图所示

其中
- a0,a1,…,an − 1 为初态。
- F 为反馈函数或者反馈逻辑。如果 F 为线性函数,那么我们称其为线性反馈移位寄存器(LFSR),否则我们称其为非线性反馈移位寄存器(NFSR)。
- ai + n = F(ai, ai + 1, ..., ai + n − 1) 。
一般来说,反馈移位寄存器都会定义在某个有限域上,从而避免数字太大和太小的问题。因此,我们可以将其视为同一个空间中的变换,即
(ai, ai + 1, ..., ai + n − 1) → (ai + 1, ..., ai + n − 1, ai + n) . 对于一个序列来说,我们一般定义其生成函数为其序列对应的幂级数的和。
线性反馈移位寄存器 - LFSR
介绍
线性反馈移位寄存器的反馈函数一般如下
$$a_{i+n}=\sum\limits_{j=1}^{n}c_ja_{i+n-j}$$
其中,cj 均在某个有限域 Fq 中。
既然线性空间是一个线性变换,我们可以得知这个线性变换为
$$ \begin{align} &\left[ a_{i+1},a_{i+2},a_{i+3}, \ldots,a_{i+n} \right] \\ &= \left[ a_{i},a_{i+1},a_{i+2}, \ldots,a_{i+n-1} \right]\left[ \begin{matrix} 0 & 0 & \cdots & 0 & c_n \\ 1 & 0 & \cdots & 0 & c_{n-1} \\ 0 & 1 & \cdots & 0 & c_{n-2} \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ 0 & 0 & \cdots & 1 & c_1 \end{matrix} \right] \\ &= \left[ a_{0},a_{1},a_{2}, \ldots,a_{n-1} \right]\left[ \begin{matrix} 0 & 0 & \cdots & 0 & c_n \\ 1 & 0 & \cdots & 0 & c_{n-1} \\ 0 & 1 & \cdots & 0 & c_{n-2} \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ 0 & 0 & \cdots & 1 & c_1 \end{matrix} \right]^{i+1} \end{align} $$
进而,我们可以求得其特征多项式为
$$f(x)=x^n-\sum\limits_{i=1}^{n}c_ix^{n-i}$$
同时,我们定义其互反多项式为
$$\overline f(x)=x^nf(\frac{1}{x})=1-\sum\limits_{i=1}^{n}c_ix^{i}$$
我们也称互反多项式为线性反馈移位寄存器的联结多项式。
这里有一些定理需要我们记一下,感兴趣的可以自行推导。
样例
特征多项式与生成函数
已知某个 n 级线性反馈移位寄存器的特征多项式,那么该序列对应的生成函数为
$$A(x)=\frac{p(x)}{\overline f(x)}$$
其中,$p(x)=\sum\limits_{i=1}^{n}(c_{n-i}x^{n-i}\sum\limits_{j=1}^{i}a_jx^{j-1})$。可以看出 p(x) 完全由初始状态和反馈函数的系数决定。
序列周期与生成函数
序列的的周期为其生成函数的既约真分式的分母的周期。
对于 n 级线性反馈移位寄存器,最长周期为 2n − 1(排除全零)。达到最长周期的序列一般称为 m 序列。
特殊性质
- 将两个序列累加得到新的序列的周期为这两个序列的周期的和。
- 序列是 n 级 m 序列,当且仅当序列的极小多项式是 n 次本原多项式。
B-M 算法
一般来说,我们可以从两种角度来考虑 LFSR
- 密钥生成角度,一般我们希望使用级数尽可能低的 LFSR 来生成周期大,随机性好的序列。
- 密码分析角度,给定一个长度为 n 的序列 a,如何构造一个级数尽可能小的 LFSR 来生成它。其实这就是 B-M 算法的来源。
一般来说,我们定义一个序列的线性复杂度如下
- 若 s 为一个全零序列,则线性复杂度为0。
- 若没有 LFSR 能生成 s,则线性复杂度为无穷。
- 否则,s 的线性复杂度为生成 L(s) 的最小级的 LFSR。
BM 算法的要求我们需要知道长度为 2n 的序列。其复杂度
- 时间复杂度:O(n^2) 次比特操作
- 空间复杂度:O(n) 比特。
关于 BM 算法的细节,后续添加,目前处于学习过程中。
但是其实如果我们知道了长度为 2n 的序列,我们也可以一种比较笨的方法来获取原先的序列。不妨假设已知的序列为a1, ..., a2n,我们可以令
S1 = (a1, ..., an)
S2 = (a2, ..., an + 1)
…
Sn + 1 = (an + 1, ..., a2n)
那么我们可以构造矩阵 X = (S1, ..., Sn),那么
Sn + 1 = (cn, ..., c1)X
所以
(cn, ..., c1) = Sn + 1X−1
进而我们也就知道了 LFSR 的反馈表达式,进而我们就可以推出初始化种子。
非线性反馈移位寄存器
介绍
为了使得密钥流输出的序列尽可能复杂,会使用非线性反馈移位寄存器,常见的有三种
- 非线性组合生成器,对多个 LFSR 的输出使用一个非线性组合函数
- 非线性滤波生成器,对一个 LFSR 的内容使用一个非线性组合函数
- 钟控生成器,使用一个(或多个)LFSR 的输出来控制另一个(或多个)LFSR 的时钟
非线性组合生成器
简介
组合生成器一般如下图所示。

JK触发器

利用J-K触发器的非线性序列生成器

样例

Rust实现
1 |
|

RC4
基本介绍
RSA 由 Ron Rivest 设计,加解密使用相同的密钥,因此也属于对称加密算法。它是面向字节的流密码,密钥长度可变,非常简单,但也很有效果。RC4 算法曾广泛应用于 SSL/TLS 协议和 WEP/WPA 协议,但由于RC4算法存在弱点,2015年2月所发布的 RFC 7465 规定禁止在TLS中使用RC4加密算法。
基本流程
RC4 主要包含三个流程
- 初始化 S 和 T 数组。
- 初始化置换 S。
- 生成密钥流。
初始化 S 和 T 数组
初始化 S 和 T 的代码如下
1 |
|

初始化置换 S
1 |
|

生成流密钥
1 |
|

我们一般称前两部分为 KSA ,最后一部分是 PRGA。
Rust实现
1 |
|

块加密
概述
所谓块加密就是每次加密一块明文,常见的加密算法有
- IDEA 加密
- DES 加密
- AES 加密
块加密也是对称加密。
其实,我们也可以把块加密理解一种特殊的替代密码,但是其每次替代的是一大块。而正是由于一大块,明文空间巨大,而且对于不同的密钥,我们无法做一个表进行对应相应的密文,因此必须得有 复杂 的加解密算法来加解密明密文。
而与此同时,明文往往可能很长也可能很短,因此在块加密时往往需要两个辅助
- padding,即 padding 到指定分组长度
- 分组加密模式,即明文分组加密的方式。
填充规则
正如我们之前所说,在分组加密中,明文的长度往往并不满足要求,需要进行 padding,而如何 padding 目前也已经有了不少的规定。
常见的 填充规则 如下。需要注意的是,即使消息的长度是块大小的整数倍,仍然需要填充。
一般来说,如果在解密之后发现 Padding 不正确,则往往会抛出异常。我们也因此可以知道 Paddig 是否正确。
Pad with bytes all of the same value as the number of padding bytes (PKCS5 padding)
1 |
|
Pad with 0x80 followed by zero bytes (OneAndZeroes Padding)
1 |
|
这里其实就是和 md5 和 sha1 的 padding 差不多。
Pad with zeroes except make the last byte equal to the number of padding bytes
1 |
|
Pad with zero (null) characters
1 |
|
Pad with spaces
1 |
|
工作模式
分组密码的工作模式是:根据不同的数据格式和安全性要求, 以一个具体的分组密码算法为基础构造一个分组密码系统的方法。分组密码的工作模式应当力求简单, 有效和易于实现,需要采用适当的工作模式来隐蔽明文的统计特性、数据的格式等,降低删除、重放、插入和伪造成功的机会。
分组密码的主要工作模式:
- 电码本(ECB)模式
- 密码分组链接(CBC)模式
- 密码反馈(CFB)模式
- 输出反馈(OFB)模式
- 计数器(CTR)模式

基本策略
在分组密码设计时,充分使用了 Shannon 提出的两大策略:混淆与扩散两大策略。
混淆
混淆,Confusion,将密文与密钥之间的统计关系变得尽可能复杂,使得攻击者即使获取了密文的一些统计特性,也无法推测密钥。一般使用复杂的非线性变换可以得到很好的混淆效果,常见的方法如下
- S 盒
- 乘法
扩散
扩散,Diffusion,使得明文中的每一位影响密文中的许多位。常见的方法有
- 线性变换
- 置换
- 移位,循环移位
常见加解密结构
目前块加密中主要使用的是结构是
- 迭代结构,这是因为迭代结构便于设计与实现,同时方便安全性评估。
迭代结构
概述
迭代结构基本如下,一般包括三个部分
- 密钥置换
- 轮加密函数
- 轮解密函数

轮函数
目前来说,轮函数主要有主要有以下设计方法
- Feistel Network,由 Horst Feistel 发明,DES 设计者之一。
- DES
- Substitution-Permutation Network(SPN)
- AES
- 其他方案
密钥扩展
目前,密钥扩展的方法有很多,没有见到什么完美的密钥扩展方法,基本原则是使得密钥的每一个比特尽可能影响多轮的轮密钥。
DES
基本介绍
Data Encryption Standard(DES),数据加密标准,是典型的块加密,其基本信息如下
- 输入 64 位。
- 输出 64 位。
- 密钥 64 位,使用 64 位密钥中的 56 位,剩余的 8 位要么丢弃,要么作为奇偶校验位。
- Feistel 迭代结构
- 明文经过 16 轮迭代得到密文。
- 密文经过类似的 16 轮迭代得到明文。
基本流程
给出一张简单的 DES 流程图。

加密
我们可以考虑一下每一轮的加密过程
Li + 1 = Ri
Ri + 1 = Li ⊕ F(Ri, Ki)
那么在最后的 Permutation 之前,对应的密文为(Rn + 1, Ln + 1)。
解密
那么解密如何解密呢?首先我们可以把密文先进行逆置换,那么就可以得到最后一轮的输出。我们这时考虑每一轮
Ri = Li + 1
Li = Ri + 1 ⊕ F(Li + 1, Ki)
因此,(L0, R0) 就是加密时第一次置换后的明文。我们只需要再执行逆置换就可以获得明文了。
可以看出,DES 加解密使用同一套逻辑,只是密钥使用的顺序不一致。
核心部件
DES 中的核心部件主要包括(这里只给出加密过程的)
- 初始置换
- F 函数
- E 扩展函数
- S 盒,设计标准未给出。
- P 置换
- 最后置换
其中 F 函数如下

如果对 DES 更加感兴趣,可以进行更加仔细地研究。欢迎提供 PR。
衍生
在 DES 的基础上,衍生了以下两种加密方式
- 双重 DES
- 三种 DES
双重 DES
双重 DES 使用两个密钥,长度为 112 比特。加密方式如下
C = Ek2(Ek1(P))
但是双重 DES 不能抵抗中间相遇攻击,我们可以构造如下两个集合
I = Ek1(P)
J = Dk2(C)
即分别枚举 K1 和 K2 分别对 P 进行加密和对 C 进行解密。
在我们对 P 进行加密完毕后,可以对加密结果进行排序,这样的复杂度为2nlog(2n) = O(n2n)
当我们对 C 进行解密时,可以每解密一个,就去对应的表中查询。
总的复杂度为还是O(n2n)。
三重 DES
三重 DES 的加解密方式如下
C = Ek3(Dk2(Ek1(P)))
P = Dk1(Ek2(Dk3(C)))
在选择密钥时,可以有两种方法
- 3 个不同的密钥,k1,k2,k3 互相独立,一共 168 比特。
- 2 个不同的密钥,k1 与 k2 独立,k3=k1,112 比特。
攻击方法
- 差分攻击
- 线性攻击
AES
基本介绍
Advanced Encryption Standard(AES),高级加密标准,是典型的块加密,被设计来取代 DES,由 Joan Daemen 和 Vincent Rijmen 所设计。其基本信息如下
- 输入:128 比特。
- 输出:128 比特。
- SPN 网络结构。
其迭代轮数与密钥长度有关系,如下
密钥长度(比特) | 迭代轮数 |
---|---|
128 | 10 |
192 | 12 |
256 | 14 |
基本流程
基本概念
在 AES 加解密过程中,每一块都是 128 比特,所以我们这里明确一些基本概念。

在 AES 中,块与 State 之间的转换过程如下

所以,可以看出,每一个 block 中的字节是按照列排列进入到状态数组的。
而对于明文来说,一般我们会选择使用其十六进制进行编码。

加解密过程
这里给个看雪上比较好的 图例 ,以便于介绍基本的流程,每一轮主要包括
- 轮密钥加,AddRoundKey
- 字节替换,SubBytes
- 行移位,ShiftRows
- 列混淆,MixColumns

上面的列混淆的矩阵乘法等号左边的列向量应该在右边。
这里再给一张其加解密的全图,其解密算法的正确性很显然。

我们这里重点关注一下以下。
字节替换
在字节替换的背后,其实是有对应的数学规则来定义对应的替换表的,如下

这里的运算均定义在 GF(28) 内。
列混淆
这里的运算也是定义在 GF(28) 上,使用的模多项式为 x8 + x4 + x3 + 1。
密钥扩展

等价解密算法
简单分析一下,我们可以发现
- 交换逆向行移位和逆向字节代替并不影响结果。
- 交换轮密钥加和逆向列混淆并不影响结果,关键在于
- 首先可以把异或看成域上的多项式加法
- 然后多项式中乘法对加法具有分配率。
攻击方法
- 积分攻击
非对称加密
介绍
在非对称密码中,加密者与解密者所使用的密钥并不一样,典型的有 RSA 加密,背包加密,椭圆曲线加密。
RSA
RSA 加密算法是一种非对称加密算法。在公开密钥加密和电子商业中 RSA 被广泛使用。RSA 是 1977 年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。RSA 就是他们三人姓氏开头字母拼在一起组成的。
RSA 算法的可靠性由极大整数因数分解的难度决定。换言之,对一极大整数做因数分解愈困难,RSA 算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用 RSA 加密的信息的可靠性就肯定会极度下降。但找到这样的算法的可能性是非常小的。如今,只有短的 RSA 密钥才可能被强力方式解破。到 2020 年为止,还没有任何可靠的攻击 RSA 算法的方式。
基本原理
公钥与私钥的产生
- 随机选择两个不同大质数 p 和 q,计算 N = p × q
- 根据欧拉函数,求得 φ(N) = φ(p)φ(q) = (p − 1)(q − 1)
- 选择一个小于 φ(N) 的整数 e,使 e 和 φ(N) 互质。并求得 e 关于 φ(N) 的模反元素,命名为 d,有 ed ≡ 1 (mod φ(N))
- 将 p 和 q 的记录销毁
此时,(N, e) 是公钥,(N, d) 是私钥。
消息加密
首先需要将消息 以一个双方约定好的格式转化为一个小于 N,且与 N 互质的整数 m。如果消息太长,可以将消息分为几段,这也就是我们所说的块加密,后对于每一部分利用如下公式加密:
me ≡ c (mod N)
消息解密
利用密钥 d 进行解密。
cd ≡ m (mod N)
正确性证明
即我们要证med ≡ m mod N,已知ed ≡ 1 mod ϕ(N),那么 ed = kϕ(N) + 1,即需要证明
mkϕ(N) + 1 ≡ m mod N
这里我们分两种情况证明
第一种情况 gcd(m, N) = 1,那么 mϕ(N) ≡ 1 mod N,因此原式成立。
第二种情况 gcd(m, N) ≠ 1,那么 m 必然是 p 或者 q 的倍数,并且 n = m 小于 N。我们假设
m = xp
那么 x 必然小于 q,又由于 q 是素数。那么
mϕ(q) ≡ 1 mod q
进而
mkϕ(N) = mk(p − 1)(q − 1) = (mϕ(q))k(p − 1) ≡ 1 mod q
那么
mkϕ(N) + 1 = m + uqm
进而
mkϕ(N) + 1 = m + uqxp = m + uxN
所以原式成立。
样例
例1
计算公钥和私钥
p = 13 , q = 5
- N = pq = 65
- r = (p-1)(q-1) = (13-1)(5-1) = 48
计算模反元素 r=48,选择e=5,得到二元一次方程:5d-48k=1 , 获得一组解:d=29,k=3
因此,公钥是 (N, e) = (65, 5),私钥是 (N, d) = (65, 29)。
加密信息
明文:m=3
计算: $ c ^{5} $
因此:3被加密为48
解密信息
密文:c=48
计算:$ n ^{29} $
因此:48被解密为3
例2

密码协议
Diffie-Hellman 密钥交换
- 密钥交换是实现安全通信的基础
- 商用加密算法AES和DES需要在安全通信之前,实现通信双方的密钥共享。
- 密钥交换的方法:
- 基于RSA的密钥交换;
- 基于KDC技术 (Key Distributed Center,密钥分发中心);
- Diffie-Hellman密钥交换(简称:DH算法);
- 基于物理层的密钥交换。
DH算法是不安全信道下实现安全密钥共享的一种方法,由 W. Diffie 和 M.Hellman 在1976年提出的第一个公开的公钥密码算法。

DH协议案例