如何使用 PyKCS11 库验证签名数据

Posted

技术标签:

【中文标题】如何使用 PyKCS11 库验证签名数据【英文标题】:How to verify signed data with PyKCS11 Library 【发布时间】:2015-11-20 13:08:22 【问题描述】:

我编写了一个简单的 Python 脚本,该脚本使用我的智能卡 (Rutoken ECP SC)、PKCS#11 库(由我的供应商实现)和 Python 的 PyKCS11 包装器进行数字签名。 我已经使用此卡生成了私钥/公钥对并使用 pkcs11 创建了签名,但我不知道如何验证此签名数据。 问题是我的 pkcs11 库(用 c++ 实现)具有验证方法,但 PyKCS11 包装器没有。现在我不知道如何解决这个问题。如果有人能告诉我如何解决这个问题,我将不胜感激。

这是我的脚本:

import PyKCS11
import getopt
import sys
import platform

red = blue = magenta = normal = ""
if sys.stdout.isatty() and platform.system().lower() != 'windows':
    red = "\x1b[01;31m"
    blue = "\x1b[34m"
    magenta = "\x1b[35m"
    normal = "\x1b[0m"

format_long = magenta + "  %s:" + blue + " %s (%s)" + normal
format_binary = magenta + "  %s:" + blue + " %d bytes" + normal
format_normal = magenta + "  %s:" + blue + " %s" + normal

pkcs11 = PyKCS11.PyKCS11Lib()
lib_path = "/usr/lib/pkcs11-arm/rtpkcs11ecp/librtpkcs11ecp.so"
pkcs11.load(lib_path)
info = pkcs11.getInfo()
print "Library manufacturerID: " + info.manufacturerID

slots = pkcs11.getSlotList()
print "Available Slots:", len(slots)

# As I understand we need only first slot
if len(slots) > 0:
    slot = slots[0]
    slotInfo = pkcs11.getSlotInfo(slot)
    tokenInfo = pkcs11.getTokenInfo(slot)

    flags = PyKCS11.CKF_RW_SESSION
    session = pkcs11.openSession(slot, flags)
    print "Opened session 0x%08X" % session.session.value()
    pin = "12345678"
    session.login(pin)
    objects = session.findObjects()                
    all_attributes = PyKCS11.CKA.keys()             # all keys supported by SC

    print "Defining KEY_GENERATION mechanism"
    mech = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS_KEY_PAIR_GEN, None)


    print "Generating key"
    public_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PUBLIC_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_FALSE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_ENCRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_VERIFY, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_WRAP, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_RSA),
        (PyKCS11.CKA_VERIFY_RECOVER, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_MODULUS_BITS, 2048),
    ]

    private_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PRIVATE_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_DECRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_SIGN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_UNWRAP, PyKCS11.CK_TRUE)
        ]

    (pub, priv) = session.generateKeyPair(public_template, private_template, mech)

    # ==================================================
    # Signing data
    sourceText = "Hello World"
    binaryData = ' '.join(format(ord(x), 'b') for x in sourceText)

    signMechanism = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS, None)
    signedData = session.sign(priv, binaryData, signMechanism)
    print signedData


    #====================================================
    # now we have to verify signedData using the private key



    session.logout()
    session.closeSession()
    print "Close session 0x%08X" % session.session.value()

这是sign方法的输出:

