核心概念

ChaCha20 的核心是一个基于ARX(Addition-Rotation-XOR,加法-循环移位-异或)操作的伪随机函数。它通过迭代一个称为“四分之一轮”的操作来生成密钥流。

  • 高安全性: 使用 256 位密钥,提供非常高的密钥空间。 目前没有已知的、针对完整 20 轮 ChaCha20 的有效密码分析攻击(如密钥恢复、区分攻击等)。它被认为是安全的。 设计简洁,便于分析和审计。
  • 高性能(尤其在软件中): 基于简单的 ARX 操作(加法、循环移位、异或),这些操作在现代 CPU 上执行速度极快。 不需要像 AES 那样依赖复杂的查表操作或专用的硬件指令(如 AES-NI)才能达到高性能。它在纯软件实现中就能非常高效。 特别适合在没有硬件加速的移动设备、嵌入式系统或旧 CPU 上运行。
  • 抗侧信道攻击: 操作是恒定时间的:执行时间不依赖于密钥或数据的值。这使其天然抵抗计时攻击。 操作简单且基于位运算,难以通过功耗分析等侧信道泄露关键信息。
  • 简单性: 算法描述和实现相对简单、紧凑,易于理解和验证,减少了引入实现错误的风险。
  • 灵活性: 虽然标准是 20 轮,但 Bernstein 也定义了变体(如 ChaCha8, ChaCha12),轮数更少,速度更快,但安全性稍低(需根据具体安全需求选择)。 常与 Poly1305 消息认证码结合使用,形成 ChaCha20-Poly1305 认证加密算法(AEAD),同时提供机密性、完整性和身份验证。这是目前非常流行和推荐的组合(例如在 TLS 1.3, OpenSSH, WireGuard 中)。

应用场景

ChaCha20(通常与 Poly1305 配对)已被广泛采用,尤其是在需要高性能软件加密或抵抗侧信道攻击的场景中:

  • TLS 1.3: 作为推荐的加密套件之一(TLS_CHACHA20_POLY1305_SHA256)。
  • OpenSSH: 自 2013 年起支持作为默认加密算法之一。
  • QUIC 协议: Google 开发的下一代网络传输协议,使用 ChaCha20-Poly1305。
  • WireGuard VPN 协议: 使用 ChaCha20-Poly1305 作为其核心加密和认证组件。
  • Signal 协议 / WhatsApp / Facebook Messenger: 用于端到端加密消息传递。
  • Linux 内核随机数生成器 (/dev/random): 使用 ChaCha20 作为其密码学核心。
  • 磁盘/文件加密: 如某些加密文件系统或工具。
  • 需要高性能软件加密的任何应用。

ChaCha20 使用代码示例以及与 AES 的性能对比

img

在Macbook M2 Max 笔记本上的性能测试结果:

img

AES-GCM 更快可能是因为 M2 CPU 对 AES 做了硬件级优化。

完整测试代码:

  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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
from __future__ import annotations

"""
AES-GCM 256 与 ChaCha20-Poly1305 加密算法对比演示
该脚本演示了两种现代认证加密算法的使用、性能差异和安全特性
"""

from cryptography.hazmat.primitives.ciphers.aead import AESGCM , ChaCha20Poly1305
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
import os
import timeit
from typing import Tuple , Callable , Any , Optional


def generate_random_data(size: int) -> bytes:
    """
    生成指定大小的随机二进制数据

    参数:
        size: 要生成的随机数据大小(字节数)

    返回:
        随机生成的二进制数据
    """
    if size <= 0 or size % 16 != 0:
        size = 32
    return os.urandom(size)


def derive_key(password: bytes ,
               salt: Optional[ bytes ] = None ,
               key_length: int = 32) -> bytes:
    """
    使用 HKDF 从密码派生安全密钥

    HKDF (HMAC-based Key Derivation Function) 是一种安全的密钥派生方法
    它可以将弱密码(如用户输入的密码)转换为强密码学密钥

    参数:
        password: 原始密码(二进制格式)
        salt: 可选盐值(用于增加派生密钥的随机性)
        key_length: 要生成的密钥长度(字节)

    返回:
        派生的安全密钥
    """
    # 如果没有提供盐值,生成随机盐值
    if salt is None:
        salt = os.urandom(16)
    
    # 创建 HKDF 实例
    # 算法:SHA-256,密钥长度,盐值,上下文信息,后端
    hkdf = HKDF(
            algorithm = hashes.SHA256() ,
            length = key_length ,
            salt = salt ,
            info = b'key-derivation' ,
            backend = default_backend() ,
            )
    
    # 从密码派生密钥
    return hkdf.derive(password)


