3、对称加密算法选型:AES vs SM4 vs ChaCha20,性能与安全性权衡

对称加密,说白了就是加密和解密用同一把钥匙。这是密码学里最基础、也最常用的技术。你想想看,从 HTTPS 连接、磁盘加密,到即时通讯的消息保护,背后都离不开它。

今天我们要聊的三个算法——AES、SM4 和 ChaCha20——是目前最主流的对称加密方案。它们各有各的脾气,选型时不能只看速度,还得看安全边界、硬件支持和合规要求。我个人习惯把这三者放在一个三角模型里权衡:安全性、性能、合规性。

核心观点:没有绝对最好的算法,只有最适合你场景的算法。选型前,先搞清楚你的威胁模型和运行环境。

3.1 三巨头速览:出身与定位

先简单认识一下这三位。

算法 设计者/标准 分组/密钥长度 核心架构 主要应用场景
AES NIST (美国标准) 128/192/256 位 SPN 结构 (Substitution-Permutation Network) 全球通用,HTTPS、Wi-Fi、全盘加密
SM4 中国国家密码管理局 128 位 Feistel 结构 (类似 DES 但更现代) 中国商用密码合规场景,政务、金融、IoT
ChaCha20 Daniel J. Bernstein 256 位 ARX 结构 (Add-Rotate-XOR) 移动端、嵌入式、TLS 1.3 可选套件

嗯,这里要注意:AES 是硬件加速的宠儿,几乎所有现代 CPU 都有 AES-NI 指令集。SM4 在国内合规场景是必选项,但国际支持度一般。ChaCha20 则是软件实现的王者,尤其在没有硬件加速的设备上表现亮眼。

3.2 性能对决:硬件加速 vs 纯软件

我在项目中遇到过好几次这样的争论:团队里有人坚持用 AES,理由是“标准、安全”;另一个人说 ChaCha20 在手机上更快。其实两边都对,关键看你的 CPU 支不支持 AES-NI。

下面这张图展示了三者在不同环境下的相对性能表现。

对称加密算法性能对比(相对吞吐量) AES-128-GCM (有 AES-NI) SM4-CBC (软件实现) ChaCha20-Poly1305 (软件实现) 高 低 相对吞吐量 桌面/服务器 (x86_64) 移动端 (ARMv8) 嵌入式 (无硬件加速) AES ~3.2 GB/s SM4 ~0.8 GB/s ChaCha20 ~1.5 GB/s AES ~2.0 GB/s SM4 ~0.6 GB/s ChaCha20 ~1.8 GB/s AES ~0.3 GB/s SM4 ~0.2 GB/s ChaCha20 ~0.7 GB/s 注:数据基于 1KB 消息块,使用 OpenSSL 1.1.1 基准测试。实际性能因实现和平台而异。

从图上可以清楚看到:

  • 桌面/服务器场景:AES 凭借硬件加速一骑绝尘。SM4 纯软件实现慢不少,ChaCha20 居中。
  • 移动端场景:ChaCha20 开始反超 AES。ARM 芯片虽然也有 AES 加速,但 ChaCha20 的 ARX 设计在低功耗设备上更省电。
  • 嵌入式场景:ChaCha20 优势明显。没有硬件加速时,AES 和 SM4 的查表操作容易受缓存时间攻击,而 ChaCha20 天然抗侧信道。

我的建议:如果你的服务器是 x86_64 且支持 AES-NI,无脑选 AES-128-GCM。如果是移动端或 IoT 设备,优先考虑 ChaCha20-Poly1305。SM4 只在需要满足国密合规时使用。

3.3 安全性深度对比

性能再快,安全不过关也是白搭。我们来逐个分析。

AES:久经考验的王者

AES 已经经过 20 多年的全球密码学家攻击检验。目前唯一有效的攻击是旁路攻击(如缓存时间攻击),但通过恒定时间实现可以防御。我个人习惯使用 AES-128 而非 AES-256,因为 128 位密钥已经足够安全,256 位只多了一轮加密,性能下降约 20%,安全增益微乎其微。

SM4:合规首选,但需谨慎

SM4 是中国国家标准,安全性经过国家密码管理局评审。但要注意:SM4 的密钥长度固定为 128 位,且分组大小也是 128 位。我在项目中遇到过一个问题:SM4 的 S 盒实现如果不小心,容易引入时序侧信道漏洞。另外,SM4 的国际密码学界分析不如 AES 充分,如果你做的是面向全球市场的产品,可能需要额外评估。

ChaCha20:现代设计的优雅