[83L, 29L, 52L, 93L, 228L, 220L, 13L, 187L, 224L, 212L, 112L, 204L, 198L, 91L, 207L, 6L, 215L, 38L, 233L, 194L, 252L, 140L, 106L, 62L, 69L, 94L, 252L, 89L, 194L, 18L, 58L, 240L, 174L, 2L, 26L, 212L, 152L, 134L, 40L, 67L, 163L, 53L, 226L, 74L, 15L, 47L, 200L, 131L, 58L, 199L, 22L, 103L, 145L, 235L, 196L, 117L, 196L, 78L, 160L, 223L, 118L, 0L, 147L, 91L, 9L, 146L, 218L, 142L, 1L, 47L, 192L, 20L, 96L, 230L, 77L, 242L, 124L, 232L, 77L, 130L, 207L, 226L, 165L, 108L, 241L, 198L, 33L, 9L, 79L, 238L, 35L, 53L, 127L, 31L, 118L, 167L, 4L, 84L, 158L, 98L, 171L, 37L, 221L, 208L, 80L, 17L, 142L, 61L, 207L, 204L, 17L, 94L, 38L, 136L, 44L, 161L, 191L, 131L, 237L, 213L, 108L, 175L, 14L, 31L, 61L, 2L, 85L, 6L, 104L, 226L, 201L, 71L, 141L, 243L, 72L, 2L, 142L, 83L, 87L, 140L, 1L, 83L, 26L, 93L, 96L, 96L, 207L, 217L, 222L, 168L, 78L, 221L, 158L, 199L, 213L, 82L, 212L, 45L, 62L, 14L, 22L, 128L, 68L, 76L, 205L, 247L, 124L, 23L, 69L, 123L, 68L, 116L, 239L, 49L, 130L, 207L, 43L, 194L, 9L, 4L, 55L, 35L, 51L, 21L, 233L, 198L, 121L, 212L, 61L, 244L, 117L, 98L, 174L, 173L, 209L, 252L, 218L, 51L, 63L, 217L, 160L, 18L, 45L, 167L, 161L, 79L, 10L, 130L, 80L, 63L, 234L, 48L, 155L, 66L, 84L, 116L, 186L, 42L, 119L, 250L, 177L, 206L, 90L, 117L, 159L, 98L, 165L, 70L, 141L, 39L, 108L, 212L, 33L, 20L, 163L, 181L, 113L, 177L, 201L, 129L, 108L, 182L, 94L, 14L, 200L, 213L, 22L, 29L, 182L, 45L, 16L, 242L, 227L, 242L, 192L, 42L]

【问题讨论】:

(理论上)由于公钥不敏感,您可以在不使用令牌的情况下验证签名(参见例如here)。您需要先提取公钥(通过C_GetAttributeValue 获取CKA_MODULUSCKA_PUBLIC_EXPONENT)。 另外注意:考虑使用一些带有摘要的RSA签名变体(例如CKM_SHA256_RSA_PKCS或任何其他CKM_*_RSA_PKCS之一)。并确保您的签名/验证算法变体匹配。 感谢您的回复,@vlp。我会尝试测试这个想法 @Tequila 如果可行,让 vlp 发布答案(没有理由不这样做)。如果您愿意,您可以随时向其中添加代码。您可能还想阅读this answer of mine,了解有关 PKCS#11 中签名算法的更多信息。 获取(一些)公钥:RSA.new_pub_key(('\x00\x00\x01\x00\xc4\x61\xb0\xb8\x39\xe1\xc0\x99..skipped...\xdc\xfa\x85', '\x00\x00\x00\x03\x01\x00\x01')) 这对我有用(请参阅here 和here)。对于代码的其余部分,我不是那么喜欢python...对不起PS:请考虑使用CKM_SHA1_RSA_PKCS而不是CKM_RSA_PKCS,因为M2Crypto的RSA.verify()似乎需要一个摘要(SHA1是默认的)...祝你好运! PS2:你得到的公钥参数看起来没问题。 【参考方案1】:

此代码适用于我(请注意,公共指数假定为 3 个字节长):

import PyKCS11
import getopt
import sys
import platform
import hashlib
from M2Crypto import RSA

