4. 密钥属性详解:CKA_CLASS、CKA_KEY_TYPE、CKA_VALUE、CKA_ID 等核心属性

各位好。这一章我们来啃 PKCS#11 里最实在的部分——密钥属性。说白了,一个密钥对象在 里是什么、能干什么、值是多少,全靠这些属性定义。搞不清楚属性,你生成的密钥可能根本没法用。

4.1 CKA_CLASS —— 密钥的“身份证”

CKA_CLASS 是所有对象的第一属性。它告诉 :“我是谁?”这个属性必须在创建对象时指定,而且一旦写入就不能改。我见过不少新手在这个属性上栽跟头——少写了它,C_CreateObject 直接返回 CKR_TEMPLATE_INCOMPLETE。

它的取值只有几个,但每个都很关键:

取值含义说明
CKO_SECRET_KEY对称密钥比如 AES、DES 的密钥
CKO_PUBLIC_KEY公钥RSA、ECC 的公钥部分
CKO_PRIVATE_KEY私钥对应的私钥部分
CKO_DOMAIN_PARAMETERS域参数ECC 曲线参数、DH 参数等
个人习惯:我在项目中喜欢把 CKA_CLASS 放在模板的第一项。不是为了好看,是因为很多 HSM 在处理模板时按顺序解析,把类放前面能提前做类型检查,避免后面的属性白配置。

4.2 CKA_KEY_TYPE —— 算法的“说明书”

确定了对象是密钥后,下一步就是告诉系统:“我这个密钥用的是什么算法。”这就是 CKA_KEY_TYPE 的事情。

它和 CKA_CLASS 是搭档关系。比如你设 CKA_CLASS = CKO_SECRET_KEY,那 CKA_KEY_TYPE 就只能是 CKK_AES、CKK_DES3 这类对称算法。如果是 CKO_PUBLIC_KEY,那就对应 CKK_RSA、CKK_EC 等。

CKA_KEY_TYPE常见配合的 CKA_CLASS典型长度
CKK_AESCKO_SECRET_KEY128/192/256 bits
CKK_RSACKO_PUBLIC_KEY / CKO_PRIVATE_KEY2048/4096 bits
CKK_ECCKO_PUBLIC_KEY / CKO_PRIVATE_KEY256/384/521 bits
CKK_GENERIC_SECRETCKO_SECRET_KEY任意长度
注意:有些 支持 CKK_GENERIC_SECRET,但实际使用时可能会限制长度必须是 8 的倍数。我曾经在兼容性测试中踩过这个坑——生成一个 17 字节的通用密钥,结果在某个老型号 HSM 上死活通不过。所以建议你在生产环境里,先查一下 的 C_GetTokenInfo 返回的硬件限制。

4.3 CKA_VALUE —— 密钥的“血肉”

CKA_VALUE 是实际密钥数据。对称密钥的字节流、公钥的模数、私钥的私密信息,都藏在这个属性里。但它有个重要规矩——只有 CKO_SECRET_KEY 类型的对象才直接暴露 CKA_VALUE。公钥和私钥不这么用,它们分别有自己的结构属性,比如 CKA_MODULUS、CKA_PUBLIC_EXPONENT、CKA_PRIVATE_EXPONENT 等。

来看一个典型例子:创建一个 AES-128 密钥。

CK_OBJECT_CLASS class = CKO_SECRET_KEY;
CK_KEY_TYPE keyType = CKK_AES;
CK_BYTE value[16] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 
                     0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
CK_BBOOL trueVal = CK_TRUE;

CK_ATTRIBUTE template[] = {
    {CKA_CLASS, &class, sizeof(class)},
    {CKA_KEY_TYPE, &keyType, sizeof(keyType)},
    {CKA_VALUE, value, sizeof(value)},
    {CKA_, &trueVal, sizeof(trueVal)},
    {CKA_PRIVATE, &trueVal, sizeof(trueVal)},
    {CKA_ENCRYPT, &trueVal, sizeof(trueVal)}
};