def aes_gcm_encrypt_decrypt(key: bytes ,
                            data: bytes ,
                            associated_data: bytes) -> bytes:
    """
    AES-GCM 256 算法的加密和解密过程

    步骤:
    1. 生成随机 nonce (12字节)
    2. 使用密钥初始化 AES-GCM
    3. 用 nonce 和关联数据加密数据
    4. 使用相同的 nonce 和密钥解密数据

    参数:
        key: 加密密钥(32字节)
        data: 要加密的数据
        associated_data: 关联数据(进行认证但不加密)

    返回:
        解密后的原始数据(用于验证算法正确性)
    """
    # 生成随机 nonce (96位,这是AES-GCM的标准推荐长度)
    nonce = os.urandom(12)
    
    # 创建 AES-GCM 密码器实例
    aes_gcm = AESGCM(key)
    
    # 加密操作:使用 nonce 和关联数据加密原始数据
    # 输出 = 加密后的数据 + 认证标签(16字节)
    ciphertext = aes_gcm.encrypt(nonce , data , associated_data)
    
    # 解密操作:使用相同的 nonce 和关联数据解密
    decrypted_data = aes_gcm.decrypt(nonce , ciphertext , associated_data)
    
    return decrypted_data


def cha_cha20_encrypt_decrypt(key: bytes ,
                              data: bytes ,
                              associated_data: bytes) -> bytes:
    """
    ChaCha20-Poly1305 算法的加密和解密过程

    步骤:
    1. 生成随机 nonce (12字节)
    2. 使用密钥初始化 ChaCha20-Poly1305
    3. 用 nonce 和关联数据加密数据
    4. 使用相同的 nonce 和密钥解密数据

    参数:
        key: 加密密钥(32字节)
        data: 要加密的数据
        associated_data: 关联数据(进行认证但不加密)

    返回:
        解密后的原始数据(用于验证算法正确性)
    """
    # 生成随机 nonce (96位,这是ChaCha20-Poly1305的标准长度)
    nonce = os.urandom(12)
    
    # 创建 ChaCha20-Poly1305 密码器实例
    cha_cha = ChaCha20Poly1305(key)
    
    # 加密操作:使用 nonce 和关联数据加密原始数据
    # 输出 = 加密后的数据 + 认证标签(16字节)
    ciphertext = cha_cha.encrypt(nonce , data , associated_data)
    
    # 解密操作:使用相同的 nonce 和关联数据解密
    decrypted_data = cha_cha.decrypt(nonce , ciphertext , associated_data)
    
    return decrypted_data


def performance_test(algorithm_func: Callable[ [ bytes , bytes , bytes ] , bytes ] ,
                     key: bytes ,
                     data: bytes ,
                     associated_data: bytes ,
                     iterations: int = 2000) -> float:
    """
    执行加密算法的性能测试

    方法:
        1. 创建一个执行单次加密解密操作的函数
        2. 预热(执行少量操作)
        3. 使用 timeit 测量多次操作的平均时间

    参数:
        algorithm_func: 要测试的算法函数
        key: 加密密钥
        data: 测试数据
        associated_data: 关联数据
        iterations: 测试迭代次数

    返回:
        平均每次操作所需时间(毫秒)
    """
    
    # 创建测试函数(无参数,调用目标算法)
    def test_wrapper() -> bytes:
        return algorithm_func(key , data , associated_data)
    
    # 预热:运行5次避免初始开销影响测试结果
    for _ in range(5):
        _ = test_wrapper()
    
    # 执行性能测试
    time_taken = timeit.timeit(test_wrapper , number = iterations)
    
    # 计算每次操作的平均时间(毫秒)
    return (time_taken / iterations) * 1000


def demo_encryption_process(algorithm_name: str ,
                            algorithm: Any ,
                            data: bytes ,
                            associated_data: bytes) -> Tuple[ bytes , bytes ]:
    """
    演示单个算法的加密过程

    参数:
        algorithm_name: 算法名称(用于打印)
        algorithm: 密码算法实例(AES-GCM-256或ChaCha20Poly1305)
        key: 加密密钥
        data: 要加密的数据
        associated_data: 关联数据

    返回:
        nonce: 使用的随机值
        ciphertext: 加密后的数据(包含认证标签)
    """
    print(f"\n{algorithm_name} 加密演示:")
    print(f"原始数据: {data.decode()}")
    
    # 生成随机 nonce
    nonce = os.urandom(12)
    
    # 加密数据
    ciphertext = algorithm.encrypt(nonce , data , associated_data)
    print(f"加密结果 (密文+标签): {ciphertext.hex()}")
    
    return nonce , ciphertext


