【问题标题】:How do you verify an RSA SHA1 signature in Python?如何在 Python 中验证 RSA SHA1 签名?
【发布时间】:2009-02-13 01:50:48
【问题描述】:

我有一个字符串、一个签名和一个公钥,我想验证字符串上的签名。密钥如下所示:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfG4IuFO2h/LdDNmonwGNw5srW
nUEWzoBrPRF1NM8LqpOMD45FAPtZ1NmPtHGo0BAS1UsyJEGXx0NPJ8Gw1z+huLrl
XnAVX5B4ec6cJfKKmpL/l94WhP2v8F3OGWrnaEX1mLMoxe124Pcfamt0SPCGkeal
VvXw13PLINE/YptjkQIDAQAB
-----END PUBLIC KEY-----

我已经阅读 pycrypto 文档有一段时间了,但我不知道如何使用这种密钥制作 RSAobj。如果您了解 PHP,我正在尝试执行以下操作:

openssl_verify($data, $signature, $public_key, OPENSSL_ALGO_SHA1);

另外,如果我对任何术语感到困惑,请告诉我。

【问题讨论】:

    标签: python cryptography rsa sha1 signature


    【解决方案1】:

    使用M2Crypto。以下是验证 RSA 和 OpenSSL 支持的任何其他算法的方法:

    pem = """-----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfG4IuFO2h/LdDNmonwGNw5srW
    nUEWzoBrPRF1NM8LqpOMD45FAPtZ1NmPtHGo0BAS1UsyJEGXx0NPJ8Gw1z+huLrl
    XnAVX5B4ec6cJfKKmpL/l94WhP2v8F3OGWrnaEX1mLMoxe124Pcfamt0SPCGkeal
    VvXw13PLINE/YptjkQIDAQAB
    -----END PUBLIC KEY-----""" # your example key
    
    from M2Crypto import BIO, RSA, EVP
    bio = BIO.MemoryBuffer(pem)
    rsa = RSA.load_pub_key_bio(bio)
    pubkey = EVP.PKey()
    pubkey.assign_rsa(rsa)
    
    # if you need a different digest than the default 'sha1':
    pubkey.reset_context(md='sha1')
    pubkey.verify_init()
    pubkey.verify_update('test  message')
    assert pubkey.verify_final(signature) == 1
    

    【讨论】:

    • PHP 的 openssl_verify 调用的函数与上述 Python 代码完全相同。
    • 谢谢乔。如果我可以将两个标记为答案,我也会标记你的。
    • Arg,我一直试图让它工作一段时间,但我一直从 verify_final 中得到-1。我使用 PHP 正确验证的值。
    • 如果您损坏了您的消息,您是否会从 verify_final 获得不同的返回码?您的消息是否有任何特殊字符可能在 Python 中以不同方式编码?您使用的是最新的 M2Crypto 吗?另见svn.osafoundation.org/m2crypto/trunk/tests
    • 不,不,以及“无论 apt 得到什么”。在检查更新版本之前,我会尝试这些测试。以下是 PHP 和 Python 版本,以防我做任何明显错误的事情:pastebin.com/f58e6809apastebin.com/f137244e1。再次感谢。
    【解决方案2】:

    标记之间的数据是包含 PKCS#1 RSAPublicKey 的 PKCS#8 PublicKeyInfo 的 ASN.1 DER 编码的 base64 编码。

    这是很多标准,最好使用加密库对其进行解码(例如 M2Crypto 为 suggested by joeforker)。将以下内容视为有关格式的一些有趣信息:

    如果你愿意,你可以这样解码:

    Base64 解码字符串:

    30819f300d06092a864886f70d010101050003818d0030818902818100df1b822e14eda1fcb74336
    6a27c06370e6cad69d4116ce806b3d117534cf0baa938c0f8e4500fb59d4d98fb471a8d01012d54b
    32244197c7434f27c1b0d73fa1b8bae55e70155f907879ce9c25f28a9a92ff97de1684fdaff05dce
    196ae76845f598b328c5ed76e0f71f6a6b7448f08691e6a556f5f0d773cb20d13f629b6391020301
    0001
    

    这是 DER 编码:

       0 30  159: SEQUENCE {
       3 30   13:   SEQUENCE {
       5 06    9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
      16 05    0:     NULL
                :     }
      18 03  141:   BIT STRING 0 unused bits, encapsulates {
      22 30  137:       SEQUENCE {
      25 02  129:         INTEGER
                :           00 DF 1B 82 2E 14 ED A1 FC B7 43 36 6A 27 C0 63
                :           70 E6 CA D6 9D 41 16 CE 80 6B 3D 11 75 34 CF 0B
                :           AA 93 8C 0F 8E 45 00 FB 59 D4 D9 8F B4 71 A8 D0
                :           10 12 D5 4B 32 24 41 97 C7 43 4F 27 C1 B0 D7 3F
                :           A1 B8 BA E5 5E 70 15 5F 90 78 79 CE 9C 25 F2 8A
                :           9A 92 FF 97 DE 16 84 FD AF F0 5D CE 19 6A E7 68
                :           45 F5 98 B3 28 C5 ED 76 E0 F7 1F 6A 6B 74 48 F0
                :           86 91 E6 A5 56 F5 F0 D7 73 CB 20 D1 3F 62 9B 63
                :           91
     157 02    3:         INTEGER 65537
                :         }
                :       }
                :   }
    

    对于 1024 位 RSA 密钥,您可以将 "30819f300d06092a864886f70d010101050003818d00308189028181" 视为常量标头,后跟 00 字节,然后是 RSA 模数的 128 字节。在那之后 95% 的时间你会得到0203010001,这表示 RSA 公共指数 0x10001 = 65537。

    您可以在一个元组中使用这两个值作为ne 来构造一个RSAobj。

    【讨论】:

    • 非常感谢。我花了相当长的时间寻找标准文档,但从未找到所有的部分。 “有趣的信息”绝对值得赞赏。
    【解决方案3】:

    公钥包含一个模数(很长的数字,可以是 1024 位、2058 位、4096 位)和一个公钥指数(小得多的数字,通常等于 1 比 2 的某个幂)。您需要了解如何将该公钥拆分为两个组件,然后才能对其进行任何操作。

    我对 pycrypto 了解不多,但要验证签名,请获取字符串的哈希值。现在我们必须解密签名。阅读modular exponentiation;解密签名的公式是message^public exponent % modulus。最后一步是检查你做的哈希和你得到的解密签名是否相同。

    【讨论】:

      【解决方案4】:

      我认为ezPyCrypto 可能会使这更容易一些。 key 类的高级方法包括这两种方法,希望能解决您的问题:

      Rasmus 在 cmets 中指出 verifyString 被硬编码为使用 MD5,在这种情况下 ezPyCryto 无法帮助 Andrew,除非他涉足其代码。我遵从joeforker's answer:考虑M2Crypto

      【讨论】:

      • 快速浏览一下,verifyString() 似乎被硬编码为使用 MD5,并且 importKey 使用自制的非标准格式。所以不幸的是,我认为他不能使用这个。
      【解决方案5】:

      有关 DER 解码的更多信息。

      DER 编码始终遵循 TLV 三元组格式:(标记、长度、值)

      • 标签指定值的类型(即数据结构)
      • 长度指定此值字段占用的字节数
      • Value 是实际值,可能是另一个三元组

      标签基本上告诉如何解释值字段中的字节数据。 ANS.1 确实有一个类型系统,例如0x02 表示整数,0x30 表示序列(一个或多个其他类型实例的有序集合)

      长度表示有一个特殊的逻辑:

      • 如果长度
      • 如果长度> 127,则在L字段的第一个字节中,第一个位 必须为1,其余7位表示后面的个数 用于指定 Value 字段长度的字节。价值, 实际上是值本身的字节数。

      比如说我要编码一个256字节长的数,那么会是这样的

      02  82  01  00  1F  2F  3F  4F  …   DE  AD  BE  EF
      
      • 标签,0x02表示是数字
      • 长度,0x82,位表示为1000 0010,意思是 后面的两个字节指定值的实际长度,即 他的 0x0100 表示值字段是 256 字节长
      • 值,从 1F 到 EF,实际 256 字节。

      现在看看你的例子

      30819f300d06092a864886f70d010101050003818d0030818902818100df1b822e14eda1fcb74336
      6a27c06370e6cad69d4116ce806b3d117534cf0baa938c0f8e4500fb59d4d98fb471a8d01012d54b
      32244197c7434f27c1b0d73fa1b8bae55e70155f907879ce9c25f28a9a92ff97de1684fdaff05dce
      196ae76845f598b328c5ed76e0f71f6a6b7448f08691e6a556f5f0d773cb20d13f629b6391020301
      0001
      

      它解释为 Rasmus Faber 在他的回复中所说的

      【讨论】:

        【解决方案6】:

        也许这不是您要寻找的答案,但如果您只需要将密钥转换为位,它看起来就像是 Base64 编码的。查看标准 Python 库中的 codecs 模块(我认为)。

        【讨论】:

        • 我能找到的创建 RSAobj 的唯一方法需要一个元组作为输入。
        【解决方案7】:

        使用 M2Crypto,上述答案不起作用。这是一个经过测试的示例。

        import base64
        import hashlib
        import M2Crypto as m2
        
        # detach the signature from the message if it's required in it (useful for url encoded data)
        message_without_sign = message.split("&SIGN=")[0]
        # decode base64 the signature
        binary_signature = base64.b64decode(signature)
        # create a pubkey object with the public key stored in a separate file
        pubkey = m2.RSA.load_pub_key(os.path.join(os.path.dirname(__file__), 'pubkey.pem'))
        # verify the key
        assert pubkey.check_key(), 'Key Verification Failed'
        # digest the message
        sha1_hash = hashlib.sha1(message_without_sign).digest()
        # and verify the signature
        assert pubkey.verify(data=sha1_hash, signature=binary_signature), 'Certificate Verification Failed'
        

        就是这样

        【讨论】:

          【解决方案8】:

          我尝试了 joeforker 给出的代码,但它不起作用。 这是我的示例代码,它工作正常。

          from Crypto.Signature import PKCS1_v1_5
          from Crypto.PublicKey import RSA 
          from Crypto.Hash import SHA
          
          pem = """-----BEGIN PUBLIC KEY-----
          MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfG4IuFO2h/LdDNmonwGNw5srW
          nUEWzoBrPRF1NM8LqpOMD45FAPtZ1NmPtHGo0BAS1UsyJEGXx0NPJ8Gw1z+huLrl
          XnAVX5B4ec6cJfKKmpL/l94WhP2v8F3OGWrnaEX1mLMoxe124Pcfamt0SPCGkeal
          VvXw13PLINE/YptjkQIDAQAB
          -----END PUBLIC KEY-----""" # your example key
          
          key = RSA.importKey(pem)
          h = SHA.new(self.populateSignStr(params))
          verifier = PKCS1_v1_5.new(key)
          if verifier.verify(h, signature):
            print "verified"
          else:
            print "not verified"
          

          【讨论】:

          • self.populateSignStr 是什么意思?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-07-30
          • 2022-01-16
          • 1970-01-01
          • 2018-08-11
          • 2013-09-12
          • 1970-01-01
          相关资源
          最近更新 更多