密码学基础(上):对称加密与非对称加密的区别,哈希函数(SHA256)的特性与应用

各位做嵌入式开发的同行,咱们今天聊点实在的。签名验签这事儿,说白了就是靠密码学撑起来的。你如果不懂加密和哈希,那签名验签就是个黑盒子。我当年刚入行时也踩过这个坑——照着文档把代码写出来了,但出了问题根本不知道从哪查起。

所以这一章,咱们把基础打牢。我会结合自己这些年做嵌入式安全项目的经验,把对称加密、非对称加密、哈希函数这三个核心概念掰开揉碎了讲清楚。

一、对称加密:一把钥匙开一把锁

对称加密,顾名思义,加密和解密用的是同一把密钥。就像你家里的门锁,用同一把钥匙锁门和开门。

我最早接触对称加密是在做一款智能门锁产品时。当时需要在门锁和手机App之间传输开锁指令,用的就是AES-128加密。嗯,这里要注意,AES是目前嵌入式领域最常用的对称加密算法。

对称加密的核心特点:

  • 加密密钥 = 解密密钥
  • 运算速度快,适合大数据量加密
  • 密钥分发是个大问题——你得想办法把密钥安全地交给对方

常见的对称加密算法有这些:

算法 密钥长度 嵌入式常用场景
AES 128 / 192 / 256 bit 固件加密、通信加密
DES / 3DES 56 / 112 / 168 bit 老旧设备兼容(不建议新设计使用)
SM4 128 bit 国密标准,国内项目常用

我曾经踩过的坑:有一次做OTA升级固件加密,我用了ECB模式。结果固件头部有固定格式,加密后还能看出明显的模式重复。后来改成CBC模式,加个随机IV,问题才解决。记住:ECB模式在嵌入式里基本用不得,除非你明确知道自己在做什么。

二、非对称加密:公钥私钥,各司其职

非对称加密就很有意思了。它有两把钥匙:一把公钥,一把私钥。公钥可以公开,私钥自己藏好。用公钥加密的数据,只有对应的私钥能解开;反过来,用私钥签名的数据,公钥可以验证。

你想想看,这解决了什么问题?对,密钥分发问题!我不用费劲把密钥传给你,直接把公钥扔网上就行。这也是签名验签能成立的根本原因。

嵌入式里最常用的非对称算法是RSA和ECC(椭圆曲线)。我个人习惯用ECC,因为同样的安全强度下,ECC的密钥更短,运算更快,对MCU更友好。

算法 安全强度对比 密钥长度 签名速度
RSA-2048 高 2048 bit 较慢(尤其私钥操作)
ECC-256 高(等效RSA-3072) 256 bit 快
SM2 高 256 bit 快(国密标准)

我的建议:如果你的MCU有硬件加密引擎,优先用ECC。如果MCU比较老、没有硬件加速,RSA-2048也能用,但签名操作可能会卡几百毫秒。我在一个STM32F103的项目上试过,RSA-2048私钥签名大概要300ms,而ECC-256只要50ms左右。

三、对称 vs 非对称:到底怎么选?

其实没有谁替代谁的说法。实际项目中,两者是配合使用的。我画了一张图,你看完就明白了:

对称加密 vs 非对称加密:混合加密流程 发送方 接收方 生成对称密钥 K 接收方公钥 PK ① 公钥 PK 公开 ② 用 PK 加密 K → 密文 C1 接收方私钥解密得 K ③ 用 K 加密大数据 → 密文 C2 用 K 解密得原始数据 混合加密:非对称加密传递对称密钥,对称加密加密实际数据 既解决了密钥分发问题,又保证了大数据量的加密效率

说白了,实际项目中我们用的是「混合加密」:用非对称加密来传递对称密钥,然后用对称密钥加密真正的数据。这样既解决了密钥分发问题,又保证了加解密速度。

四、哈希函数:数据的数字指纹

哈希函数,你可以把它理解成数据的「数字指纹」。不管输入多大,输出都是固定长度。而且,哪怕只改一个bit,输出的哈希值都会完全不一样。

嵌入式里最常用的哈希函数就是SHA-256。输出256位,也就是32字节。我为什么偏爱SHA-256?因为它在安全性和计算开销之间取得了很好的平衡。SHA-1已经被证明不安全了,SHA-512对很多MCU来说又太吃资源。

SHA-256的核心特性:

  • 单向性:从哈希值无法反推出原始数据
  • 抗碰撞性:很难找到两个不同输入产生相同哈希值
  • 雪崩效应:输入微小变化,输出天翻地覆
  • 固定输出:不管输入多大,输出永远是32字节

我在项目中遇到过这样一个场景:需要验证固件是否被篡改。做法很简单——发布固件时计算SHA-256哈希值,设备端收到固件后重新计算哈希,对比一下就知道有没有被改过。

// 嵌入式SHA-256计算示例(基于mbedTLS)
#include <mbedtls/sha256.h>

int verify_firmware(const uint8_t *fw, size_t fw_len, 
                    const uint8_t *expected_hash) {
    uint8_t computed_hash[32];
    mbedtls_sha256_context ctx;
    
    mbedtls_sha256_init(&ctx);
    mbedtls_sha256_starts(&ctx, 0);  // 0表示SHA-224,1表示SHA-256
    mbedtls_sha256_update(&ctx, fw, fw_len);
    mbedtls_sha256_finish(&ctx, computed_hash);
    mbedtls_sha256_free(&ctx);
    
    // 对比哈希值
    return memcmp(computed_hash, expected_hash, 32) == 0;
}

我曾经踩过的坑:有一次做固件完整性校验,我直接比较了哈希值,但忘了考虑字节序问题。PC端算出来的哈希和设备端算出来的,看起来一样,但字节序反了。排查了半天才发现是大小端的问题。所以,做哈希校验时,一定要确认两端的字节序一致。

五、哈希函数在签名验签中的角色

你可能会问:哈希函数跟签名验签有什么关系?关系大了去了。

非对称签名算法(比如RSA、ECDSA)有个特点——它们不能处理大数据。你想想看,一个固件可能几兆甚至几十兆,如果用私钥直接对整个固件签名,那计算量谁也扛不住。

所以实际做法是:先对固件计算SHA-256哈希,得到32字节的摘要,然后对这个摘要进行签名。验签时也一样,先计算哈希,再验证签名。

这样做有两个好处:

  • 效率高:签名操作只处理32字节,而不是整个固件
  • 安全性好:哈希函数的抗碰撞性保证了「改数据必改哈希」

我的建议:在设计签名验签方案时,一定要把哈希计算放在设备端。有些方案图省事,让服务器算好哈希再传给设备,设备直接验证签名。这就有安全漏洞了——攻击者可以同时篡改固件和哈希值。记住:哈希必须在本地计算,签名才可信。

六、本章小结

好了,这一章的内容就这些。咱们把三个核心概念理清楚了:

  • 对称加密:速度快,适合加密数据,但密钥分发麻烦
  • 非对称加密:解决了密钥分发问题,但速度慢,适合加密小数据
  • 哈希函数:数据的数字指纹,单向、抗碰撞,是签名验签的基础