如何在Python中验证RSA SHA1签名?

30

我有一个字符串、一个签名和一个公钥,我想验证这个字符串上的签名。该密钥看起来像这样:

-----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);

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

8个回答

30

使用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

1
PHP的openssl_verify调用与上述Python代码完全相同的函数。 - joeforker
谢谢Joe。如果我能标记两个答案,我也会把你的标记上。 - Andrew B.
1
啊,我已经试了好一阵子了,但是我一直从verify_final得到-1。我使用PHP验证的值是正确的。 - Andrew B.
@Andrew:你找出了PHP和Python版本之间的区别吗?我也遇到了完全相同的问题。 - Sylvain
@Sylvain,实际上我不认为我曾经这样做过。如果有人发布可行的代码,我会将其标记为正确。 - Andrew B.
显示剩余3条评论

24
在这些标记之间的数据是一个PKCS#8 PublicKeyInfo的ASN.1 DER编码的base64编码,其中包含一个PKCS#1 RSAPublicKey。
这是很多标准,最好使用加密库进行解码(如由joeforker建议的M2Crypto)。将以下内容视为有关格式的一些有趣信息:
如果您愿意,可以这样解码:
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。

1
非常感谢。我花了很多时间追寻标准文档,但从未找到所有的要点。这些“有趣的信息”确实让人感激。 - Andrew B.

2
公钥包含模数(非常长的数字,可以是1024位、2058位或4096位)和公钥指数(较小的数字,通常等于某个幂的两倍加一)。在进行任何操作之前,您需要找出如何将公钥分成这两个组件。
我不太了解pycrypto,但要验证签名,请获取字符串的哈希值。现在,我们必须解密签名。阅读模指数;解密签名的公式为message^public exponent % modulus。最后一步是检查您制作的哈希值和您获得的解密签名是否相同。

1
更多关于DER解码的内容。
DER编码始终遵循TLV三元组格式:(标签,长度,值)
- 标签指定值的类型(即数据结构) - 长度指定该值字段占用的字节数 - 值是实际值,可以是另一个三元组
标签基本上告诉如何解释值字段中的字节数据。ANS.1确实有一种类型系统,例如0x02表示整数,0x30表示序列(一个包含一个或多个其他类型实例的有序集合)。
长度表示具有特殊的逻辑:
- 如果长度<127,则L字段仅使用一个字节,并直接编码为长度数字值。 - 如果长度> 127,则在L字段的第一个字节中,第一位必须为1,其余7位表示用于指定值字段长度的后续字节数。值是实际值本身的字节。
例如,假设我想编码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在回复中所说的。

1

我认为ezPyCrypto可能会让这个过程更容易。key类的高级方法包括以下两种方法,我希望能解决你的问题:

  • verifyString - 验证字符串是否与签名匹配
  • importKey - 导入公钥(以及可能的私钥)

Rasmus在评论中指出,verifyString硬编码为使用MD5,因此除非Andrew深入研究其代码,否则ezPyCryto无法帮助他。我倾向于joeforker的答案:考虑使用M2Crypto


2
快速浏览一下,看起来 verifyString() 是硬编码使用 MD5,而 importKey 使用自制的非标准格式。所以不幸的是,我认为他将无法使用这个。 - Rasmus Faber

0

使用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'

就是这样了


0
也许这不是你想要的答案,但如果你只需要将密钥转换为位,那么它看起来是Base64编码的。查看标准Python库中的codecs模块(我想是这个)。

我所找到的唯一创建RSAobj的方法需要一个元组作为输入。 - Andrew B.

-1

我尝试了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"

1
self.populateSignStr是什么意思? - Diego Lins de Freitas

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