How do you verify an RSA SHA1 signature in Python?
Asked Answered
B

8

30

I've got a string, a signature, and a public key, and I want to verify the signature on the string. The key looks like this:

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

I've been reading the pycrypto docs for a while, but I can't figure out how to make an RSAobj with this kind of key. If you know PHP, I'm trying to do the following:

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

Also, if I'm confused about any terminology, please let me know.

Burnejones answered 13/2, 2009 at 1:50 Comment(0)
P
25

The data between the markers is the base64 encoding of the ASN.1 DER-encoding of a PKCS#8 PublicKeyInfo containing an PKCS#1 RSAPublicKey.

That is a lot of standards, and you will be best served with using a crypto-library to decode it (such as M2Crypto as suggested by joeforker). Treat the following as some fun info about the format:

If you want to, you can decode it like this:

Base64-decode the string:

30819f300d06092a864886f70d010101050003818d0030818902818100df1b822e14eda1fcb74336
6a27c06370e6cad69d4116ce806b3d117534cf0baa938c0f8e4500fb59d4d98fb471a8d01012d54b
32244197c7434f27c1b0d73fa1b8bae55e70155f907879ce9c25f28a9a92ff97de1684fdaff05dce
196ae76845f598b328c5ed76e0f71f6a6b7448f08691e6a556f5f0d773cb20d13f629b6391020301
0001

This is the DER-encoding of:

   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
            :         }
            :       }
            :   }

For a 1024 bit RSA key, you can treat "30819f300d06092a864886f70d010101050003818d00308189028181" as a constant header, followed by a 00-byte, followed by the 128 bytes of the RSA modulus. After that 95% of the time you will get 0203010001, which signifies a RSA public exponent of 0x10001 = 65537.

You can use those two values as n and e in a tuple to construct a RSAobj.

Pennate answered 13/2, 2009 at 8:54 Comment(1)
Thanks a lot. I spent quite some some time chasing standards docs but never found all the pieces. The "fun info" is definitely appreciated.Burnejones
W
31

Use M2Crypto. Here's how to verify for RSA and any other algorithm supported by 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
Weaken answered 13/2, 2009 at 15:59 Comment(8)
PHP's openssl_verify calls exactly the same functions as the above Python code.Weaken
Thanks joe. If I could mark two as the answer I'd mark yours too.Burnejones
Arg, I've been trying to get this to work for a while, but I keep just getting -1 from the verify_final. The values I have verify correctly using PHP.Burnejones
If you corrupt your message do you get a different return code from verify_final? Does your message have any special characters that might be encoded differently in Python? Are you using the latest M2Crypto? See also svn.osafoundation.org/m2crypto/trunk/testsWeaken
No, no, and "whatever apt gets". I'll try those tests before checking for a newer version. Here are the PHP and Python versions, in case I'm doing anything obviously wrong: pastebin.com/f58e6809a pastebin.com/f137244e1 . Thanks again.Burnejones
@Andrew So you know which files to look for, the example above mostly uses the EVP API (test_evp.py). You could also try making RSA-specific "verify" calls (test_rsa.py). If you get desperate, compare with cvs.php.net/viewvc.cgi/php-src/ext/openssl/…Weaken
@Andrew: did you find out what was the difference between the PHP and Python version? I do have the same exact problem as you.Enzymolysis
@Enzymolysis No, I actually don't think I ever did. If someone posts working code I'll mark it as correct.Burnejones
P
25

The data between the markers is the base64 encoding of the ASN.1 DER-encoding of a PKCS#8 PublicKeyInfo containing an PKCS#1 RSAPublicKey.

That is a lot of standards, and you will be best served with using a crypto-library to decode it (such as M2Crypto as suggested by joeforker). Treat the following as some fun info about the format:

If you want to, you can decode it like this:

Base64-decode the string:

30819f300d06092a864886f70d010101050003818d0030818902818100df1b822e14eda1fcb74336
6a27c06370e6cad69d4116ce806b3d117534cf0baa938c0f8e4500fb59d4d98fb471a8d01012d54b
32244197c7434f27c1b0d73fa1b8bae55e70155f907879ce9c25f28a9a92ff97de1684fdaff05dce
196ae76845f598b328c5ed76e0f71f6a6b7448f08691e6a556f5f0d773cb20d13f629b6391020301
0001

This is the DER-encoding of:

   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
            :         }
            :       }
            :   }

For a 1024 bit RSA key, you can treat "30819f300d06092a864886f70d010101050003818d00308189028181" as a constant header, followed by a 00-byte, followed by the 128 bytes of the RSA modulus. After that 95% of the time you will get 0203010001, which signifies a RSA public exponent of 0x10001 = 65537.

You can use those two values as n and e in a tuple to construct a RSAobj.

Pennate answered 13/2, 2009 at 8:54 Comment(1)
Thanks a lot. I spent quite some some time chasing standards docs but never found all the pieces. The "fun info" is definitely appreciated.Burnejones
G
3

A public key contains both a modulus(very long number, can be 1024bit, 2058bit, 4096bit) and a public key exponent(much smaller number, usually equals one more than a two to some power). You need to find out how to split up that public key into the two components before you can do anything with it.

I don't know much about pycrypto but to verify a signature, take the hash of the string. Now we must decrypt the signature. Read up on modular exponentiation; the formula to decrypt a signature is message^public exponent % modulus. The last step is to check if the hash you made and the decrypted signature you got are the same.

Gora answered 13/2, 2009 at 3:44 Comment(0)
L
1

I think ezPyCrypto might make this a little easier. The high-level methods of the key class includes these two methods which I hope will solve your problem:

  • verifyString - verify a string against a signature
  • importKey - import public key (and possibly private key too)

Rasmus points out in the comments that verifyString is hard-coded to use MD5, in which case ezPyCryto can't help Andrew unless he wades into its code. I defer to joeforker's answer: consider M2Crypto.

Latishalatitude answered 13/2, 2009 at 9:1 Comment(1)
Taking a quick look at this, it seems like verifyString() is hardcoded to use MD5 and that importKey uses a homemade, non-standard format. So unfortunately, I don't think he will be able to use this.Pennate
A
1

More on the DER decoding.

DER encoding always follows a TLV triplet format: (Tag, Length, Value)

  • Tag specifies the type (i.e. data structure) of the value
  • Length specifies the number of byte this value field occupies
  • Value is the actual value which could be another triplet

Tag basically tells how to interpret the bytes data in the Value field. ANS.1 does have a type system, e.g. 0x02 means integer, 0x30 means sequence (an ordered collection of one or more other type instances)

Length presentation has a special logic:

  • If the length < 127, the L field only uses one byte and coded as the length number value directly
  • If the length > 127, then in the first byte of L field, the first bit must be 1, and the rest 7 bits represents the number of following bytes used to specifies the length of the Value field. Value, the actually bytes of the value itself.

For example, say I want to encode a number of 256 bytes long, then it would be like this

02  82  01  00  1F  2F  3F  4F  …   DE  AD  BE  EF
  • Tag, 0x02 means it's a number
  • Length, 0x82, bit presentation of it is 1000 0010, meaning the following two bytes specifies the actually length of the value, which his 0x0100 meaning the value field is 256 bytes long
  • Value, from 1F to EF, the actual 256 bytes.

Now looking at your example

30819f300d06092a864886f70d010101050003818d0030818902818100df1b822e14eda1fcb74336
6a27c06370e6cad69d4116ce806b3d117534cf0baa938c0f8e4500fb59d4d98fb471a8d01012d54b
32244197c7434f27c1b0d73fa1b8bae55e70155f907879ce9c25f28a9a92ff97de1684fdaff05dce
196ae76845f598b328c5ed76e0f71f6a6b7448f08691e6a556f5f0d773cb20d13f629b6391020301
0001

It interprets as just what Rasmus Faber put in his reply

Advised answered 30/7, 2014 at 8:34 Comment(0)
F
0

Maybe this isn't the answer you're looking for, but if all you need is to turn the key into bits, it looks like it's Base64 encoded. Look at the codecs module (I think) in the standard Python library.

Foredeck answered 13/2, 2009 at 2:13 Comment(1)
The only way I can find to create an RSAobj requires a tuple as input.Burnejones
C
0

Using M2Crypto, the above answers does not work. Here is a tested example.

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'

And that's about it

Chide answered 27/3, 2015 at 13:3 Comment(0)
Z
-1

I try the code given by joeforker but it does not work. Here is my example code and it works fine.

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"
Zelazny answered 8/8, 2014 at 3:59 Comment(1)
what does it means self.populateSignStr?Disbelieve

© 2022 - 2024 — McMap. All rights reserved.