简体   繁体   中英

Python RSA sign a string with NONEWithRSA

I am working on a project in which client side code is in Java and they are using NONEWithRSA algorithm to sign and verify a string.

I am using PyCryptodome to generate sign for a given string. https://pycryptodome.readthedocs.io/en/latest/src/signature/pkcs1_v1_5.html

But I have not found it uses NONEWithRSA algorithm so its giving different signature than what my clients expects on the server.

Do any library on python supports this algorithm?

I recently ran into this same issue trying to support signature generation for an Android app that expects the equivalent of "NONEwithRSA." In my case, it was actually a situation where the application is using a hash, but it is obviously missing the ASN.1 code prefix.

It turns out that the Java RSA cipher works like magic behind the scenes and automatically uses signature block type PKCS#1 padding (so that each message/key combination produces the same signature).

This caused me problems because I initially thought I could just encrypt the raw hash using the private key in Python, and then it would be compatible with Java. However, encrypting with the private key in PyCryptodome uses encryption block type PKCS#1 which causes a v15 unpad exception when verifying with Java.

The simplest solution I found was to use the rsa package ( https://pypi.org/project/rsa/ ) and directly access some of its internal methods.

    key = rsa.PrivateKey.load_pkcs1(
        RSA.import_key(base64.b64decode(private_key)).export_key()
    )
    keylength = rsa.pkcs1.common.byte_size(key.n)
    padded = rsa.pkcs1._pad_for_signing(message, keylength)
    payload = rsa.pkcs1.transform.bytes2int(padded)
    encrypted = key.blinded_encrypt(payload)
    block = rsa.pkcs1.transform.int2bytes(encrypted, keylength)
    signature = base64.b64encode(block).decode()

Essentially, this code originates in the rsa/pkcs1.py module's "sign_hash" method. We just do not add the ASN.1 code.

This solution is admittedly fragile, but it should give you a good working reference to make something more robust.

Validation

This Python signature generation procedure produces an identical signature to the Java "NONEwithRSA" algorithm for the same key/input.

private_key = "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDhqFoTCc5FEgL3OiM4yEmN/rLkS+bSBigRLW+2N+TdbR/n1O48fUqUoPPa4sEETz7KRHI8EIl+FISyJCk07sks47ucbdfmy0d9omb/GmDnwiRwKe0vFtoTZnrO+sW+l45VEmOymfPYeo6E5pdi9jp7oVZufGiFGryp9DX6mzmpw6unjeZpSfqWaQfba4+EjxO8uiN2GKNlFFxyL6ohrKafGsS9rPaMZv9U4dGpY2ls1855kt8WG4RCHxv8Dh5e0Uj2R3VVnuM/ZvwrW/KLvAf6aUWOZxZhqLknBLUrN+Wh+1KcwZ3PsUoZWAWtY/IIisy/EWatNZYQ0nbk9vFYeEYvAgMBAAECggEBANCJuXxAopwYMh9xXd160ubji0UJk+1h/SMqv0NBUkMDNuoTUHscOpEyUUlmJoQqC9f2+y7hBX87zQmn+2DMj6PpbZb5t/20vIOxUsfMA+QJ+YyiYmwTDUPCgBnJjHc/Li0WSoZiWgQj9pOBWkP9Uzyvc+W1GQ5xEAtsAXpwexCTZB/4Ce6Kmlps4K7QyAJhZnefDNOdtvDzqDlYP0hCfolNK9Y7w0+eGYRBMw7eoxtPlWhAwW+/49/Kljf6YOGYPDY9fP+X+U06YNyuP4kbWLRSaW+uYfLzol2lN1U9y9O83FbvIYI/m4fJVq0xveTkpTj6nwAm6FHcumsoIXEEQykCgYEA937skZ01b6/ea04erQA4kCZZIdAVXrZBSuFHsK/XozBLwVxE3rIIiGAVP5F27GFYsQZzqavx70IIJ/jjYYTc2HRxkT+yU4eXJKTwZTsyuGgR9YAqQrTgfA38NIJ3fYMcwzLOd5gR32GRWJELZI+DT4h+y2PjNPckXfuDQXurMQUCgYEA6WlUgic4yuhnjsT6lZVHCS+RFcp4rgSMBufn24zVkFLS5ajiczth2qMKQquewawoHnGWPQrD1JC1uPtZVL1Xu8oySx13BFlY2Mr6uTJG2TIMY2sh1Nk85QAUXye2nfS1LcuZ3hr6De/B2OCtf+F23dHZ9domxMzhyVQ6hklU0KMCgYEA2+izW09Mw8iI4N+w8hdYKv1oBVtXIajfqyExChW0jRPEvDK4Hwgh/MsjFjCycqasn98TtpdJiUYq6RmFMIlVkh8lKeJnGUFJUw4bbFeFYcVW78TJyCvfq1rh1eZ3NdbiH69tNWaxusJ+2ytF7Dx4eJrXzvEaWV2fusJYhj7UheECgYEAimIHV273jd1B4HRKeLrbNcf8xaWX7NwHROTjItiZmYpxPPXm5Bi1yFFXePWVFxABbSM8xo4GUpVFGBn0+XeUd2LGdKBQesSaF1YoVfpH5Od2Ts8qbK4rGz3z1gKOk5GMhU0ruXHXBMI79GAonSPIPHT5O3iN+anax2lnlUwOu+8CgYEAj/I5LNVX+ocHvM/VaaDAXPPEvcv00Wl84PWDJnKe2kOz7ouKU2fqvmv1LUHBDiPaTL3vK5REKFA0NQSQ4SzbHX1BAdNuZAOMP3Fmtd5CigOJx4lr508rutyCDNBlGjvXLuV7IzjelT4C6o0QoHLPIKN7r57jxP+uOcbMgmuVNmI="
message = "PLWReC8yuVEViE6kpS9tV2ueldyruoCMBLthuPgKDnw="

Decode the message from base 64 to a byte array in each language. The previously supplied Python signing method will produce the following signature:

signature_result = "WhM77ROa7Dl0HmO6QdMQjkQ336N2gfi4c2QuLKkIHtxWZQDm7xmLbzbcVxzN3A6hrSdZIS/v5HIpuLe5MovEno6t5QhTyONL4J0evYeO3Ou784qQZORXRlgoOCbkZu25Ty9hBy043HaeUprkOC5f7hcJjRmrel1+izD25NFEDreCnC3paPJqzMXQyUI6gioqdaxth3H0lRW9rAu4pgH/Hgt0/ljd0ch8FJPhfEUNKAV6wltp4h53D9gSlJVBCW2/VFDpzfTHkgkNK+egA2GTc/glWKQ0jm2tEVWniR9nia2bJfNJ/LWrr19ktejex+7g93569HX8w+jp6dAlSIMk9Q=="

The following Java code produces the same result:

    Signature instance = Signature.getInstance("NONEwithRSA");
    instance.initSign(privateKey);
    instance.update(Base64.decode(data, Base64.NO_WRAP));
    byte[] signature = instance.sign();
    String b64Signature = Base64.encodeToString(signature, Base64.NO_WRAP);

Yes, you can use the library cryptography with Prehashed .

This will assume your data is already the output of a hash (so it will not add extra hashing):

import hashlib
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa, utils

private_key = rsa.generate_private_key(
    public_exponent = 65537,
    key_size = 2048,
    backend = default_backend()
)
public_key = private_key.public_key()

# Compute the hash here
prehashed_msg = hashlib.sha256(b"A message I want to sign").digest()

# Sign the hash (do not perform any hashing)
signature = private_key.sign(
    prehashed_msg,
    padding.PSS(
        mgf = padding.MGF1(hashes.SHA256()),
        salt_length = padding.PSS.MAX_LENGTH),
    utils.Prehashed(hashes.SHA256())
)

# Verify
public_key.verify(
    signature,
    prehashed_msg,
    padding.PSS(
        mgf = padding.MGF1(hashes.SHA256()),
        salt_length = padding.PSS.MAX_LENGTH),
    utils.Prehashed(hashes.SHA256())
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM