Salsa20流密码算法的密钥扩展与初始化过程
1. 题目描述
Salsa20是一个由Daniel J. Bernstein设计的流密码算法,以其简洁、快速和高安全性著称。它通过一个核心的哈希函数(基于加法、异或和循环移位)处理一个512位的内部状态,生成密钥流。本题将详细讲解Salsa20算法中如何从用户提供的密钥、随机数和块计数器,构造出初始的512位内部状态矩阵,即密钥扩展与初始化过程。这是Salsa20生成密钥流的首要关键步骤。
2. 算法背景与核心组件
在深入初始化之前,先了解Salsa20的几个核心输入参数:
- 密钥(Key):可以是128位(16字节)或256位(32字节)。
- 随机数(Nonce):64位(8字节),在相同密钥下必须唯一,以确保密钥流不重复。
- 块计数器(Block Counter):64位(8字节),用于索引加密数据流的每个64字节块。
- 常数(Constants):固定的4个32位字,取决于密钥长度。
算法的目标是:将(密钥,随机数,计数器)混合,初始化成一个4×4的矩阵,其中每个元素是32位(4字节)的字。这个初始矩阵随后会被核心的“哈希轮函数”处理,生成密钥流块。
3. 初始化步骤详解
我们以256位密钥的版本为例,逐步说明初始化过程。128位密钥版本类似,但常数和密钥排列略有不同。
步骤1:理解矩阵布局
初始状态是一个4行4列的矩阵,共16个32位字(记为V[0]到V[15])。其布局是固定的,如下所示:
| 列0 | 列1 | 列2 | 列3 |
|---|---|---|---|
| 常数0 | 密钥0-3 | 密钥4-7 | 常数1 |
| 随机数0-1 | 常数2 | 块计数器0-1 | 随机数2-3 |
| 块计数器2-3 | 随机数4-5 | 常数3 | 密钥8-11 |
| 密钥12-15 | 常数4 | 随机数6-7 | 块计数器4-5 |
注意:上述是逻辑布局,实际填充时需要将字节序列转换为32位小端序字。
步骤2:准备输入数据的字拆分
我们需要将所有输入数据拆分成32位的小端序字:
- 密钥(256位=32字节):拆分成8个32位字,记为
K[0],K[1], ...,K[7]。
例如,密钥字节key[0]到key[3]组成K[0],其中key[0]是最低有效字节(小端序)。 - 随机数(64位=8字节):拆分成2个32位字,记为
N[0],N[1]。 - 块计数器(64位=8字节):拆分成2个32位字,记为
C[0],C[1]。
步骤3:确定常数
Salsa20使用ASCII字符串的编码作为常数,以确保初始矩阵无对称性。对于256位密钥:
- 常数0:
"expa"-> 字节0x65, 0x78, 0x70, 0x61-> 小端序字0x61707865。 - 常数1:
"nd 3"->0x6e642033。 - 常数2:
"2-by"->0x322d6279。 - 常数3:
"te k"->0x7465206b。 - 常数4:
"ey"后跟两个空字节0x7965, 0x0000? 实际上标准中是"ey"+0x00,0x00? 让我们精确核对:标准定义中,常数4是"ey"后跟0x00,0x00,但实际小端序字是0x79656b2e? 这里需要修正:标准Salsa20的256位密钥的4个常数是固定的4个32位字,确切值是:
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574。这些是"expand 32-byte k"这个16字节ASCII串的4个小端序字。我们记这些常数为T[0],T[1],T[2],T[3]。
步骤4:填充初始矩阵
按照Salsa20的标准文档,256位密钥版本的初始矩阵V的16个字按行主序填充如下(索引从0开始):
V[0] = T[0] = 0x61707865
V[1] = K[0]
V[2] = K[1]
V[3] = K[2]
V[4] = K[3]
V[5] = T[1] = 0x3320646e
V[6] = N[0] // 随机数低32位
V[7] = N[1] // 随机数高32位
V[8] = C[0] // 计数器低32位
V[9] = C[1] // 计数器高32位
V[10] = T[2] = 0x79622d32
V[11] = K[4]
V[12] = K[5]
V[13] = K[6]
V[14] = K[7]
V[15] = T[3] = 0x6b206574
注意:这个布局与步骤1的表格逻辑对应,但具体行列对应关系不重要,因为核心哈希函数是按列和行混合的。重要的是,密钥、随机数、计数器和常数被放置在了矩阵的特定位置。
步骤5:128位密钥版本的区别
如果使用128位密钥(16字节),则密钥字只有K[0]到K[3],常数变为"expand 16-byte k"对应的4个字:0x61707865, 0x3120646e, 0x79622d36, 0x6b206574。初始化矩阵中,V[1], V[2], V[3], V[4], V[11], V[12], V[13], V[14]这8个位置会重复填充这4个密钥字,具体模式为:V[1]=K[0], V[2]=K[1], V[3]=K[2], V[4]=K[3], V[11]=K[0], V[12]=K[1], V[13]=K[2], V[14]=K[3]。随机数和计数器的位置不变。
4. 实例演示(256位密钥)
假设:
- 密钥:全0字节(32个0),则
K[0]到K[7]均为0x00000000。 - 随机数:全0字节(8个0),则
N[0]=0x00000000,N[1]=0x00000000。 - 块计数器:从0开始,则
C[0]=0x00000000,C[1]=0x00000000。
则初始矩阵V为:
V[0] = 0x61707865
V[1] = 0x00000000
V[2] = 0x00000000
V[3] = 0x00000000
V[4] = 0x00000000
V[5] = 0x3320646e
V[6] = 0x00000000
V[7] = 0x00000000
V[8] = 0x00000000
V[9] = 0x00000000
V[10]= 0x79622d32
V[11]= 0x00000000
V[12]= 0x00000000
V[13]= 0x00000000
V[14]= 0x00000000
V[15]= 0x6b206574
这个矩阵接下来会输入到Salsa20的20轮哈希函数(每轮包括4次的“quarter-round”操作)中,最终的输出是该初始矩阵与轮函数输出的加和(模2^32),从而产生一个64字节的密钥流块。
5. 小结
Salsa20的密钥扩展与初始化过程,实质上是将密钥、随机数、计数器与固定常数按照特定顺序排列成一个4×4的矩阵。这个过程确保了:
- 不同密钥产生截然不同的初始状态。
- 相同密钥下,不同的随机数或计数器值会产生不同的初始状态,从而生成不同的密钥流。
- 固定常数的引入打破了对称性,并作为“nothing-up-my-sleeve numbers”增加算法可信度。
初始化完成后,Salsa20的核心轮函数会以这个矩阵为输入,经过20轮(或简化版的12轮、8轮)的混淆,最终输出密钥流块。这个设计使得Salsa20在保证安全性的同时,非常适合软件实现和高性能应用。