使用 pkcs11 模块使用公钥加密数据失败

4

我正在使用Python的pkcs11包来访问存储在Yubikey 5上的X.509证书。使用pkcs11对象访问证书、公钥和私钥以及签名和验证签名都正常工作。然而,我无法理解为什么使用公钥进行加密不起作用。以下是我的代码:

import pkcs11
from pkcs11 import Attribute, ObjectClass, KeyType, util
lib = pkcs11.lib('/usr/lib/x86_64-linux-gnu/pkcs11/onepin-opensc-pkcs11.so')
token = lib.get_token(token_label='PIV Card Holder pin (PIV_II)'
session = token.open(user_pin=pin)
# Getting a private and a public key as pkcs11 Object
private = next(session.get_objects({
    Attribute.CLASS: ObjectClass.PRIVATE_KEY, 
}))
public = next(session.get_objects({
    Attribute.CLASS: ObjectClass.PUBLIC_KEY, 
}))
data = 'Hello, world!'
sig = private.sign(data) # Works!
sig_verif = public.verify(data, sig) # Works!
print("Signature is valid? "+str(sig_verif)) # True
# So far, everything above worked fine.
# ----------
# Now, this is the part that does not work
encrypt_data = public.encrypt(data) # Fails!

上述最后一行代码报错,错误提示为pkcs11.exceptions.FunctionNotSupported。我做了一些调查研究,发现可能是因为我使用的openSC库文件(*.so)不支持此函数(encrypt)。但考虑到签名功能可以正常使用,这一点令人难以置信。
为确保我可以在会话上下文之外使用该特定公钥,我尝试了使用Crypto包的以下代码:
from Crypto.Cipher import PKCS1_OAEP
public_key = RSA.importKey(public[Attribute.VALUE]) # The content of pkcs11 public key as DER
cipher = PKCS1_OAEP.new(public_key)
encr_data = cipher.encrypt(data) # This works!

看起来,使用我的独立公钥可以加密数据。但为什么我不能在 pkcs11 令牌会话的上下文中这么做呢?

然后,我尝试使用 pkcs11 对象解密函数来解密使用上面的 Crypto 模块生成的数据:

decrypted = private.decrypt(encr_data) # It fails!

上述操作失败,出现了pkcs11.exceptions.MechanismInvalid错误。我尝试使用不同的机制,但所有的结果都导致相同的错误。有趣的是——看起来pkcs11对象允许我调用decrypt函数而不会抱怨它不被支持。

还有一件事我应该提到。我检查了我的证书,并在扩展 -> 证书密钥用途下看到:

Critical
Signing
Key Encipherment

我了解了“密钥加密”和“数据加密”的区别,并了解到“密钥加密”用于加密秘密(对称)密钥而不是数据。这可能是我不能为此令牌会话使用“加密”函数的原因吗?
任何反馈都将不胜感激!

每个令牌中的对象都具有一定的属性,这些属性定义了对象的用途。你应该关注的属性是 EncryptDecrypt。你正在尝试加密操作的公钥是否启用了这些属性? - always_a_rookie
@always_a_rookie_to_learn:我检查了“public [Attribute.ENCRYPT]”,它返回True,所以我猜这个对象中启用了它。 - Proto Ukr
2个回答

5

经过一番广泛的研究并考虑了本主题的回复,我发现加密在此令牌会话中不起作用是由于OpenSC API的限制。事实上,Python-pkcs11页面上有一个兼容性表格(以纯文本形式显示),表明OpenSC不支持加密、对称密钥生成、密钥包装和其他功能,但它完全支持签名/验证并部分支持解密。如果我做了更好的搜索,那么将节省我很多时间。

事实上,使用'pkcs11-tool'进行硬令牌测试非常有用,因为它将显示每个PIV插槽支持的功能和机制。在我的情况下,我按以下方式调用它:

pkcs11-tool -p $pin -t

并收到以下报告:

Using slot 0 with a present token (0x0)
C_SeedRandom() and C_GenerateRandom():
  seeding (C_SeedRandom) not supported
  seems to be OK
Digests:
  all 4 digest functions seem to work
  MD5: OK
  SHA-1: OK
  RIPEMD160: OK
Signatures (currently only for RSA)
  testing key 0 (PIV AUTH key) 
  all 4 signature functions seem to work
  testing signature mechanisms:
    RSA-X-509: OK
    RSA-PKCS: OK
    SHA1-RSA-PKCS: OK
    MD5-RSA-PKCS: OK
    RIPEMD160-RSA-PKCS: OK
    SHA256-RSA-PKCS: OK
  testing key 1 (2048 bits, label=SIGN key) with 1 signature mechanism
    RSA-X-509: OK
  testing key 2 (2048 bits, label=KEY MAN key) with 1 signature mechanism -- can't be used to sign/verify, skipping
Verify (currently only for RSA)
  testing key 0 (PIV AUTH key)
    RSA-X-509: OK
    RSA-PKCS: OK
    SHA1-RSA-PKCS: OK
    MD5-RSA-PKCS: OK
    RIPEMD160-RSA-PKCS: OK
  testing key 1 (SIGN key) with 1 mechanism
    RSA-X-509: OK
  testing key 2 (KEY MAN key) with 1 mechanism -- can't be used to sign/verify, skipping
Unwrap: not implemented
Decryption (currently only for RSA)
  testing key 0 (PIV AUTH key) 
    RSA-X-509: OK
    RSA-PKCS: OK
  testing key 1 (SIGN key) 
    RSA-X-509: OK
    RSA-PKCS: OK
  testing key 2 (KEY MAN key) 
    RSA-X-509: OK
    RSA-PKCS: OK
No errors

从这里我们可以看到,所有三个已占用的插槽都支持解密,但只能使用RSA-X-509和RSA-PKCS机制(没有OAEP)。
现在,我正在考虑融合pkcs11-tool和openssl功能来进行数据加密。我还没有完全弄清楚这种流程的所有复杂性,但我正在考虑类似于这样的东西:
  1. (pkcs11-tool)从所需的PIV插槽导出证书
  2. (openssl)创建对称(例如AES)秘钥
  3. (openssl)使用此秘钥加密数据
  4. (openssl)使用步骤1中的证书加密秘钥
  5. 将[对称]加密的数据和[非对称]加密的秘钥发送给收件人
  6. (pkcs11-tool)在安全令牌上解密秘钥
  7. (openssl)使用解密后的秘钥来解密实际数据
看起来我应该能够在Linux shell中使用pkcs11-tool和openssl实用程序或在Python中使用pkcs11和OpenSSL库来实现这样的变通方法。如果我决定以后通过GUI来处理它,后者似乎更可取。这一切看起来都很低级,所以我想知道是否有更简单的方法来加密/解密数据。我知道PGP将加密数据和包装后的秘钥合并到一个单一文件中,因此最终用户只需在其端执行一个命令即可。

当涉及到选择与X.509相关的密钥管理(PKIX)的容器格式时,我们通常使用加密消息语法。PGP主要使用自己的数据结构,并使用相当不同的PKI结构。 - Maarten Bodewes
@Maarten,我了解了CMS,似乎Python有一些库可以让我创建或读取CMS/PKCS#7数据。当我将智能卡加入到方程式中时,它会变得更加复杂,我希望有一个简单的示例可以让我开始学习。目前,我可以使用openssl cms / engine实用程序从shell中完成它。 - Proto Ukr

2
很抱歉,我认为这只是API的一个缺陷。由于使用公钥加密不需要任何安全性保障,因此在Yubikey上实现它没有意义。更快的方法是导出公钥值并在主机上执行加密。
公平地说,如果Yubikey能够在Ubikey PKCS#11库中通过软件实现该功能,那将是很好的。如果你真的想要,那么你可以创建一个新的PKCS#11“包装”库,并在其中通过软件包含缺失的功能。所有其他Yubikey实现的命令都可以转发到原始的Yubikey PKCS#11库。

@Maarten_Bodewes,是的,你说得完全正确。自从我最初发布帖子以来,我进行了大量研究,现在我确信这确实是OpenSC API的缺点。似乎这些缺点不是针对任何特定的硬件令牌(例如Yubikey、Nitrokey、Gemalto等),而是API本身不完整。感谢您的反馈! - Proto Ukr
请注意,OpenSC是针对智能卡构建的。这些智能卡甚至不太可能实现加密功能;通常公钥被嵌入某个证书中并且对智能卡来说是未知的。大多数智能卡不支持X.509解析... - Maarten Bodewes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接