CK_SESSION_HANDLE hSession;
CK_OBJECT_HANDLE hKey;
rv = C_CreateObject(hSession, template, 5, &hKey);
避坑指南:设置 CKA_VALUE 时,长度必须和 CKA_KEY_TYPE 匹配。比如 AES-128 必须是 16 字节,AES-256 是 32 字节。我曾经见过有人把 24 字节的 AES 密钥传进去,结果 返回 CKR_ATTRIBUTE_VALUE_INVALID。记住了,不是所有 都会自动帮你补零或截断。

4.4 CKA_ID —— 密钥的“家庭地址”

如果说 CKA_CLASS 是身份证,那 CKA_ID 就是家庭住址。它用来把一组相关的密钥关联起来——最常见的就是把公钥和对应的私钥绑定在一起。

比如你有一个 RSA 密钥对。公钥和私钥是两个独立的 PKCS#11 对象,但它们共享同一个 CKA_ID。这样当你需要查找某个密钥对的公钥来做加密时,可以用 C_FindObjectsInit 传入 CKA_CLASS = CKO_PUBLIC_KEY 和 CKA_ID = 某个值,一下子就定位到了。

我的做法是:用一个 4 字节的整数作为 ID,比如 0x00000001、0x00000002,然后另外用 CKA_LABEL 取一个人类可读的名字,比如 "RSA Signing Key #1"。这样机器查找快,人也看得懂。

你想想看,如果所有密钥都不设置 CKA_ID,那 里几百个对象怎么管理?当然可以遍历一个个对比,但性能就太差了。

我建议:哪怕你只有一个密钥,也给它设一个 CKA_ID。不是必须的,但这是个好习惯。以后密钥多了,你会感谢当初自己这么做的。

4.5 其他重要属性一览

除了上面四个,还有几个属性也挺常见:

  • CKA_LABEL:给密钥取个名字。UTF-8 字符串,长度一般限制在 32 字节内。纯辅助作用,不影响功能。
  • CKA_:CK_TRUE 表示密钥存储在 里(掉电不丢),CK_FALSE 表示仅会话有效(一断电就没了)。
  • CKA_PRIVATE:CK_TRUE 表示需要登录才能访问,CK_FALSE 表示公开可查。
  • CKA_MODULUS、CKA_PUBLIC_EXPONENT、CKA_PRIVATE_EXPONENT:RSA 的公钥/私钥专属属性。
  • CKA_EC_POINT、CKA_EC_PARAMS:ECC 的专属属性。

这些属性怎么搭配,取决于你的业务场景。做一个加密密钥,至少要设 CKA_ENCRYPT = CK_TRUE;做签名密钥,就设 CKA_SIGN = CK_TRUE。嗯,这里要注意,如果你啥都不设,默认都是 CK_FALSE,那密钥就是个“哑巴”密钥,啥也干不了。

4.6 核心逻辑关系图

下面这张图梳理了密钥属性的核心关系。你可以看到 CKA_CLASS 决定对象类型,CKA_KEY_TYPE 决定算法,CKA_ID 做关联,而 CKA_VALUE 和各类算法专属属性承载实际数据。

PKCS#11 密钥属性核心逻辑 CKA_CLASS CKO_SECRET_KEY CKO_PUBLIC_KEY CKO_PRIVATE_KEY CKA_KEY_TYPE (CKK_AES/...) CKA_VALUE (密钥字节) CKA_KEY_TYPE (CKK_RSA/...) CKA_MODULUS / CKA_EC_POINT CKA_KEY_TYPE (CKK_RSA/...) CKA_PRIVATE_EXPONENT /... CKA_ID: 同一个 ID 关联公钥与私钥

这张图我建议你存一下。做 PKCS#11 开发时,对着图配模板,基本不会漏属性。我自己早期做项目时就把它贴在工位墙上,一眼扫过去就知道该设什么。