3.14 签名验证函数:密码世界的“签名盖章“
为什么需要签名?
在现实生活中,签名是什么?
签名的本质:
- 证明“是我签署的“(身份认证)
- 证明“内容未被篡改“(完整性)
- 证明“我已同意此内容“(不可否认)
在数字世界,数字签名实现了同样的功能:
- 身份认证:签名证明了消息来自哪个密钥持有者
- 完整性:签名证明了消息未被篡改
- 不可否认:签名者无法否认曾签署此消息
签名的基本流程
数字签名有两个操作:
签名流程:
签名者(私钥持有者):
┌─────────────────────────────────────┐
│ │
│ 消息 → 哈希 → 用私钥签名 → 签名值 │
│ │
└─────────────────────────────────────┘
│
│ 发送:消息 + 签名值 + 公钥
▼
验证者(公钥持有者):
┌─────────────────────────────────────┐
│ │
│ 消息 + 签名值 + 公钥 → 验证签名 │
│ │
│ 结果:有效 or 无效 │
│ │
└─────────────────────────────────────┘
PKCS#11的签名函数
PKCS#11规范第5.13节定义了签名函数:
| 函数 | 描述 |
|---|---|
| C_SignInit | 初始化签名操作 |
| C_Sign | 单步签名(消息较短) |
| C_SignUpdate | 多步签名,添加更多数据 |
| C_SignFinal | 多步签名,完成签名 |
| C_SignRecoverInit | 初始化可恢复签名 |
| C_SignRecover | 可恢复签名(签名包含消息) |
C_SignInit:准备签名
功能:初始化签名操作,指定签名密钥和签名机制。
原型:
CK_RV C_SignInit(CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_OBJECT_HANDLE hKey);
参数:
hSession:Session句柄pMechanism:签名机制(如CKM_RSA_PKCS)hKey:私钥句柄
示例:
CK_MECHANISM mechanism = {CKM_RSA_PKCS, NULL_PTR, 0};
CK_OBJECT_HANDLE hPrivateKey;
CK_RV rv;
// 查找私钥
// ...
// 初始化签名
rv = C_SignInit(hSession, &mechanism, hPrivateKey);
if (rv != CKR_OK) {
printf("签名初始化失败:%08x\n", rv);
return rv;
}
注意事项:
- 必须使用私钥(CKO_PRIVATE_KEY)
- 必须在已登录Session中(私钥通常是CKA_PRIVATE = TRUE)
- 一个Session只能有一个活跃的签名操作
C_Sign:单步签名
功能:一次性签名整个消息。
原型:
CK_RV C_Sign(CK_SESSION_HANDLE hSession,
CK_BYTE_PTR pData,
CK_ULONG ulDataLen,
CK_BYTE_PTR pSignature,
CK_ULONG_PTR pulSignatureLen);
参数:
hSession:Session句柄pData:待签名数据ulDataLen:数据长度pSignature:签名值输出缓冲区pulSignatureLen:签名值长度
两步调用模式:
CK_BYTE data[] = "Hello, PKCS#11!";
CK_BYTE signature[256];
CK_ULONG signatureLen = 256;
CK_RV rv;
// 第一步:获取签名长度
rv = C_Sign(hSession, data, sizeof(data)-1, NULL_PTR, &signatureLen);
if (rv != CKR_OK) {
printf("获取签名长度失败:%08x\n", rv);
return rv;
}
// 第二步:执行签名
rv = C_Sign(hSession, data, sizeof(data)-1, signature, &signatureLen);
if (rv == CKR_OK) {
printf("签名成功,长度:%lu字节\n", signatureLen);
// 打印签名值(十六进制)
for (CK_ULONG i = 0; i < signatureLen; i++) {
printf("%02x", signature[i]);
}
printf("\n");
}
C_SignUpdate + C_SignFinal:多步签名
功能:分批签名大数据。
适用场景:
- 文件签名(文件可能很大)
- 流式数据签名(数据持续到达)
示例:
CK_BYTE chunk1[] = "First chunk of data...";
CK_BYTE chunk2[] = "Second chunk of data...";
CK_BYTE chunk3[] = "Third chunk of data...";
CK_BYTE signature[256];
CK_ULONG signatureLen = 256;
CK_RV rv;
// 初始化签名
rv = C_SignInit(hSession, &mechanism, hPrivateKey);
// 分批添加数据
rv = C_SignUpdate(hSession, chunk1, sizeof(chunk1)-1);
rv = C_SignUpdate(hSession, chunk2, sizeof(chunk2)-1);
rv = C_SignUpdate(hSession, chunk3, sizeof(chunk3)-1);
// 完成签名
rv = C_SignFinal(hSession, signature, &signatureLen);
if (rv == CKR_OK) {
printf("多步签名成功\n");
}
签名机制的选择
PKCS#11支持多种签名机制(规范第6章):
RSA签名机制:
| 机制 | 描述 | 特点 |
|---|---|---|
| CKM_RSA_PKCS | PKCS#1 v1.5签名 | 最常用,兼容性好 |
| CKM_RSA_PKCS_PSS | RSASSA-PSS签名 | 更安全,抗选择明文攻击 |
| CKM_RSA_X_509 | X.509原始签名 | 无填充,不推荐 |
ECDSA签名机制:
| 机制 | 描述 | |:—|:—|:—| | CKM_ECDSA | ECDSA签名 | | CKM_ECDSA_SHA1 | SHA-1哈希后ECDSA签名 | | CKM_ECDSA_SHA256 | SHA-256哈希后ECDSA签名 |
使用示例:
/* RSA PKCS#1 v1.5签名 */
CK_MECHANISM rsaPkcsMech = {CKM_RSA_PKCS, NULL_PTR, 0};
/* RSA PSS签名(需要参数) */
CK_RSA_PKCS_PSS_PARAMS pssParams = {CKM_SHA256, CKG_MGF1_SHA256, 32};
CK_MECHANISM rsaPssMech = {CKM_RSA_PKCS_PSS, &pssParams, sizeof(pssParams)};
/* ECDSA签名 */
CK_MECHANISM ecdsaMech = {CKM_ECDSA, NULL_PTR, 0};
/* ECDSA with SHA256 */
CK_MECHANISM ecdsaSha256Mech = {CKM_ECDSA_SHA256, NULL_PTR, 0};
验证函数
PKCS#11规范第5.15节定义了验证函数:
| 函数 | 描述 | |:—|:—|:—| | C_VerifyInit | 初始化验证操作 | | C_Verify | 单步验证 | | C_VerifyUpdate | 多步验证,添加更多数据 | | C_VerifyFinal | 多步验证,完成验证 | | C_VerifyRecoverInit | 初始化可恢复验证 | | C_VerifyRecover | 可恢复验证 |
C_Verify:验证签名
功能:验证签名是否有效。
原型:
CK_RV C_Verify(CK_SESSION_HANDLE hSession,
CK_BYTE_PTR pData,
CK_ULONG ulDataLen,
CK_BYTE_PTR pSignature,
CK_ULONG ulSignatureLen);
返回值:
- CKR_OK:签名有效
- CKR_SIGNATURE_INVALID:签名无效
- CKR_SIGNATURE_LEN_RANGE:签名长度错误
示例:
CK_BYTE data[] = "Hello, PKCS#11!";
CK_BYTE signature[256];
CK_ULONG signatureLen;
CK_OBJECT_HANDLE hPublicKey;
CK_MECHANISM mechanism = {CKM_RSA_PKCS, NULL_PTR, 0};
CK_RV rv;
// 查找公钥
// ...
// 初始化验证
rv = C_VerifyInit(hSession, &mechanism, hPublicKey);
if (rv != CKR_OK) {
printf("验证初始化失败\n");
return rv;
}
// 执行验证
rv = C_Verify(hSession, data, sizeof(data)-1, signature, signatureLen);
if (rv == CKR_OK) {
printf("签名验证成功:签名有效\n");
} else if (rv == CKR_SIGNATURE_INVALID) {
printf("签名验证失败:签名无效\n");
} else {
printf("签名验证错误:%08x\n", rv);
}
完整的签名验证流程示例
CK_RV perform_sign_verify_demo(CK_SESSION_HANDLE hSession) {
CK_RV rv;
CK_MECHANISM mechanism = {CKM_RSA_PKCS, NULL_PTR, 0};
CK_BYTE message[] = "Test message for signing";
CK_BYTE signature[256];
CK_ULONG signatureLen = 256;
// 1. 查找私钥
CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY;
CK_KEY_TYPE rsaKeyType = CKK_RSA;
CK_ATTRIBUTE findTemplate[] = {
{CKA_CLASS, &privKeyClass, sizeof(privKeyClass)},
{CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType)},
};
CK_OBJECT_HANDLE hPrivateKey, hPublicKey;
CK_ULONG ulCount;
rv = C_FindObjectsInit(hSession, findTemplate, 2);
rv = C_FindObjects(hSession, &hPrivateKey, 1, &ulCount);
rv = C_FindObjectsFinal(hSession);
if (ulCount == 0) {
printf("未找到私钥\n");
return CKR_KEY_HANDLE_INVALID;
}
// 2. 初始化签名
rv = C_SignInit(hSession, &mechanism, hPrivateKey);
if (rv != CKR_OK) {
printf("签名初始化失败:%08x\n", rv);
return rv;
}
// 3. 执行签名
rv = C_Sign(hSession, message, sizeof(message)-1, signature, &signatureLen);
if (rv != CKR_OK) {
printf("签名失败:%08x\n", rv);
return rv;
}
printf("签名成功,长度:%lu字节\n", signatureLen);
// 4. 查找公钥
CK_OBJECT_CLASS pubKeyClass = CKO_PUBLIC_KEY;
CK_ATTRIBUTE pubFindTemplate[] = {
{CKA_CLASS, &pubKeyClass, sizeof(pubKeyClass)},
{CKA_KEY_TYPE, &rsaKeyType, sizeof(rsaKeyType)},
};
rv = C_FindObjectsInit(hSession, pubFindTemplate, 2);
rv = C_FindObjects(hSession, &hPublicKey, 1, &ulCount);
rv = C_FindObjectsFinal(hSession);
if (ulCount == 0) {
printf("未找到公钥\n");
return CKR_KEY_HANDLE_INVALID;
}
// 5. 初始化验证
rv = C_VerifyInit(hSession, &mechanism, hPublicKey);
if (rv != CKR_OK) {
printf("验证初始化失败:%08x\n", rv);
return rv;
}
// 6. 执行验证
rv = C_Verify(hSession, message, sizeof(message)-1, signature, signatureLen);
if (rv == CKR_OK) {
printf("验证成功:签名有效\n");
} else if (rv == CKR_SIGNATURE_INVALID) {
printf("验证失败:签名无效\n");
} else {
printf("验证错误:%08x\n", rv);
}
return rv;
}
消息签名模式(PKCS#11 v3.0新增)
PKCS#11 v3.0新增了“消息签名“模式(规范第5.14节):
| 函数 | 描述 | |:—|:—|:—| | C_MessageSignInit | 初始化消息签名 | | C_SignMessage | 一次性消息签名 | | C_SignMessageBegin | 开始消息签名 | | C_SignMessageNext | 添加数据并可选签名 | | C_MessageSignFinal | 完成消息签名 |
消息签名模式与传统签名的区别:
- 传统签名:先Init,再Sign,需要两个函数
- 消息签名:SignMessage一步完成,更简洁
/* 传统签名 */
rv = C_SignInit(hSession, &mechanism, hKey);
rv = C_Sign(hSession, data, len, sig, &sigLen);
/* 消息签名(v3.0) */
CK_VOID_PTR pParams = NULL_PTR;
rv = C_MessageSignInit(hSession, &mechanism, hKey);
rv = C_SignMessage(hSession, pParams, paramLen, data, len, sig, &sigLen);
本篇小结
签名验证是密码操作的核心功能:
签名函数:
- C_SignInit:初始化签名(指定私钥和机制)
- C_Sign:单步签名
- C_SignUpdate + C_SignFinal:多步签名
验证函数:
- C_VerifyInit:初始化验证(指定公钥和机制)
- C_Verify:单步验证
- C_VerifyUpdate + C_VerifyFinal:多步验证
签名机制:
- RSA:CKM_RSA_PKCS(PKCS#1 v1.5)、CKM_RSA_PKCS_PSS(PSS)
- ECDSA:CKM_ECDSA、CKM_ECDSA_SHA256
返回值:
- CKR_OK:签名有效
- CKR_SIGNATURE_INVALID:签名无效
消息签名模式(v3.0):
- C_SignMessage:一步完成签名
下一节,我们将学习哈希与MAC函数——数据的“指纹与印章“。
【下集预告】
PKCS#11支持哪些哈希算法?
如何进行多步哈希运算?
HMAC如何使用C_Sign实现?
双操作函数(DigestEncrypt等)有何用途?
下一节,哈希与MAC函数详解。