pkcs11 = PyKCS11.PyKCS11Lib()
lib_path = "/opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so"
pkcs11.load(lib_path)
info = pkcs11.getInfo()
slots = pkcs11.getSlotList()
if len(slots) > 0:
    session = pkcs11.openSession(slots[0], PyKCS11.CKF_RW_SESSION)
    session.login("12345678")
    mech = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS_KEY_PAIR_GEN, None)
    public_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PUBLIC_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_FALSE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_ENCRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_VERIFY, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_WRAP, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_RSA),
        (PyKCS11.CKA_VERIFY_RECOVER, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_MODULUS_BITS, 2048),
    ]
    private_template = [
        (PyKCS11.CKA_CLASS, PyKCS11.CKO_PRIVATE_KEY),
        (PyKCS11.CKA_PRIVATE, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_DECRYPT, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_SIGN, PyKCS11.CK_TRUE),
        (PyKCS11.CKA_UNWRAP, PyKCS11.CK_TRUE)
        ]
    (pub, priv) = session.generateKeyPair(public_template, private_template, mech)
    (pubExp,pubModulus) = session.getAttributeValue(pub,[PyKCS11.CKA_PUBLIC_EXPONENT,PyKCS11.CKA_MODULUS], True)
    # ==================================================
    # Signing data
    binaryData = "Hello world"
    # Generate SHA1
    sha1 = hashlib.sha1()
    sha1.update(str(bytearray(binaryData)))
    digest=sha1.digest()
    # Indicate SHA1 is used
    binaryData2='\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'+digest
    signMechanism = PyKCS11.Mechanism(PyKCS11.CKM_RSA_PKCS, None)
    signedData = session.sign(priv, binaryData2, signMechanism)
    session.logout()
    session.closeSession()
    # ==================================================
    # Verify
    pubkey = RSA.new_pub_key(('\x00\x00\x00\x03' + str(bytearray(pubExp)), '\x00\x00\x01\x01\x00'+str(bytearray(pubModulus))))
    result=pubkey.verify(str(bytearray(digest)), str(bytearray(signedData)), 'sha1')
    print "VERIFY:" + str(result)

我不喜欢python,所以请把它当作概念证明而不是解决方案。对于有趣的部分:

由于您的 PKCS#11 驱动程序不支持带有哈希的 RSA 签名,因此需要计算哈希并手动构建 DigestInfo ASN.1 part(结果在 binaryData2 变量中)

由于RSA.new_pub_key() 接受一个以openssl 格式为BN_mpi2bn 的元组(它在内部使用),因此需要在模数前面加上一个\x00,以确保它被解释为正数(@987654329 @部分)

给定函数verify() 使用openssl 的RSA_verify 将签名数据的摘要(而不是数据本身)作为参数,它需要服从并给出摘要(从签名中重复使用)生成部分,如果你打算有一个单独的验证功能,你必须生成一个新的)


注意:例如SHA256,您需要使用适当的 digestInfo 魔术 ASN.1 字符串前缀(有关可用值,请参阅 here)+ 来自 hashlib 的适当摘要对象 + 正确的第三次验证调用参数。

祝你好运!

【讨论】:

哇,它有效!你是怎么做到的?)这对今天来说是个好消息!非常感谢你!我差点在 c++ 上写了这个签名机制,但在你的帮助下,现在没有必要)) 你能推荐一些关于这些事情的书籍或资源吗?如果这些文献有俄语翻译,那就太好了(如果我没记错的话,你用俄语说)。非常感谢! @Tequila 我很高兴它有效!关于资源......谷歌?祝你的项目好运!

以上是关于如何使用 PyKCS11 库验证签名数据的主要内容,如果未能解决你的问题,请参考以下文章

如何使用自签名证书创建密钥库和信任库?

如何使用 npm Jose 创建签名的 JWT,然后验证此令牌?

如何在 Java 中使用令牌和公钥验证 JWT 签名

如何使用使用 sha1ecdsa 的公钥验证数据是不是符合签名?

如何使用 java 验证由 keycloak 签名的 JWT

使用 JWT PHP 库导致“签名验证失败”的网站的 Google 登录