算法详解

Salsa20是一种流加密算法,是由 Daniel J. Bernstein 于 2005 年设计的流加密算法,速度快、安全性高,被广泛用于网络加密(如 Google 的 QUIC 协议)。它的加密方式是生成密钥流(Key Stream)与明文按位异或,从而得到密文(解密时也是异或,过程相同)。

加密流程示意图:

实战识别

在逆向分析实战中判断Salsa20算法的可从一下几点入手:

  1. 初始化矩阵中出现 "expand 32-byte k""expand 16-byte k".
  2. 使用 ROTL32 循环左移 7、9、13、18 位
  3. 20 轮循环,且分为 “列变换” 与 “行变换” 两个阶段。
  4. 数据块为 64 字节,逐块生成密钥流。

代码实现

下面是Salsa20算法过程的C语言函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define U32C(x) ((uint32_t)(x))
#define ROTL32(v, n) (U32C((v) << (n)) | U32C((v) >> (32 - (n))))

/* -------------------- 工具:小端装载/存储 -------------------- */
static uint32_t load32_le(const uint8_t *p) {
return U32C(p[0]) | (U32C(p[1]) << 8) | (U32C(p[2]) << 16) | (U32C(p[3]) << 24);
}
static void store32_le(uint8_t *p, uint32_t x) {
p[0] = (uint8_t)(x);
p[1] = (uint8_t)(x >> 8);
p[2] = (uint8_t)(x >> 16);
p[3] = (uint8_t)(x >> 24);
}

/* -------------------- Salsa20 核心(20轮) -------------------- */
/* quarterround 原位宏(按论文次序:+、ROTL、XOR) */
#define QR(a,b,c,d) \
do { \
b ^= ROTL32(a + d, 7); \
c ^= ROTL32(b + a, 9); \
d ^= ROTL32(c + b,13); \
a ^= ROTL32(d + c,18); \
} while(0)

/* 对 16×32bit 状态做 Salsa20/20 轮,out = in + 轮后状态 */
static void salsa20_block(uint32_t out[16], const uint32_t in[16]) {
uint32_t x[16];
memcpy(x, in, sizeof(x));

// 若轮数不同,则在此处修改
for (int i = 0; i < 10; i++)
{ /* 20轮 = 10次(列轮+行轮) */
/* column round */
QR(x[0], x[4], x[8], x[12]);
QR(x[5], x[9], x[13], x[1]);
QR(x[10], x[14], x[2], x[6]);
QR(x[15], x[3], x[7], x[11]);
/* row round */
QR(x[0], x[1], x[2], x[3]);
QR(x[5], x[6], x[7], x[4]);
QR(x[10], x[11], x[8], x[9]);
QR(x[15], x[12], x[13], x[14]);
}
for (int i = 0; i < 16; i++) out[i] = x[i] + in[i];
}

/* -------------------- Salsa20 加/解密(32字节密钥) -------------------- */
/* key: 32字节; nonce: 8字节; counter: 64位块计数 */
void salsa20_xor(uint8_t *out, const uint8_t *in, size_t len,
const uint8_t key[32], const uint8_t nonce[8], uint64_t counter)
{
/* 常量 "expand 32-byte k" → 4个32位小端 */
static const uint32_t c0 = 0x61707865; /* "expa" */
static const uint32_t c1 = 0x3320646e; /* "nd 3" */
static const uint32_t c2 = 0x79622d32; /* "2-by" */
static const uint32_t c3 = 0x6b206574; /* "te k" */

uint32_t state[16];
uint8_t ks[64];
size_t off = 0;

/* 初始状态(严格按规范) */
state[0] = c0;
state[1] = load32_le(key + 0);
state[2] = load32_le(key + 4);
state[3] = load32_le(key + 8);
state[4] = load32_le(key + 12);
state[5] = c1;
state[6] = load32_le(nonce + 0); /* nonce 低32位 */
state[7] = load32_le(nonce + 4); /* nonce 高32位 */
state[8] = (uint32_t)(counter & 0xFFFFFFFFu); /* 计数器低32 */
state[9] = (uint32_t)(counter >> 32); /* 计数器高32 */
state[10] = c2;
state[11] = load32_le(key + 16);
state[12] = load32_le(key + 20);
state[13] = load32_le(key + 24);
state[14] = load32_le(key + 28);
state[15] = c3;

while (len) {
uint32_t block[16];
salsa20_block(block, state);

/* 导出 64 字节密钥流(小端) */
for (int i = 0; i < 16; i++) store32_le(ks + 4*i, block[i]);

size_t n = len < 64 ? len : 64;
for (size_t i = 0; i < n; i++) out[off + i] = in[off + i] ^ ks[i];

off += n;
len -= n;

/* 计数器自增(64位,小端放在 state[8], state[9]) */
state[8]++;
if (state[8] == 0) state[9]++;
}
}

