加密基础:对称加密(AES-GCM)、非对称加密(ECC)、哈希函数(SHA-256)、密钥派生函数(KDF)
好,咱们进入正题。加密基础这块,说白了就是整个SOME/IP安全通信的「地基」。你想想看,如果地基没打好,上面盖的楼再漂亮也没用。我在做车载以太网安全方案时,见过太多因为加密选型不当导致性能崩盘的案例。
今天咱们就聊四个核心组件:AES-GCM、ECC、SHA-256 和 KDF。它们各司其职,又相互配合。
1. 对称加密:AES-GCM
对称加密,就是加密和解密用同一把钥匙。速度快,适合大数据量。但问题来了——钥匙怎么安全地传给对方?这个咱们后面讲非对称加密时再聊。
AES-GCM 是我个人最推荐的模式。为什么?因为它同时干了三件事:加密、完整性校验、防重放攻击。GCM 模式在加密的同时会生成一个认证标签(Authentication Tag),接收方可以用它来验证数据有没有被篡改。
核心要点:AES-GCM 是「认证加密」模式,一次操作搞定机密性和完整性。
我在项目中遇到过一个问题:某ECU用AES-CBC模式加密SOME/IP消息,结果攻击者通过修改密文块顺序,成功让接收方解析出错误数据。后来换成GCM模式,认证标签一校验,任何篡改都立刻暴露。
来个代码示例,看看怎么用OpenSSL实现AES-GCM加密:
#include <openssl/evp.h>
// 加密函数
int aes_gcm_encrypt(unsigned char *plaintext, int plaintext_len,
unsigned char *aad, int aad_len,
unsigned char *key, unsigned char *iv,
unsigned char *ciphertext, unsigned char *tag) {
EVP_CIPHER_CTX *ctx;
int len, ciphertext_len;
ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);
EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv);
EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len); // 附加认证数据
EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len);
ciphertext_len = len;
EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
ciphertext_len += len;
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag); // 获取认证标签
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
避坑指南:IV(初始化向量)绝对不能重复使用!我曾经见过一个团队,为了省事把IV写死在代码里,结果同一个密钥+同一个IV加密了两条消息,攻击者直接就能破解出密钥。IV每次都要随机生成,长度推荐12字节。
2. 非对称加密:ECC
非对称加密,用一对钥匙:公钥加密,私钥解密。公钥可以公开,私钥自己藏好。
ECC(椭圆曲线密码学)比RSA强在哪?更短的密钥长度,同等的安全强度。256位的ECC密钥,安全性和3072位的RSA差不多。在车载ECU这种资源受限的环境里,这优势太明显了。
| 算法 | 密钥长度(位) | 安全强度(位) | 性能 |
|---|---|---|---|
| RSA | 3072 | 128 | 慢 |
| ECC | 256 | 128 | 快 |
| ECC | 384 | 192 | 中等 |
在SOME/IP安全通信中,ECC主要用于密钥交换和数字签名。比如,两个ECU要建立安全会话,先用ECC交换一个临时密钥,再用这个临时密钥派生AES-GCM的加密密钥。
我个人习惯用Curve25519(X25519),它设计简洁,实现安全,不容易踩坑。相比之下,NIST P-256曲线虽然也常用,但实现时稍不注意就会引入侧信道漏洞。
注意:ECC的私钥生成必须使用高质量的随机数发生器。我曾经在某个项目中发现,ECU的随机数发生器因为硬件设计缺陷,生成的私钥只有几十种可能,攻击者枚举一下就能破解。后来我们强制要求使用硬件TRNG(真随机数发生器)。
3. 哈希函数:SHA-256
哈希函数,就是把任意长度的数据,压缩成一个固定长度的「指纹」。SHA-256输出256位(32字节),碰撞概率极低。
哈希函数在SOME/IP安全中的用途:
- 消息完整性校验:发送方计算哈希值,接收方重新计算,对比是否一致
- 数字签名:先对消息哈希,再对哈希值签名,效率更高
- 密钥派生:配合KDF使用,生成各种子密钥
你想想看,如果直接对整条消息做签名,那计算量得多大?先哈希再签名,是业界标准做法。
SHA-256的实现很简单,OpenSSL一行调用:
#include <openssl/sha.h>
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, data, data_len);
SHA256_Final(hash, &sha256);
小技巧:在SOME/IP的SD(服务发现)阶段,可以用SHA-256对服务描述做哈希,防止攻击者篡改服务信息。我参与的一个项目中,就靠这个机制拦截了一次中间人攻击。
4. 密钥派生函数:KDF
KDF,说白了就是「从一个密钥,派生出多个密钥」。为什么需要这个?
假设你通过ECC交换了一个共享密钥,但这个密钥不能直接用于AES-GCM加密。为什么?因为:
- 原始密钥可能太长或太短
- 你需要多个密钥(加密密钥、认证密钥、IV生成种子等)
- 直接使用原始密钥会暴露信息
KDF就是解决这些问题的。它接收一个输入密钥材料(IKM),加上一些上下文信息(salt、info),输出指定长度的派生密钥。
在SOME/IP安全中,常用的KDF是HKDF(基于HMAC的密钥派生函数)。它分两步:
- 提取(Extract):用salt对IKM做HMAC,生成伪随机密钥(PRK)
- 扩展(Expand):用PRK和info,生成所需长度的输出密钥
代码示例:
#include <openssl/kdf.h>
int hkdf_extract(const unsigned char *salt, size_t salt_len,
const unsigned char *ikm, size_t ikm_len,
unsigned char *prk, size_t prk_len) {
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256());
EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len);
EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len);
EVP_PKEY_derive(pctx, prk, &prk_len);
EVP_PKEY_CTX_free(pctx);
return prk_len;
}
关键点:KDF中的「info」参数很重要。它用来绑定派生密钥的用途。比如,加密密钥的info设为"enc-key",认证密钥的info设为"auth-key"。这样即使一个密钥泄露,也不会影响其他密钥。
5. 四者如何配合?
咱们捋一下,在SOME/IP安全会话建立过程中,这四个组件是怎么协作的:
- ECC:两个ECU通过椭圆曲线Diffie-Hellman(ECDH)交换,生成共享密钥
- KDF:用HKDF从共享密钥派生出AES-GCM的加密密钥和IV
- AES-GCM:用派生出的密钥加密SOME/IP消息,同时生成认证标签
- SHA-256:在握手阶段,对证书和参数做哈希,确保完整性
这个流程,我在多个量产项目中验证过。性能上,ECC握手一次大约耗时10-20ms(在Cortex-M4上),之后AES-GCM加密每条消息只需微秒级。完全满足车载通信的实时性要求。
最后提醒:加密算法本身是安全的,但实现方式可能引入漏洞。比如,AES-GCM的认证标签长度不要截短,至少用16字节;ECC的标量乘法要用常数时间实现,防止时序攻击;KDF的salt要随机生成,不要固定。这些坑,我都踩过,希望你别再踩。
好,加密基础就聊到这儿。下一章咱们讲SOME/IP的安全会话建立流程,到时候会把这些组件串起来,看看它们在实际协议栈中是怎么跑的。