RSA sign a string with private key in python
Asked Answered
C

1

7

I am communicating with our clients server. For an api I need to sign a string with my private key. They have the following condition to follow

  1. User SHA 256 algorithm to calculate the hash of the string
  2. Use the private key and RSA (PKCS1_PADDING) algorithm to sign the Hash Value.
  3. Base64 encode the encrypted Hash Value

and I am doing following

from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
import base64


pkey = RSA.importKey(keystring)

message = "Hello world"

h = SHA256.new(message.encode())
signature = PKCS1_v1_5.new(pkey).sign(h)
result = base64.b64encode(signature).decode()

Here I am getting a string as result. But on the server side my signature is not matching.

Is there anything I am going wrong with ?? Can anyone help me on this ?

Camarata answered 2/1, 2020 at 11:28 Comment(10)
Is there an example implementation of the API you could check your code against?Despumate
Also, if the documentation tells you to encrypt the hash value, you should probably do that instead of signing? pycryptodome.readthedocs.io/en/latest/src/…Despumate
Also, are you sure you're encoding the right value to begin with? You should probably call .hexdigest() or .digest() on h.Despumate
@Despumate I do not have example implementation I just have document on this. I am following pycryptodome.readthedocs.io/en/latest/src/signature/… . If I do .hexdigest() or .digest() I get error like 'bytes' object has no attribute 'oid'Camarata
@Despumate Its not encryption it should be signed. That I confirmed from our clientCamarata
encode and decode are for unicode/bytes. I don't see any mention of doing that in the documentation link you gave.Fabric
It seems like RSA signing is well-documented but you don't seem to following the documentation. Can I ask why not? You would use padding.PKCSv15.Phenylketonuria
@JamesReinstateMonicaPolk I tried cryptography too. Above method you mentioned but I get the same result as of my above example. My results are on bits like `b"L\xb1\xcbU\xa4FG\xd2\xa8\xac\xb4m\xd5(p\xa2\xd1}\xcc0\xd9\xb5\x90\xc7T\x9d]U\xc6\xf6\xe5*\xffz\xfd\xf6` . I base64 encode it.Camarata
...I base64 encode it. And ...? According to step 3 you're suppose to base64 encode it.Phenylketonuria
@JamesReinstateMonicaPolk Yes.. Using both libraries I get same result on encoding the bytes. I dont know why the Java code gives different result on the serverCamarata
P
2

I came back to this question recently and noticed it was never resolved. I don't know what was going wrong with the OPs setup but the following code worked for me.

First, the python code that generates the signature of "Hello world":

from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.Hash import SHA256
from Cryptodome.PublicKey import RSA
import base64


def sign(message: str, private_key_str: str) -> str:
    priv_key = RSA.importKey(private_key_str)
    h = SHA256.new(message.encode('utf-8'))
    signature = PKCS1_v1_5.new(priv_key).sign(h)
    result = base64.b64encode(signature).decode()
    return result

And now the Java code that verifies it:

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

...
...

    public static boolean verify(String message, String b64Sig, byte[] pubkey_spki) throws GeneralSecurityException {
        var pubKey = (PublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(pubkey_spki));
        var verifier = Signature.getInstance("SHA256withRSA");
        verifier.initVerify(pubKey);
        verifier.update(message.getBytes(StandardCharsets.UTF_8));
        return verifier.verify(Base64.getDecoder().decode(b64Sig));
    }

Perhaps the trickiest part of this is specifying the correct padding scheme in each language/library. These signatures use the scheme identified as RSASSA-PKCS1-v1_5 in the PKCS#1 RFC 8017. On the python side this is accomplished by providing the SHA256 hash object to the PKCS1_v1_5 signature object. In Java it is perhaps a little more straightforward in that you ask for Signature object that implements the RSA algorithm with SHA256 as the hash function, but still have to know that this is RSASSA-PKCS1-v1_5 and not some other possibility in RFC 8017.

I think if one is not already something of an expert then understanding that these magic choices in python and Java produce compatible code is going to be difficult.

Phenylketonuria answered 6/12, 2022 at 2:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.