def tampering(algorithm_name: str ,
              algorithm: Any ,
              nonce: bytes ,
              ciphertext: bytes ,
              associated_data: bytes):
    """
    测试数据篡改检测能力

    方法:
        1. 修改密文的一个字节
        2. 尝试解密修改后的密文
        3. 捕获并报告验证失败错误

    参数:
        algorithm_name: 算法名称(用于打印)
        algorithm: 密码算法实例
        nonce: 原始加密使用的 nonce
        ciphertext: 原始密文
        associated_data: 关联数据
    """
    print(f"\n测试 {algorithm_name} 的篡改检测...")
    
    try:
        # 创建密文的可修改副本
        tampered_ciphertext = bytearray(ciphertext)
        
        # 修改一个字节(在位置10进行XOR修改)
        tampered_ciphertext[ 10 ] ^= 0x01
        
        # 尝试解密篡改后的数据
        algorithm.decrypt(nonce , bytes(tampered_ciphertext) , associated_data)
        
        # 如果成功,说明篡改检测失败(这不应该发生)
        print("警告:篡改未被检测到!")
    except Exception as e:
        # 验证失败时会引发异常
        print(f"{algorithm_name} 认证失败: {type(e).__name__} - {str(e)}")


def main_encryption_decryption():
    """主函数:协调整个演示过程"""
    # 1. 准备测试数据和参数
    password = b"my-secret-password"  # 原始密码(实际应用中应使用更复杂的密码)
    associated_data = b"authenticated-but-not-encrypted"  # 关联数据(进行认证但不加密)
    data_size = 1024 * 1024  # 1MB 测试数据
    test_data = generate_random_data(data_size)
    
    print(f"测试数据大小: {data_size / 1024:.2f} KB")
    
    # 2. 从密码派生安全的加密密钥(32字节 = 256位)
    key = derive_key(password)
    print(f"使用的密钥: {key.hex()}")
    
    # 3. 性能测试
    # 测试 AES-GCM-256
    print("\n性能测试 - AES-GCM-256:")
    aes_time = performance_test(aes_gcm_encrypt_decrypt , key , test_data , associated_data)
    print(f"加解密时间: {aes_time:.4f} 毫秒")
    
    # 测试 ChaCha20-Poly1305
    print("\n性能测试 - ChaCha20-Poly1305:")
    cha_cha_time = performance_test(cha_cha20_encrypt_decrypt , key , test_data , associated_data)
    print(f"加解密时间: {cha_cha_time:.4f} 毫秒")
    
    # 性能比较
    if aes_time < cha_cha_time:
        ratio = cha_cha_time / aes_time
        print(f"\n性能比较: AES-GCM 比 ChaCha20-Poly1305 快 {ratio:.3f}x")
    else:
        ratio = aes_time / cha_cha_time
        print(f"\n性能比较: ChaCha20-Poly1305 比 AES-GCM 快 {ratio:.3f}x")
    
    # 4. 加密过程演示
    small_data = b"Hello, AEAD algorithms!"  # 用于演示的小型数据
    
    # 创建算法实例
    aes_gcm_256 = AESGCM(key)
    cha_cha_256 = ChaCha20Poly1305(key)
    
    # 演示 AES-GCM 加密
    aes_nonce , aes_ciphertext = demo_encryption_process("AES-GCM" ,
                                                         aes_gcm_256 ,
                                                         small_data ,
                                                         associated_data)
    
    # 演示 ChaCha20-Poly1305 加密
    cha_cha_nonce , cha_cha_ciphertext = demo_encryption_process("ChaCha20-Poly1305" ,
                                                               cha_cha_256 ,
                                                               small_data ,
                                                               associated_data)
    
    # 5. 解密验证
    aes_decrypted = aes_gcm_256.decrypt(aes_nonce , aes_ciphertext , associated_data)
    cha_cha_decrypted = cha_cha_256.decrypt(cha_cha_nonce , cha_cha_ciphertext , associated_data)
    print(f"\nAES-GCM 解密结果: {aes_decrypted.decode()}")
    print(f"ChaCha20-Poly1305 解密结果: {cha_cha_decrypted.decode()}")
    
    # 6. 篡改测试
    tampering("AES-GCM" , aes_gcm_256 , aes_nonce , aes_ciphertext , associated_data)
    tampering("ChaCha20-Poly1305" , cha_cha_256 , cha_cha_nonce , cha_cha_ciphertext , associated_data)


if __name__ == "__main__":
    main_encryption_decryption()

ChaCha20-Poly1305 与 AES-CBC、AES-GCM的特性对比