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.19 RSA机制详解:非对称加密的“门面担当“

一个场景:你需要生成签名

假设你正在开发一个电子合同系统。

合同签署需要数字签名。你查了一下,发现RSA是最常用的签名算法。

但打开PKCS#11规范,搜索“RSA“——

发现15种RSA机制。

CKM_RSA_PKCS、CKM_RSA_PKCS_PSS、CKM_RSA_X_509、CKM_RSA_PKCS_OAEP…

你可能会困惑:

  • PKCS和PSS有什么区别?
  • 哪个签名机制更安全?
  • OAEP是什么,为什么加密用OAEP?

更深层的问题是:

为什么RSA有签名和加密两种用途,机制还不同?


“门面担当“的设计意图

RSA是密码学的“门面担当“——最早、最著名、最广泛使用的非对称算法:

门面担当类比:

RSA(门面担当)
│
├── 为什么是"门面"?
│   ├── 1977年发明(最早的实用非对称算法)
│   ├── 历史最悠久
│   ├── 使用最广泛
│   ├── 几乎所有密码库都支持
│   └── 开发者最熟悉的非对称算法
│
├── 为什么有多种"面孔"?
│   ├── 签名面孔(PKCS#1 v1.5、PSS)
│   ├── 加密面孔(PKCS#1 v1.5、OAEP)
│   ├── 原始面孔(X.509,不推荐)
│   └── 密钥生成面孔
│   └── 每种用途需要不同的机制
│
├── 为什么需要PSS和OAEP?
│   ├── PKCS#1 v1.5有安全漏洞
│   ├── PSS解决了签名安全问题
│   ├── OAEP解决了加密安全问题
│   └── 新机制更安全,但旧机制仍兼容
│
└── 现代选择:
    ├── 签名:PSS(推荐)
    ├── 加密:OAEP(推荐)
    └── 密钥长度:2048位以上

RSA机制概览

RSA是最经典的非对称加密算法,PKCS#11支持多种RSA机制:

RSA机制列表:

密钥生成:
CKM_RSA_PKCS_KEY_PAIR_GEN    RSA密钥对生成(PKCS#1)

签名机制:
CKM_RSA_PKCS                 PKCS#1 v1.5签名
CKM_RSA_PKCS_PSS             PSS签名(更安全)
CKM_RSA_X_509                原始RSA签名(不推荐)

加密机制:
CKM_RSA_PKCS                 PKCS#1 v1.5加密
CKM_RSA_PKCS_OAEP            OAEP加密(更安全)
CKM_RSA_X_509                原始RSA加密(不推荐)

密钥包装:
CKM_RSA_PKCS                 PKCS#1 v1.5包装
CKM_RSA_PKCS_OAEP            OAEP包装

带哈希的签名:
CKM_SHA256_RSA_PKCS          SHA-256 + PKCS签名
CKM_SHA256_RSA_PKCS_PSS      SHA-256 + PSS签名
CKM_SHA384_RSA_PKCS          SHA-384 + PKCS签名
CKM_SHA512_RSA_PKCS          SHA-512 + PKCS签名

RSA密钥生成

RSA密钥对通过C_GenerateKeyPair生成:

/* RSA密钥对生成 */

CK_MECHANISM mechanism = {CKM_RSA_PKCS_KEY_PAIR_GEN, NULL_PTR, 0};

CK_ULONG modulusBits = 2048;  // 密钥长度
CK_BYTE publicExponent[] = {0x01, 0x00, 0x01};  // 65537(推荐)

CK_BBOOL bTrue = CK_TRUE;
CK_BBOOL bFalse = CK_FALSE;

CK_ATTRIBUTE publicKeyTemplate[] = {
    {CKA_MODULUS_BITS, &modulusBits, sizeof(modulusBits)},
    {CKA_PUBLIC_EXPONENT, publicExponent, sizeof(publicExponent)},
    {CKA_ENCRYPT, &bTrue, sizeof(bTrue)},
    {CKA_VERIFY, &bTrue, sizeof(bTrue)}
};

CK_ATTRIBUTE privateKeyTemplate[] = {
    {CKA_SENSITIVE, &bTrue, sizeof(bTrue)},      // 私钥不可读取
    {CKA_EXTRACTABLE, &bFalse, sizeof(bFalse)},  // 私钥不可导出
    {CKA_DECRYPT, &bTrue, sizeof(bTrue)},
    {CKA_SIGN, &bTrue, sizeof(bTrue)}
};

CK_OBJECT_HANDLE hPublicKey, hPrivateKey;
rv = C_GenerateKeyPair(hSession, &mechanism,
                       publicKeyTemplate, 4,
                       privateKeyTemplate, 4,
                       &hPublicKey, &hPrivateKey);

密钥参数选择

RSA密钥参数选择:

密钥长度:
├── 1024位:已不安全,禁用
├── 2048位:推荐(2030年前安全)
├── 3072位:等效128位安全强度
└── 4096位:等效约140位安全强度

公钥指数:
├── 3:不推荐(安全风险)
├── 17:可用
├── 65537:推荐(标准选择)
└── 固定值,便于快速运算

安全属性:
├── CKA_SENSITIVE = TRUE:私钥值不可读取
├── CKA_EXTRACTABLE = FALSE:私钥不可导出
└── 这些属性设置后不可更改

PKCS#1 v1.5签名:经典但有问题

PKCS#1 v1.5是最早的RSA签名标准:

/* PKCS#1 v1.5签名示例 */

CK_MECHANISM mechanism = {CKM_RSA_PKCS, NULL_PTR, 0};

rv = C_SignInit(hSession, &mechanism, hPrivateKey);
rv = C_Sign(hSession, data, dataLen, signature, &sigLen);

PKCS#1 v1.5签名结构

PKCS#1 v1.5签名结构:

签名 = RSA私钥运算(填充后的数据)

填充格式:
├── 0x00 0x01 [0xFF...0xFF] 0x00 [DigestInfo]
├── DigestInfo = AlgorithmID + Hash值
└── 填充使签名长度等于模数长度

安全问题:
├── Bleichenbacher攻击(1998年)
├── 选择密文攻击
└── 可能伪造签名

建议:
├── 仅用于兼容旧系统
├── 新系统使用PSS
└── 生产环境不推荐

PSS签名:现代安全标准

PSS(Probabilistic Signature Scheme)是现代推荐:

/* PSS签名示例 */

CK_RSA_PKCS_PSS_PARAMS pssParams = {
    .hashAlg = CKM_SHA256,       // Hash算法
    .mgf = CKG_MGF1_SHA256,      // MGF函数
    .sLen = 32                   // Salt长度
};

CK_MECHANISM mechanism = {CKM_RSA_PKCS_PSS, &pssParams, sizeof(pssParams)};

rv = C_SignInit(hSession, &mechanism, hPrivateKey);
rv = C_Sign(hSession, data, dataLen, signature, &sigLen);

PSS特点

PSS签名特点:

优点:
├── 随机Salt,每次签名不同
├── 抗选择密文攻击
├── 安全性可证明
└── 现代推荐标准

参数选择:
├── hashAlg:推荐SHA-256或SHA-384
├── mgf:使用MGF1,与Hash相同
└── sLen:推荐等于Hash长度

安全建议:
├── 新系统使用PSS
├── Salt长度等于Hash长度
└── 避免sLen = 0(固定签名)

带Hash的签名机制

PKCS#11提供组合机制,自动处理Hash:

/* SHA-256 + PSS签名示例 */

CK_RSA_PKCS_PSS_PARAMS pssParams = {
    .hashAlg = CKM_SHA256,
    .mgf = CKG_MGF1_SHA256,
    .sLen = 32
};

CK_MECHANISM mechanism = {CKM_SHA256_RSA_PKCS_PSS, &pssParams, sizeof(pssParams)};

rv = C_SignInit(hSession, &mechanism, hPrivateKey);
rv = C_Sign(hSession, data, dataLen, signature, &sigLen);

// 注意:传入原始数据,不是Hash值
// HSM内部自动计算SHA-256

组合机制的好处

组合机制(带Hash的签名):

使用组合机制:
├── CKM_SHA256_RSA_PKCS_PSS
├── 输入原始数据(不是Hash)
├── HSM内部自动Hash
└── 更安全(Hash在HSM内完成)

不使用组合机制:
├── 先在外部计算Hash
├── 再调用CKM_RSA_PKCS_PSS
├── 输入Hash值
└── Hash值可能在Host暴露

推荐:使用组合机制
├── Hash在HSM内完成
├── 避免Host暴露Hash值
└── 更安全

OAEP加密:现代加密标准

OAEP(Optimal Asymmetric Encryption Padding)是推荐:

/* OAEP加密示例 */

CK_RSA_PKCS_OAEP_PARAMS oaepParams = {
    .hashAlg = CKM_SHA256,       // Hash算法
    .mgf = CKG_MGF1_SHA256,      // MGF函数
    .source = CKZ_DATA_SPECIFIED,// 源数据类型
    .pSourceData = label,        // 可选Label
    .ulSourceDataLen = labelLen  // Label长度
};

CK_MECHANISM mechanism = {CKM_RSA_PKCS_OAEP, &oaepParams, sizeof(oaepParams)};

rv = C_EncryptInit(hSession, &mechanism, hPublicKey);
rv = C_Encrypt(hSession, data, dataLen, encrypted, &encLen);

OAEP特点

OAEP加密特点:

优点:
├── 随机填充,每次加密不同
├── 抗选择密文攻击
├── 安全性可证明
└── 现代推荐标准

参数选择:
├── hashAlg:推荐SHA-256
├── mgf:使用MGF1
└── source:可设为CKZ_DATA_SPECIFIED

Label(可选):
├── 可以添加标签数据
├── 用于区分不同加密
└── 增强安全性

加密长度限制:
├── 最大明文长度 = 模数长度 - 2*Hash长度 - 2
├── 如:2048位RSA,SHA-256
├── 最大明文 = 256 - 64 - 2 = 190字节
└── 大数据需要分段或用对称加密

X.509原始RSA:不推荐

X.509是“裸RSA“,没有填充:

/* X.509原始RSA(不推荐) */

CK_MECHANISM mechanism = {CKM_RSA_X_509, NULL_PTR, 0};

rv = C_SignInit(hSession, &mechanism, hPrivateKey);
rv = C_Sign(hSession, hashValue, hashLen, signature, &sigLen);

为什么不推荐

X.509原始RSA问题:

问题:
├── 没有填充保护
├── 相同输入产生相同输出
├── 可被数学攻击
└── 不安全

使用场景:
├── 仅用于特殊协议
├── 需要明确知道风险
└── 一般应用禁用

替代:
├── 签名用PSS
├── 加密用OAEP
└── 不要用X.509

签名vs加密:机制选择

RSA有两种用途,机制不同:

签名vs加密机制选择:

用途          推荐机制           原因
──────────────────────────────────────────
签名          PSS              安全,随机Salt
签名(兼容)   PKCS#1 v1.5      旧系统兼容
加密          OAEP             安全,随机填充
加密(兼容)   PKCS#1 v1.5      旧系统兼容
密钥包装      OAEP             安全
──────────────────────────────────────────

重要区别:
├── 签名用私钥(hPrivateKey)
├── 验证用公钥(hPublicKey)
├── 加密用公钥(hPublicKey)
├── 解密用私钥(hPrivateKey)
└── 密钥不同,机制选择也不同

一个类比:门面担当的多种“面孔“

门面担当的多种面孔类比:

RSA算法(名人)
│
├── 签名面孔(盖章)
│   │
│   ├── PKCS#1 v1.5签名
│   │   ├── 像传统印章
│   │   ├── 固定格式
│   │   ├── 有安全问题
│   │   └── 仅用于兼容
│   │
│   └── PSS签名
│   │   ├── 像防伪印章
│   │   ├── 加随机Salt
│   │   ├── 每次不同
│   │   ├── 防伪造
│   │   └── 推荐使用
│   │
│   └── 组合签名(带Hash)
│   │   ├── 像全自动盖章机
│   │   ├── 自动盖Hash + 章印
│   │   └── 最推荐
│
├── 加密面孔(密封)
│   │
│   ├── PKCS#1 v1.5加密
│   │   ├── 像普通信封
│   │   ├── 有安全问题
│   │   └── 仅用于兼容
│   │
│   └── OAEP加密
│   │   ├── 像防拆信封
│   │   ├── 随机填充
│   │   ├── 防篡改
│   │   └── 推荐使用
│
└── X.509原始RSA
    ├── 像没有任何保护的裸操作
    ├── 不安全
    └── 禁用

关键:
├── 根据用途选择机制
├── 签名用PSS
├── 加密用OAEP
└── 避免X.509

本篇小结

RSA是非对称加密的“门面担当“:

机制数量

  • 约15种RSA机制
  • 密钥生成、签名、加密、包装

密钥生成

  • CKM_RSA_PKCS_KEY_PAIR_GEN
  • 推荐2048位以上

签名机制

  • PKCS#1 v1.5:经典,有问题
  • PSS:现代,推荐
  • 组合机制:带Hash,更安全

加密机制

  • PKCS#1 v1.5:经典,有问题
  • OAEP:现代,推荐

安全建议

  • 签名:PSS(或组合机制)
  • 加密:OAEP
  • 密钥:2048位以上
  • 禁用:X.509

下一节,我们将分析ECDSA与ECDH机制——椭圆曲线密码学的“新生代“。

【下集预告】

  • ECDSA如何签名?

  • ECDH如何密钥交换?

  • 椭圆曲线如何选择?

  • EC与RSA哪个更安全?

下一节,ECDSA与ECDH机制。