3.13 加密解密函数:密码运算的“生产线“
加密解密函数概览
PKCS#11规范第5.8-5.11节定义了加密解密函数,实现数据的加密和解密操作。
加密解密函数分类:
传统加密函数(Section 5.8):
────────────────────────────────────────
C_EncryptInit 初始化加密操作
C_Encrypt 单步加密
C_EncryptUpdate 多步加密中间步骤
C_EncryptFinal 多步加密结束
传统解密函数(Section 5.10):
────────────────────────────────────────
C_DecryptInit 初始化解密操作
C_Decrypt 单步解密
C_DecryptUpdate 多步解密中间步骤
C_DecryptFinal 多步解密结束
消息式加密函数(Section 5.9, 5.11):
────────────────────────────────────────
C_EncryptMessage 单步消息加密
C_EncryptMessageBegin 开始消息加密
C_EncryptMessageNext 继续消息加密
C_EncryptMessageFinal 完成消息加密
C_DecryptMessage 单步消息解密
C_DecryptMessageBegin 开始消息解密
C_DecryptMessageNext 继续消息解密
C_DecryptMessageFinal 完成消息解密
单步操作 vs 多步操作
PKCS#11支持两种加密解密模式:
单步操作(Single-part):
应用程序 PKCS#11
│ │
│ C_EncryptInit │
│─────────────────────→│
│ │
│ C_Encrypt(全部数据) │
│─────────────────────→│
│ │ 加密
│←────────────────────│
│ 返回密文 │
│ │
完成!
多步操作(Multi-part):
应用程序 PKCS#11
│ │
│ C_EncryptInit │
│─────────────────────→│
│ │
│ C_EncryptUpdate(数据1)│
│─────────────────────→│
│←────────────────────│ 部分密文
│ │
│ C_EncryptUpdate(数据2)│
│─────────────────────→│
│←────────────────────│ 部分密文
│ │
│ C_EncryptUpdate(数据3)│
│─────────────────────→│
│←────────────────────│ 部分密文
│ │
│ C_EncryptFinal │
│─────────────────────→│
│←────────────────────│ 最后密文
│ │
完成!
C_EncryptInit:初始化加密
函数原型:
CK_RV C_EncryptInit(
CK_SESSION_HANDLE hSession,
CK_MECHANISM_PTR pMechanism,
CK_OBJECT_HANDLE hKey
);
参数说明:
参数含义:
hSession 会话句柄
pMechanism 加密机制(算法+参数)
hKey 加密密钥句柄
机制结构:
CK_MECHANISM mechanism = {
.mechanism = CKM_AES_CBC, // 机制类型
.pParameter = &iv, // 机制参数(如IV)
.ulParameterLen = 16 // 参数长度
};
密钥约束:密钥必须设置CKA_ENCRYPT = TRUE。
/* 检查密钥是否支持加密 */
CK_ATTRIBUTE getEncrypt = {CKA_ENCRYPT, NULL_PTR, 0};
rv = C_GetAttributeValue(hSession, hKey, &getEncrypt, 1);
if (/* encrypt == CK_FALSE */) {
printf("密钥不支持加密\n");
return CKR_KEY_FUNCTION_NOT_PERMITTED;
}
AES加密示例
/* AES-CBC加密 */
CK_MECHANISM mechanism = {
CKM_AES_CBC,
iv, // 16字节IV
16
};
rv = C_EncryptInit(hSession, &mechanism, hAesKey);
if (rv != CKR_OK) {
printf("加密初始化失败: %d\n", rv);
return;
}
/* 单步加密 */
CK_BYTE plaintext[] = "Hello, World! This is a test.";
CK_BYTE ciphertext[64];
CK_ULONG cipherLen = sizeof(ciphertext);
rv = C_Encrypt(hSession, plaintext, sizeof(plaintext)-1,
ciphertext, &cipherLen);
if (rv == CKR_OK) {
printf("加密成功,密文长度=%lu\n", cipherLen);
}
C_Encrypt:单步加密
函数原型:
CK_RV C_Encrypt(
CK_SESSION_HANDLE hSession,
CK_BYTE_PTR pData, // 明文
CK_ULONG ulDataLen, // 明文长度
CK_BYTE_PTR pEncryptedData, // 密文缓冲区
CK_ULONG_PTR pulEncryptedDataLen // 密文长度
);
两步调用惯例:先获取大小,再获取数据。
/* 第一步:获取密文大小 */
CK_ULONG cipherLen;
rv = C_Encrypt(hSession, plaintext, plainLen, NULL_PTR, &cipherLen);
/* 第二步:获取密文 */
CK_BYTE *ciphertext = malloc(cipherLen);
rv = C_Encrypt(hSession, plaintext, plainLen, ciphertext, &cipherLen);
C_EncryptUpdate/C_EncryptFinal:多步加密
/* 多步AES加密 */
CK_MECHANISM mechanism = {CKM_AES_CBC, iv, 16};
rv = C_EncryptInit(hSession, &mechanism, hAesKey);
/* 分块处理 */
CK_BYTE plaintext[1024] = "Hello HSM! This is a multi-part encryption test.";
CK_BYTE ciphertext[1024 + 16]; // 预留padding空间
CK_ULONG totalCipherLen = 0;
CK_ULONG chunkLen;
/* 第一块 */
chunkLen = 0;
rv = C_EncryptUpdate(hSession, plaintext, 512,
ciphertext, &chunkLen);
totalCipherLen += chunkLen;
/* 第二块 */
chunkLen = sizeof(ciphertext) - totalCipherLen;
rv = C_EncryptUpdate(hSession, plaintext + 512, 512,
ciphertext + totalCipherLen, &chunkLen);
totalCipherLen += chunkLen;
/* 结束加密(处理padding) */
CK_ULONG finalLen = sizeof(ciphertext) - totalCipherLen;
rv = C_EncryptFinal(hSession, ciphertext + totalCipherLen, &finalLen);
totalCipherLen += finalLen;
printf("总密文长度=%lu\n", totalCipherLen);
解密函数:C_Decrypt系列
解密函数与加密函数对称:
加密 解密
────────────────────────────────────────
C_EncryptInit → C_DecryptInit
C_Encrypt → C_Decrypt
C_EncryptUpdate → C_DecryptUpdate
C_EncryptFinal → C_DecryptFinal
解密示例:
/* AES-CBC解密 */
CK_MECHANISM mechanism = {CKM_AES_CBC, iv, 16};
rv = C_DecryptInit(hSession, &mechanism, hAesKey);
/* 单步解密 */
CK_BYTE plaintext[64];
CK_ULONG plainLen = sizeof(plaintext);
rv = C_Decrypt(hSession, ciphertext, cipherLen,
plaintext, &plainLen);
if (rv == CKR_OK) {
printf("解密成功,明文长度=%lu\n", plainLen);
}
密钥属性约束
加密解密操作受到密钥属性的约束:
密钥属性约束:
加密操作:
- CKA_ENCRYPT = TRUE(密钥必须支持加密)
解密操作:
- CKA_DECRYPT = TRUE(密钥必须支持解密)
私钥解密:
- CKA_PRIVATE = TRUE(需要登录)
- CKA_SENSITIVE = TRUE(密钥值不可读取)
常见错误码:
错误码含义:
CKR_KEY_FUNCTION_NOT_PERMITTED
→ 密钥不支持此操作(CKA_ENCRYPT/DECRYPT = FALSE)
CKR_KEY_TYPE_INCONSISTENT
→ 密钥类型与机制不匹配
CKR_KEY_HANDLE_INVALID
→ 密钥句柄无效
CKR_USER_NOT_LOGGED_IN
→ 需要登录才能使用私钥
数据长度约束
不同机制对数据长度有不同要求:
机制数据长度约束:
AES-ECB:
- 输入必须是16字节的整数倍
- 违反返回CKR_DATA_LEN_RANGE
AES-CBC:
- 输入必须是16字节的整数倍
- C_EncryptFinal会添加padding
AES-GCM:
- 输入长度无限制
- 输出密文长度 = 输入长度 + 16(认证标签)
RSA-PKCS:
- 输入长度 ≤ 密钥长度 - 11(padding开销)
- 2048位密钥最多加密245字节
RSA-OAEP:
- 输入长度 ≤ 密钥长度 - 2*hashLen - 2
- SHA-256时,2048位密钥最多加密190字节
流式处理的优势
多步操作适合流式数据处理:
流式加密场景:
大文件加密(1GB视频):
┌─────────────────────────────────────┐
│ 单步加密问题: │
│ - 需要1GB明文缓冲区 │
│ - 需要1GB+密文缓冲区 │
│ - 内存压力大 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 多步加密优势: │
│ - 读取64KB → 加密 → 写入64KB │
│ - 只需要64KB缓冲区 │
│ - 内存效率高 │
│ - 可边读边加密 │
└─────────────────────────────────────┘
车载流式加密示例:
/* 车载日志加密流式处理 */
CK_MECHANISM mechanism = {CKM_AES_CBC, iv, 16};
rv = C_EncryptInit(hSession, &mechanism, hLogKey);
while (/* 有日志数据 */) {
CK_BYTE logChunk[4096];
CK_ULONG chunkLen = read_log_data(logChunk, 4096);
CK_BYTE cipherChunk[4096 + 16];
CK_ULONG cipherChunkLen = sizeof(cipherChunk);
rv = C_EncryptUpdate(hSession, logChunk, chunkLen,
cipherChunk, &cipherChunkLen);
write_encrypted_log(cipherChunk, cipherChunkLen);
}
/* 结束加密 */
CK_BYTE finalCipher[16];
CK_ULONG finalLen = 16;
rv = C_EncryptFinal(hSession, finalCipher, &finalLen);
write_encrypted_log(finalCipher, finalLen);
AES-GCM:认证加密
GCM(Galois/Counter Mode)提供加密+认证双重保障:
AES-GCM结构:
输入:
- plaintext(明文)
- AAD(Additional Authenticated Data,可选)
- IV(初始化向量,12字节推荐)
输出:
- ciphertext(密文)
- authentication tag(认证标签,16字节)
/* AES-GCM加密 */
CK_GCM_PARAMS gcmParams = {
.pIv = iv, // 12字节IV
.ulIvLen = 12,
.ulIvBits = 96, // IV位数
.pAAD = aad, // 附加认证数据
.ulAADLen = aadLen,
.ulTagBits = 128 // 标签长度(128位)
};
CK_MECHANISM mechanism = {
CKM_AES_GCM,
&gcmParams,
sizeof(gcmParams)
};
rv = C_EncryptInit(hSession, &mechanism, hAesKey);
CK_BYTE ciphertext[1024 + 16]; // 密文 + 认证标签
CK_ULONG cipherLen = sizeof(ciphertext);
rv = C_Encrypt(hSession, plaintext, plainLen,
ciphertext, &cipherLen);
// cipherLen = plainLen + 16(密文长度 + 标签长度)
解密验证
GCM解密会自动验证认证标签:
/* AES-GCM解密 */
CK_GCM_PARAMS gcmParams = {
.pIv = iv,
.ulIvLen = 12,
.pAAD = aad,
.ulAADLen = aadLen,
.ulTagBits = 128
};
CK_MECHANISM mechanism = {CKM_AES_GCM, &gcmParams, sizeof(gcmParams)};
rv = C_DecryptInit(hSession, &mechanism, hAesKey);
CK_BYTE plaintext[1024];
CK_ULONG plainLen = sizeof(plaintext);
rv = C_Decrypt(hSession, ciphertext, cipherLen,
plaintext, &plainLen);
if (rv == CKR_OK) {
// 认证成功,密文未被篡改
} else if (rv == CKR_ENCRYPTED_DATA_INVALID) {
// 认证失败!密文被篡改或IV错误
printf("认证失败,密文无效\n");
}
RSA加密解密
RSA是非对称加密,公钥加密、私钥解密:
/* RSA公钥加密 */
CK_MECHANISM mechanism = {CKM_RSA_PKCS};
rv = C_EncryptInit(hSession, &mechanism, hPublicKey);
CK_BYTE plaintext[] = "Secret message";
CK_BYTE ciphertext[256]; // 2048位RSA密钥
CK_ULONG cipherLen = sizeof(ciphertext);
rv = C_Encrypt(hSession, plaintext, sizeof(plaintext)-1,
ciphertext, &cipherLen);
/* RSA私钥解密 */
rv = C_DecryptInit(hSession, &mechanism, hPrivateKey);
CK_BYTE decrypted[256];
CK_ULONG decryptedLen = sizeof(decrypted);
rv = C_Decrypt(hSession, ciphertext, cipherLen,
decrypted, &decryptedLen);
RSA-OAEP:更安全的RSA加密
OAEP(Optimal Asymmetric Encryption Padding)比PKCS#1 v1.5更安全:
/* RSA-OAEP加密 */
CK_RSA_PKCS_OAEP_PARAMS oaepParams = {
.hashAlg = CKM_SHA256, // 哈希算法
.mgf = CKG_MGF1_SHA256, // MGF函数
.source = CKZ_DATA_SPECIFIED, // 来源类型
.pSourceData = label, // 标签数据
.ulSourceDataLen = labelLen
};
CK_MECHANISM mechanism = {
CKM_RSA_PKCS_OAEP,
&oaepParams,
sizeof(oaepParams)
};
rv = C_EncryptInit(hSession, &mechanism, hPublicKey);
rv = C_Encrypt(hSession, plaintext, plainLen,
ciphertext, &cipherLen);
操作状态管理
Session上的加密解密操作是“独占“的:
操作状态约束:
同一Session:
- 只能有一个加密操作
- 只能有一个解密操作
- 加密和解密可以同时存在
如果操作已激活:
- 再次调用C_EncryptInit返回CKR_OPERATION_ACTIVE
- 必须先完成或取消当前操作
取消操作:
/* 取消正在进行的加密操作 */
rv = C_EncryptInit(hSession, NULL_PTR, 0);
// pMechanism = NULL_PTR表示取消当前操作
if (rv == CKR_OPERATION_CANCEL_FAILED) {
// Token不支持取消操作
}
小结:加密解密函数的设计智慧
加密解密要点回顾:
1. 两种操作模式
- 单步:适合小数据量
- 多步:适合流式大数据
2. 机制决定参数
- AES-CBC需要IV
- AES-GCM需要IV、AAD、标签长度
- RSA-OAEP需要哈希算法、MGF
3. 密钥属性约束
- CKA_ENCRYPT/DECRYPT控制操作权限
- CKA_PRIVATE需要登录
4. 长度约束因机制而异
- 分组密码需要整数倍块长度
- RSA有最大加密长度限制
5. 认证加密的双重保障
- AES-GCM提供加密+认证
- 解密时自动验证完整性
6. 流式处理节省内存
- 多步操作适合大文件
- 车载日志加密场景实用
加密解密函数是PKCS#11的核心“生产线“,将明文转换为密文,保护数据的机密性。结合签名验证函数,构成完整的密码服务体系。 下一节,我们将学习签名验证函数——看身份证明的实现。
【下集预告】
C_Sign怎么签名?C_Verify怎么验证?
RSA签名和ECDSA签名有什么区别?
签名长度是什么?
下一节,签名验证函数。