密码学基础(上):对称加密、非对称加密与哈希函数

说实话,很多做嵌入式开发的朋友一听到「密码学」三个字就头大。我当年也一样。总觉得这是数学博士才该操心的事。直到我第一次接手一个物联网网关项目——设备连上云,数据明文传输,结果被人在中间抓包抓了个底朝天。嗯,从那以后,我再也不敢跳过密码学基础了。

这一章,咱们就把嵌入式TLS里最核心的三种密码学工具讲透:对称加密、非对称加密和哈希函数。你不需要成为数学家,但得知道它们各自干什么、怎么配合、以及——最容易在哪栽跟头。

一句话记住三者的关系:

  • 对称加密:快,但密钥怎么安全地告诉对方?
  • 非对称加密:慢,但能解决密钥分发问题。
  • 哈希函数:不可逆,用来验证数据有没有被篡改。

TLS握手协议,说白了就是让这三者配合干活。

密码学基础三件套关系图 密码学基础三件套 · 各司其职 对称加密 AES / ChaCha20 非对称加密 RSA / ECC 哈希函数 SHA-256 TLS 中的配合逻辑: 1. 非对称加密(RSA/ECC)安全地交换对称密钥 2. 对称加密(AES/ChaCha20)加密后续所有通信数据 3. 哈希函数(SHA-256)验证消息完整性,防止篡改

2.1 对称加密:AES 与 ChaCha20

对称加密,说白了就是「一把钥匙开一把锁」。加密和解密用同一个密钥。好处是快,坏处是——你怎么把密钥安全地给对方?

在嵌入式世界里,对称加密是主力。因为MCU算力有限,非对称加密太慢了。我见过一个项目,用RSA加密每一条传感器数据,结果电池两天就耗光了。后来换成AES,续航直接翻了三倍。

AES(高级加密标准)

AES是目前最主流的对称加密算法。它有三种密钥长度:128、192、256位。我个人习惯,只要硬件支持,一律用AES-256。虽然128位在可预见的未来也够安全,但多几字节的密钥换来几十年的安心,值。

AES的工作模式很多,嵌入式里最常见的是:

  • ECB(电子密码本):别用。同样的明文块会加密成同样的密文块,模式太明显。我在一个智能锁项目里见过有人用ECB,结果攻击者通过重放攻击直接开了门。
  • CBC(密码块链接):需要初始向量(IV),每个密文块依赖前一块。安全性还行,但无法并行解密。
  • GCM(伽罗瓦/计数器模式):我最推荐。它同时提供加密和完整性校验(认证加密)。TLS 1.3里AES-GCM是标配。

我的经验:在资源受限的MCU上,如果硬件没有AES加速指令,纯软件实现AES-GCM会有点吃力。这时候可以考虑ChaCha20-Poly1305——它纯软件跑得飞快,而且安全性不输AES。

ChaCha20

ChaCha20是Google力推的流加密算法。它比AES更「亲民」——不需要硬件加速,在ARM Cortex-M系列上跑得特别顺。Poly1305是它的配套消息认证码,组合起来就是ChaCha20-Poly1305。

我记得有一次给一个低功耗蓝牙门锁做固件升级。MCU是Cortex-M0,没有AES硬件加速。用AES-GCM软件实现,加密1KB数据要花12毫秒。换成ChaCha20-Poly1305,直接降到3毫秒。电池续航从两个月变成了半年。

特性 AES-256-GCM ChaCha20-Poly1305
算法类型 分组加密(128位块) 流加密
硬件加速 需要(多数MCU支持) 不需要
纯软件性能 较慢 快(约3-5倍)
安全强度 高 高
TLS 1.3支持 是(标配) 是(可选)
推荐场景 有AES硬件加速的MCU 无硬件加速、低功耗设备

2.2 非对称加密:RSA 与 ECC

非对称加密,也叫公钥密码学。它有两把钥匙:公钥公开,私钥自己藏好。公钥加密的数据只有私钥能解开。反过来,私钥签名,公钥可以验证。

它的核心价值是密钥分发。你不需要提前和对方共享密钥,只需要拿到对方的公钥,就能安全地通信。但代价是——慢。比对称加密慢几百到几千倍。

RSA

RSA是历史最悠久的非对称算法。它基于大整数分解的数学难题。密钥越长越安全,但计算量也越大。

  • RSA-2048:当前最低安全标准。TLS 1.2里很常见。
  • RSA-4096:更安全,但嵌入式设备上签名一次可能要几百毫秒。

我曾经在一个工业网关项目里用RSA-4096做TLS握手。每次握手要花1.2秒,客户嫌慢。后来换成ECC,降到200毫秒。嗯,这就是差距。

