How do you verify an RSA SHA1 signature in Python?

24,187

Solution 1

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.

Solution 2

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

Solution 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.

Solution 4

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

Solution 5

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.

Share:
24,187
Andrew B.
Author by

Andrew B.

Updated on November 13, 2020

Comments

  • Andrew B.
    Andrew B. over 3 years

    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.

  • Andrew B.
    Andrew B. over 15 years
    The only way I can find to create an RSAobj requires a tuple as input.
  • Rasmus Faber
    Rasmus Faber over 15 years
    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.
  • Andrew B.
    Andrew B. over 15 years
    Thanks a lot. I spent quite some some time chasing standards docs but never found all the pieces. The "fun info" is definitely appreciated.
  • Andrew B.
    Andrew B. over 15 years
    Thanks joe. If I could mark two as the answer I'd mark yours too.
  • Andrew B.
    Andrew B. over 15 years
    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.
  • joeforker
    joeforker over 15 years
    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/tests
  • Andrew B.
    Andrew B. over 15 years
    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.
  • joeforker
    joeforker over 15 years
    @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/…
  • Sylvain
    Sylvain about 13 years
    @Andrew: did you find out what was the difference between the PHP and Python version? I do have the same exact problem as you.
  • Andrew B.
    Andrew B. almost 13 years
    @Sylvain No, I actually don't think I ever did. If someone posts working code I'll mark it as correct.
  • Diego Lins de Freitas
    Diego Lins de Freitas over 9 years
    what does it means self.populateSignStr?