Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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签名有什么区别?

签名长度是什么?

下一节,签名验证函数。