Python中RSA PKCS #1 v1.5签名与openssl相比多了一个字节

1 投票
2 回答
4484 浏览
提问于 2025-04-17 10:28

我想用Python按照OAuth 1的RFC标准中提到的RSA-SHA1签名方法来签署一条消息,具体可以参考这个链接:https://www.rfc-editor.org/rfc/rfc5849#section-3.4.3。我想通过和openssl的结果进行对比,来确认我是否走在正确的道路上。

不过,我突然发现自己多出了一个字节,而我对加密技术的了解有限,所以需要一些帮助。我查看了crypto的源代码和openssl的手册,发现输出结果非常相似,所以我觉得至少我在使用正确的算法。

但是,当我使用openssl的rsautl时,结果却完全不一样……

$ openssl genrsa -out private.pem 1024

$ cat message
Lorem ipsum

$ cat sign.py
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
key = RSA.importKey(open("private.pem").read())
message = open("message").read()[:-1] # skip last newline
h = SHA.new(message)
p = PKCS1_v1_5.new(key)
signature = p.sign(h)
signature_trim = p.sign(h)[:-1] # will give same output as openssl dgst -sign
print signature    # remove print when not using hexdump

这是Python的输出结果

$ python sign.py | hexdump
0000000 1346 38af 89a8 d203 ee26 0cfa a4bc 3a6c
0000010 44fd a436 2c50 03ba 7c84 333a 910a 843e
0000020 f71b 5731 1d2a 8895 9f5c 86b1 1838 7de9
0000030 5c13 d7e5 a019 6ad1 e5a5 d4d5 bd6f 0032
0000040 f320 c5ad fc41 da2c a9c3 2d9a cdce f6d6
0000050 4ef4 6dbd 1ba2 edc1 648e 184a 2e6c e746
0000060 fd92 ba61 b4da f607 d7a4 fbef 8230 378d
0000070 a143 b444 c711 7121 6e08 9d88 bb05 0d25
0000080 000a                                   
0000081

这是使用openssl dgst签名的输出(不确定这是否真的是rsa pkcs #1 v1.5)

$ echo -n $(cat message) | openssl dgst -sign private.pem | hexdump
0000000 1346 38af 89a8 d203 ee26 0cfa a4bc 3a6c
0000010 44fd a436 2c50 03ba 7c84 333a 910a 843e
0000020 f71b 5731 1d2a 8895 9f5c 86b1 1838 7de9
0000030 5c13 d7e5 a019 6ad1 e5a5 d4d5 bd6f 0032
0000040 f320 c5ad fc41 da2c a9c3 2d9a cdce f6d6
0000050 4ef4 6dbd 1ba2 edc1 648e 184a 2e6c e746
0000060 fd92 ba61 b4da f607 d7a4 fbef 8230 378d
0000070 a143 b444 c711 7121 6e08 9d88 bb05 0d25
0000080

在sign.py中,我去掉了签名的打印,并用公钥验证签名。因为如果去掉签名会导致验证失败,所以我觉得这应该是不应该做的,但我之前也有过错误的判断。

pubkey = key.publickey()
pp = PKCS1_v1_5.new(pubkey)
print pp.verify(h, signature)         # True
print pp.verify(h, signature_trim)    # False

这是openssl rsautl的输出(我也不确定这是否是pkcs #1 v1.5)

$ echo -n $(cat message) | openssl dgst -sha1 | openssl rsautl -sign -inkey private.pem  | hexdump
0000000 14a4 f02c 527f 26f9 29f6 281c 3185 4a1a
0000010 def8 052b b620 cca2 38d9 a389 0b44 112a
0000020 283c ebff 4228 6f77 7a65 9d53 4b98 a073
0000030 bbd9 1aca 3447 a917 d7c3 0968 63c4 6806
0000040 6112 6f36 2d38 a770 5afa a8e0 adf3 4bef
0000050 120c cc10 5194 75ad bdda 91e6 fd79 8f4c
0000060 b864 efb8 cc88 a4da e977 b488 6241 15fb
0000070 e105 1d11 8627 75bd 345b 34da 538f a8db
0000080

显然我做错了什么,现在我想知道错得有多离谱。除了base64编码和“Lorem ipsum”并不是一个有效的签名基础字符串……

...我需要修改什么才能使这个成为一个有效的RSA-SHA1签名呢?

2 个回答

0

找到了答案。其实什么都没有。

我使用以下步骤创建了一个证书和密钥:

$ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -sha1 -subj \ 
  '/C=US/ST=CA/L=Mountain View/CN=www.example.com' -keyout myrsakey.pem \
  -out myrsacert.pem
  1. 在Google Auth Playground上注册了一个域名 http://googlecodesamples.com/oauth_playground/
  2. 验证了我的域名并上传了证书文件
  3. 使用Playground来制作请求
  4. 确认Playground的输出和我代码的输出一致 =)

RSA-SHA1 签名方法

def sign_rsa(method, url, params, private_rsa):
   """Sign a request using RSASSA-PKCS #1 v1.5.

   Per `section 3.4.3`_ of the spec.

   .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3

   """
   from Crypto.PublicKey import RSA
   from Crypto.Signature import PKCS1_v1_5
   from Crypto.Hash import SHA
   key = RSA.importKey(private_rsa)
   message = prepare_base_string(method, url, params)
   h = SHA.new(message)    
   p = PKCS1_v1_5.new(key)
   return escape(binascii.b2a_base64(p.sign(h))[:-1])

RSA-SHA1 验证

def verify_rsa(method, url, params, public_rsa, signature):
   """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature.

   Per `section 3.4.3`_ of the spec.

   .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3

   """
   from Crypto.PublicKey import RSA
   from Crypto.Signature import PKCS1_v1_5
   from Crypto.Hash import SHA
   key = RSA.importKey(public_rsa)
   message = prepare_base_string(method, url, params)
   h = SHA.new(message)
   p = PKCS1_v1_5.new(key)
   signature = binascii.a2b_base64(urllib.unquote(signature))
   return p.verify(h, signature)

好奇的人可以在这里找到这两者,以及prepare_base_string和escape: https://github.com/ib-lundgren/oauthlib/blob/master/oauthlib/oauth.py

对十六进制转储还是不太明白,不过如果我搞明白了会更新的。

4

在Python中,print这个命令总是会在输出的最后加上一个换行符,这就是你看到的那个多出来的字节。

据我所知,没有简单的方法可以避免这个问题,使用print signature也不行。

一个替代的方法是使用更底层的命令sys.stdout.write(signature)

撰写回答