3.15 哈希与MAC函数:数据的“指纹与印章“
哈希与MAC函数概览
PKCS#11规范第5.12节定义了哈希(Digest)函数,用于计算数据的摘要值。
哈希函数列表:
函数名 作用
─────────────────────────────────────
C_DigestInit 初始化哈希操作
C_Digest 单步哈希计算
C_DigestUpdate 多步哈希中间步骤
C_DigestKey 将密钥加入哈希计算
C_DigestFinal 多步哈希结束
MAC(消息认证码):
─────────────────────────────────────
通过签名函数实现(C_Sign系列)
使用HMAC机制(CKM_SHA256_HMAC等)
哈希函数的基本概念
哈希函数将任意长度数据映射为固定长度的摘要值:
哈希运算示意:
输入(任意长度) 输出(固定长度)
───────────────────────────────────────
"Hello" → SHA-256 → 32字节摘要
"Hello World" → SHA-256 → 32字节摘要
1GB视频文件 → SHA-256 → 32字节摘要
特性:
- 单向性:无法从摘要还原原始数据
- 确定性:相同输入产生相同输出
- 抗碰撞性:难以找到不同输入产生相同摘要
C_DigestInit:初始化哈希
函数原型:
CK_RV C_DigestInit(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism
);
常用哈希机制:
哈希机制列表:
CKM_SHA_1 SHA-1(160位,已不推荐)
CKM_SHA224 SHA-224(224位)
CKM_SHA256 SHA-256(256位,最常用)
CKM_SHA384 SHA-384(384位)
CKM_SHA512 SHA-512(512位)
CKM_MD5 MD5(128位,已不安全)
CKM_RIPEMD160 RIPEMD-160(160位)
国密哈希(厂商扩展,非PKCS#11 v3.1标准):
CKM_SM3 SM3(256位,中国标准)
注意:SM3等国密机制是厂商对PKCS#11的扩展,并非OASIS PKCS#11 v3.1标准规范的一部分。
单步哈希示例
/* SHA-256哈希计算 */
CK_MECHANISM mechanism = {CKM_SHA256, NULL_PTR, 0};
rv = C_DigestInit(hSession, &mechanism);
CK_BYTE data[] = "Hello, PKCS#11!";
CK_BYTE digest[32]; // SHA-256输出32字节
CK_ULONG digestLen = sizeof(digest);
rv = C_Digest(hSession, data, sizeof(data)-1, digest, &digestLen);
if (rv == CKR_OK) {
printf("哈希值:");
for (int i = 0; i < digestLen; i++) {
printf("%02x", digest[i]);
}
printf("\n");
}
// 输出:a1b2c3d4...(32字节的十六进制)
C_Digest:单步哈希
函数原型:
CK_RV C_Digest(
CK_SESSION_HANDLE hSession,
CK_BYTE_PTR pData, // 输入数据
CK_ULONG ulDataLen, // 数据长度
CK_BYTE_PTR pDigest, // 摘要缓冲区
CK_ULONG_PTR pulDigestLen // 摘要长度
);
两步调用惯例:
/* 先获取摘要长度 */
CK_ULONG digestLen;
rv = C_Digest(hSession, data, dataLen, NULL_PTR, &digestLen);
/* 再获取摘要值 */
CK_BYTE *digest = malloc(digestLen);
rv = C_Digest(hSession, data, dataLen, digest, &digestLen);
多步哈希计算
/* 流式哈希计算(适合大文件) */
CK_MECHANISM mechanism = {CKM_SHA256, NULL_PTR, 0};
rv = C_DigestInit(hSession, &mechanism);
/* 分块处理 */
CK_BYTE buffer[4096];
CK_ULONG bytesRead;
while ((bytesRead = read_file_chunk(buffer, 4096)) > 0) {
rv = C_DigestUpdate(hSession, buffer, bytesRead);
}
/* 获取最终摘要 */
CK_BYTE digest[32];
CK_ULONG digestLen = sizeof(digest);
rv = C_DigestFinal(hSession, digest, &digestLen);
C_DigestKey:将密钥加入哈希
函数原型:
CK_RV C_DigestKey(
CK_SESSION_HANDLE hSession,
CK_OBJECT_HANDLE hKey
);
用途:计算密钥的哈希值,用于密钥验证或密钥指纹。
/* 计算密钥的哈希 */
CK_MECHANISM mechanism = {CKM_SHA256, NULL_PTR, 0};
rv = C_DigestInit(hSession, &mechanism);
/* 将密钥加入哈希 */
rv = C_DigestKey(hSession, hSecretKey);
/* 可选:再加入其他数据 */
rv = C_DigestUpdate(hSession, additionalData, dataLen);
/* 获取摘要 */
CK_BYTE digest[32];
CK_ULONG digestLen = sizeof(digest);
rv = C_DigestFinal(hSession, digest, &digestLen);
注意:C_DigestKey计算的是密钥值本身(即使CKA_SENSITIVE=TRUE),这是规范定义的特殊行为。
HMAC:带密钥的哈希
HMAC(Hash-based MAC)使用密钥进行哈希运算,提供消息认证能力。
HMAC vs 普通哈希:
普通哈希:
- 无密钥参与
- 只验证数据完整性
- 任何人都能计算
HMAC:
- 密钥参与运算
- 验证完整性+真实性
- 只有持有密钥者才能计算
HMAC机制:
HMAC机制列表:
CKM_SHA_1_HMAC HMAC-SHA1(已不推荐)
CKM_SHA224_HMAC HMAC-SHA224
CKM_SHA256_HMAC HMAC-SHA256(最常用)
CKM_SHA384_HMAC HMAC-SHA384
CKM_SHA512_HMAC HMAC-SHA512
CKM_MD5_HMAC HMAC-MD5(已不安全)
国密HMAC:
CKM_SM3_HMAC HMAC-SM3
HMAC计算示例
HMAC使用签名函数(C_Sign)实现:
/* HMAC-SHA256计算 */
CK_MECHANISM mechanism = {CKM_SHA256_HMAC, NULL_PTR, 0};
rv = C_SignInit(hSession, &mechanism, hHmacKey);
CK_BYTE message[] = "Message to authenticate";
CK_BYTE mac[32]; // HMAC输出长度与哈希相同
CK_ULONG macLen = sizeof(mac);
rv = C_Sign(hSession, message, sizeof(message)-1, mac, &macLen);
if (rv == CKR_OK) {
printf("HMAC值:");
for (int i = 0; i < macLen; i++) {
printf("%02x", mac[i]);
}
printf("\n");
}
HMAC密钥对象
HMAC密钥是特殊的对称密钥对象:
/* 创建HMAC密钥 */
CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY;
CK_KEY_TYPE keyType = CKK_GENERIC_SECRET; // 通用密钥类型
CK_BYTE keyId[] = {0x01};
CK_BYTE keyValue[] = {...}; // HMAC密钥值
CK_BBOOL true = CK_TRUE;
CK_BBOOL sign = CK_TRUE; // 支持签名(HMAC)
CK_BBOOL verify = CK_TRUE; // 支持验证
CK_ATTRIBUTE keyTemplate[] = {
{CKA_CLASS, &keyClass, sizeof(keyClass)},
{CKA_KEY_TYPE, &keyType, sizeof(keyType)},
{CKA_TOKEN, &true, sizeof(true)},
{CKA_ID, keyId, sizeof(keyId)},
{CKA_VALUE, keyValue, sizeof(keyValue)},
{CKA_SIGN, &sign, sizeof(sign)},
{CKA_VERIFY, &verify, sizeof(verify)}
};
CK_OBJECT_HANDLE hHmacKey;
rv = C_CreateObject(hSession, keyTemplate, 7, &hHmacKey);
HMAC验证
使用C_Verify验证HMAC:
/* HMAC验证 */
CK_MECHANISM mechanism = {CKM_SHA256_HMAC, NULL_PTR, 0};
rv = C_VerifyInit(hSession, &mechanism, hHmacKey);
CK_BYTE message[] = "Message to authenticate";
CK_BYTE receivedMac[32];
CK_ULONG macLen = 32;
rv = C_Verify(hSession, message, sizeof(message)-1, receivedMac, macLen);
if (rv == CKR_OK) {
printf("HMAC验证成功,消息真实\n");
} else if (rv == CKR_SIGNATURE_INVALID) {
printf("HMAC验证失败,消息可能被篡改\n");
}
双重操作:哈希+加密
PKCS#11支持同时进行哈希和加密操作:
/* 同时加密和计算哈希 */
CK_MECHANISM encryptMech = {CKM_AES_CBC, iv, 16};
CK_MECHANISM digestMech = {CKM_SHA256, NULL_PTR, 0};
rv = C_EncryptInit(hSession, &encryptMech, hAesKey);
rv = C_DigestInit(hSession, &digestMech);
/* 同时加密和哈希 */
CK_BYTE data[] = "Data to encrypt and hash";
CK_BYTE ciphertext[64];
CK_ULONG cipherLen = sizeof(ciphertext);
CK_BYTE digest[32];
CK_ULONG digestLen = sizeof(digest);
/* 使用C_DigestEncryptUpdate */
rv = C_DigestEncryptUpdate(hSession, data, sizeof(data)-1,
ciphertext, &cipherLen);
// 同时进行加密和哈希计算
/* 结束操作 */
rv = C_EncryptFinal(hSession, ciphertext + cipherLen, &finalCipherLen);
rv = C_DigestFinal(hSession, digest, &digestLen);
C_DigestEncryptUpdate
函数原型:
CK_RV C_DigestEncryptUpdate(
CK_SESSION_HANDLE hSession,
CK_BYTE_PTR pData,
CK_ULONG ulDataLen,
CK_BYTE_PTR pEncryptedData,
CK_ULONG_PTR pulEncryptedDataLen
);
同时进行哈希计算和加密,输出加密结果。
C_DecryptDigestUpdate
同时进行解密和哈希计算:
/* 同时解密和计算哈希 */
CK_MECHANISM decryptMech = {CKM_AES_CBC, iv, 16};
CK_MECHANISM digestMech = {CKM_SHA256, NULL_PTR, 0};
rv = C_DecryptInit(hSession, &decryptMech, hAesKey);
rv = C_DigestInit(hSession, &digestMech);
CK_BYTE decrypted[64];
CK_ULONG decryptedLen = sizeof(decrypted);
rv = C_DecryptDigestUpdate(hSession, ciphertext, cipherLen,
decrypted, &decryptedLen);
// 解密的同时计算哈希
rv = C_DecryptFinal(hSession, decrypted + decryptedLen, &finalLen);
rv = C_DigestFinal(hSession, digest, &digestLen);
哈希在车载安全中的应用
车载哈希应用场景:
固件完整性验证:
┌─────────────────────────────────────┐
│ Boot ROM │
│ │
│ 1. 计算固件哈希 │
│ digest = SHA256(firmware) │
│ │
│ 2. 验证签名 │
│ verify(publicKey, signature, │
│ digest) │
│ │
│ 3. 哈希匹配 → 固件可信 │
└─────────────────────────────────────┘
日志完整性:
┌─────────────────────────────────────┐
│ 日志系统 │
│ │
│ 每条日志: │
│ log_entry = { │
│ timestamp, │
│ message, │
│ hmac = HMAC(key, message) │
│ } │
│ │
│ 验证:检查HMAC确保日志未被篡改 │
└─────────────────────────────────────┘
消息认证:
┌─────────────────────────────────────┐
│ V2X通信 │
│ │
│ 发送: │
│ message = { │
│ data, │
│ mac = HMAC(key, data) │
│ } │
│ │
│ 接收验证: │
│ verify_mac(key, received_mac) │
└─────────────────────────────────────┘
哈希算法选择
哈希算法安全性对比:
算法 输出长度 安全性 推荐度
───────────────────────────────────────
MD5 128位 已破解 ❌不推荐
SHA-1 160位 已破解 ❌不推荐
SHA-224 224位 安全 ⚠️可用
SHA-256 256位 安全 ✓推荐
SHA-384 384位 安全 ✓高安全
SHA-512 512位 安全 ✓高安全
SM3 256位 安全 ✓中国标准
选择建议:
- 车载安全:SHA-256(平衡性能和安全)
- 高安全场景:SHA-384/SHA-512
- 中国合规:SM3
- 避免:MD5、SHA-1(已被破解)
小结:哈希与MAC函数的设计智慧
哈希与MAC要点回顾:
1. 哈希函数的单步/多步模式
- 单步:小数据量,一次计算
- 多步:大文件流式处理
2. HMAC通过签名函数实现
- 使用C_Sign/C_Verify
- 密钥类型:CKK_GENERIC_SECRET
3. 双重操作效率高
- C_DigestEncryptUpdate:加密同时哈希
- C_DecryptDigestUpdate:解密同时哈希
4. C_DigestKey特殊行为
- 可以计算敏感密钥的哈希
- 用于密钥验证和指纹
5. 算法选择关键
- 避免MD5、SHA-1
- 推荐SHA-256、SHA-384
- 中国场景使用SM3
哈希函数为PKCS#11提供了数据完整性验证能力,HMAC则在此基础上增加了消息认证能力,两者共同构成数据安全的“指纹与印章“。 下一节,我们将学习密钥管理函数——看密钥的生命周期。
【下集预告】
C_GenerateKeyPair怎么生成密钥?
C_DeriveKey怎么派生密钥?
C_WrapKey怎么备份密钥?
下一节,密钥管理函数。