/* -------------------- Demo 测试 -------------------- */
static void hexprint(const uint8_t *p, size_t n) {
for (size_t i = 0; i < n; i++) {
printf("%02X%s", p[i], (i + 1 == n) ? "" : ((i % 16 == 15) ? "\n" : " "));
}
if (n && n % 16) puts("");
}

int main(void) {
// key秘钥(32字节)
uint8_t key[32] = "12345678901234567890123456789012";
// 初始化向量(IV),也称一次性数字(nonce)
uint8_t nonce[8] = {0};
// 明文或密文
const char *msg = "Hello, Salsa20! This is a test message.";
size_t len = strlen(msg);

uint8_t *pt = (uint8_t *)msg;
uint8_t *ct = (uint8_t *)malloc(len);
uint8_t *dec = (uint8_t *)malloc(len);

if (!ct || !dec) return 1;

/* 加密 */
salsa20_xor(ct, pt, len, key, nonce, 0);

/* 解密(同一函数) */
salsa20_xor(dec, ct, len, key, nonce, 0);

printf("Plaintext: %.*s\n", (int)len, pt);
printf("Ciphertext (hex):\n");
hexprint(ct, len);
printf("Decrypted: %.*s\n", (int)len, dec);

free(ct);
free(dec);
return 0;
}

魔改

  1. 修改加密轮数

    标准 Salsa20 使用20 轮(rounds),而一轮其实是两步 quarterround(偶数轮和奇数轮各一次),所以通常代码里会写成:

    1
    2
    for (i = 0; i < 10; i++) // 10 * 2 = 20 rounds
    { /*算法内容*/ }

    如果你遇到的是 Salsa8(8 轮)或者 Salsa12(12 轮),就要将10这个数字改为46

  2. 修改密钥长度

    Salsa20有 32 字节16 字节 两种密钥长度,在16字节长度模式下需要将秘钥扩展常量修改为“expand 16-byte k”,即”0x61707865”, “0x3120646e”, “0x79622d36”, “0x6b206574”,然后再把16字节的秘钥分两次填入。

  3. 修改常量”expand 32-byte k”

    修改此处内容

    1
    2
    3
    4
    5
    /* 常量 "expand 32-byte k" → 4个32位小端 */
    static const uint32_t c0 = 0x61707865; /* "expa" */
    static const uint32_t c1 = 0x3320646e; /* "nd 3" */
    static const uint32_t c2 = 0x79622d32; /* "2-by" */
    static const uint32_t c3 = 0x6b206574; /* "te k" */
  4. XSalsa20

    XSalsa20是在Salsa20基础上扩展了nonce长度和密钥处理,提升了强度和安全使用灵活性,同时保持了Salsa20原有的高速和安全特性。

    主要区别在于它们使用的随机数(nonce)的长度和密钥衍生方式:

    1. Nonce长度:

      Salsa20使用64位(8字节)的nonce。

      XSalsa20使用192位(24字节)的nonce,显著长于Salsa20。

    2. 密钥衍生:

      XSalsa20用256位密钥和nonce的前128位来计算一个子密钥,然后用这个子密钥和nonce的后64位作为输入参数,执行Salsa20的核心流程来产生加密流。
      换句话说,XSalsa20实际是先对nonce和密钥做一次扩展(通过HSalsa20),产生一个子密钥,再用子密钥执行标准的Salsa20算法。

例题

暂略