避坑指南:我曾经在STM32上实现RSA私钥签名时,忘了检查「私钥是否有效」。结果有一次烧录的固件私钥被意外截断,签名一直失败,设备连不上服务器。排查了两天才发现。记住:永远在代码里加一层密钥有效性校验。

ECC(椭圆曲线密码学)

ECC用更短的密钥提供和RSA同等级别的安全强度。比如:

  • ECC-256 ≈ RSA-3072
  • ECC-384 ≈ RSA-7680

在嵌入式设备上,ECC的优势太明显了:密钥小、签名快、省电。TLS 1.3里,ECC(尤其是ECDHE密钥交换)是绝对主力。

常用的椭圆曲线有:

  • P-256(secp256r1):最广泛支持,TLS标配。
  • P-384(secp384r1):更高安全等级,但计算量也大一些。
  • Curve25519(X25519):我个人的最爱。它设计上就防了很多侧信道攻击,而且纯软件实现性能极好。

我的建议:新项目直接上ECC,别再用RSA了。除非你要兼容老设备。TLS 1.3已经默认禁用RSA密钥交换,只保留了RSA签名。未来是ECC的天下。

2.3 哈希函数:SHA-256

哈希函数,你可以把它想象成一个「数字指纹」。输入任意长度的数据,输出固定长度的摘要。而且——不可逆。你没法从摘要反推出原始数据。

在TLS里,哈希函数干三件事:

  1. 消息完整性校验:确保数据在传输过程中没被改过。
  2. 数字签名:先对消息哈希,再对哈希值签名,效率高。
  3. 密钥派生:TLS握手时,用哈希函数从共享密钥派生出会话密钥。

SHA-256

SHA-256是SHA-2家族的一员,输出256位(32字节)的摘要。它是目前TLS里使用最广泛的哈希函数。TLS 1.3里,证书验证、密钥派生、Finished消息校验,全用它。

为什么是SHA-256,而不是更短的SHA-1?

  • SHA-1:已经被攻破。Google在2017年就展示了实际的碰撞攻击。别用了。
  • SHA-256:目前安全,而且效率足够高。在Cortex-M4上,软件实现SHA-256大概能跑到几十MB/s,够用。
  • SHA-3:更新,但嵌入式支持还不普遍。除非有特殊需求,否则SHA-256是稳妥之选。

一个常见的误解:哈希不是加密。加密可以解密,哈希不行。哈希是单向的。如果有人跟你说「我把密码用SHA-256加密了」,你可以礼貌地纠正他——那叫哈希,不叫加密。

代码示例:在嵌入式C里计算SHA-256

下面是一个使用mbedTLS库计算SHA-256的示例。mbedTLS是嵌入式TLS的标配库,几乎所有MCU都能跑。

#include <mbedtls/sha256.h>
#include <stdio.h>
#include <string.h>

void calc_sha256_example(void) {
    mbedtls_sha256_context ctx;
    unsigned char input[] = "Hello, Embedded TLS!";
    unsigned char output[32];  // SHA-256输出32字节
    int i;

    // 1. 初始化上下文
    mbedtls_sha256_init(&ctx);
    // 2. 启动SHA-256计算(0表示SHA-256,1表示SHA-224)
    mbedtls_sha256_starts(&ctx, 0);
    // 3. 输入数据
    mbedtls_sha256_update(&ctx, input, strlen((char*)input));
    // 4. 输出摘要
    mbedtls_sha256_finish(&ctx, output);
    // 5. 清理
    mbedtls_sha256_free(&ctx);

    // 打印摘要(十六进制)
    printf("SHA-256: ");
    for (i = 0; i < 32; i++) {
        printf("%02x", output[i]);
    }
    printf("\n");
}

这段代码在几乎所有MCU上都能跑。你只需要把mbedTLS移植到你的平台就行。我建议你把这个函数封装成一个工具,调试TLS握手时经常需要手动算哈希来对比。

注意:在中断服务函数里别调SHA-256。mbedTLS的哈希计算不是中断安全的。我曾经在一个RTOS项目里踩过这个坑——在中断里调了mbedtls_sha256_update,结果系统随机死机。后来把哈希计算挪到任务线程里,问题解决。

2.4 小结:三种工具怎么配合?

咱们用TLS握手的实际流程来串一遍:

  1. 客户端和服务器通过非对称加密(ECC)完成密钥交换,协商出一个临时对称密钥。
  2. 双方用哈希函数(SHA-256)验证握手消息的完整性,确保没人篡改过。
  3. 握手完成后,用对称加密(AES-GCM或ChaCha20-Poly1305)加密所有应用数据。

你看,没有一个是多余的。对称加密负责速度,非对称加密负责安全分发密钥,哈希函数负责完整性校验。三者缺一不可。