ChaCha20 是流密码,但通常与 Poly1305 认证码搭配使用。它的设计避免了 AES 的查表操作,天然抗缓存时间攻击。我记得有一次帮客户审计一个嵌入式设备,他们用 AES 做加密,结果被侧信道攻击轻松破解。换成 ChaCha20 后,问题迎刃而解。

避坑指南:我曾经见过一个团队在 ARM Cortex-M3 上硬跑 AES-CBC,结果因为没有硬件加速,加密一个 1KB 的数据包耗时 50ms,直接导致系统响应超时。后来换成 ChaCha20,耗时降到 5ms。选型前,一定要在目标硬件上做基准测试。

3.4 模式与认证:GCM vs CCM vs Poly1305

算法本身只是基础,工作模式和认证方式同样关键。下面这张表对比了常见的组合。

组合 认证方式 并行性 适用场景 注意事项
AES-GCM GMAC (Galois Message Authentication Code) 高(CTR 模式可并行) 网络协议、TLS、VPN nonce 不能重复,否则密钥可被恢复
AES-CCM CBC-MAC 低(串行处理) IoT、802.11 Wi-Fi 实现复杂,容易出错
ChaCha20-Poly1305 Poly1305 一次性 MAC 中等 移动端、嵌入式、TLS 1.3 软件实现快,无硬件依赖
SM4-GCM GMAC 高 国密合规场景 需要国密算法库支持

嗯,这里要特别强调:永远不要只用加密,不用认证。我在早期项目中犯过这个错误——只用了 AES-CBC 加密,结果攻击者通过修改密文实现了数据篡改。从那以后,我坚持使用认证加密模式(AEAD),比如 GCM 或 ChaCha20-Poly1305。

3.5 选型决策树

说了这么多,到底怎么选?我整理了一个简单的决策流程。

对称加密算法选型决策树 开始选型 需要满足国密合规? 是 SM4-GCM 否 有硬件 AES 加速? 是 AES-128-GCM 否 ChaCha20-Poly1305 选型要点 • 国密合规:SM4-GCM 是唯一选择,但注意实现质量 • 有硬件加速:AES-128-GCM 性能最佳,生态最成熟 • 无硬件加速:ChaCha20-Poly1305 更安全、更高效 • 所有场景:优先使用 AEAD 模式,避免单独加密

3.6 实战代码示例

最后,给一段实际代码。我用 Go 语言演示三种算法的 AEAD 加密用法。你可以在自己的项目中直接参考。

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "fmt"
    "io"

    "golang.org/x/crypto/chacha20poly1305"
    "github.com/tjfoc/gmsm/sm4"
)

// AES-128-GCM 加密
func encryptAESGCM(key, plaintext []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }
    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }
    return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

// SM4-GCM 加密
func encryptSM4GCM(key, plaintext []byte) ([]byte, error) {
    block, err := sm4.NewCipher(key)
    if err != nil {
        return nil, err
    }
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }
    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }
    return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

// ChaCha20-Poly1305 加密
func encryptChaCha20(key, plaintext []byte) ([]byte, error) {
    aead, err := chacha20poly1305.New(key)
    if err != nil {
        return nil, err
    }
    nonce := make([]byte, aead.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }
    return aead.Seal(nonce, nonce, plaintext, nil), nil
}

func main() {
    key := make([]byte, 16) // AES-128 / SM4 需要 16 字节
    key256 := make([]byte, 32) // ChaCha20 需要 32 字节
    rand.Read(key)
    rand.Read(key256)

    plaintext := []byte("Hello, 密码学选型实战!")

    aesCiphertext, _ := encryptAESGCM(key, plaintext)
    sm4Ciphertext, _ := encryptSM4GCM(key, plaintext)
    chachaCiphertext, _ := encryptChaCha20(key256, plaintext)

    fmt.Printf("AES-128-GCM 密文长度: %d\n", len(aesCiphertext))
    fmt.Printf("SM4-GCM 密文长度: %d\n", len(sm4Ciphertext))
    fmt.Printf("ChaCha20-Poly1305 密文长度: %d\n", len(chachaCiphertext))
}

小提示:代码中的 nonce 必须每次加密都重新生成,且不能重复。我习惯用 crypto/rand 生成 12 字节的 nonce,这是 GCM 和 ChaCha20 的推荐长度。

好了,对称加密的选型就聊到这里。记住:没有银弹,只有权衡。AES 是标准答案,SM4 是合规答案,ChaCha20 是灵活答案。根据你的场景,选一个,然后把它用对。