1
$\begingroup$

I am writing an application that involves checking to see if data has been created by me. I do this by encrypting the data with the private key on my server, and then decrypting it using the public key on the client. If the result matches the hashed version of the message then I know it came from me. However, I'm using RSA and so that means I have to flip the keys since I know you can't encrypt with a private key.

Here is how I am generating my keys:

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

public_key = private_key.public_key()

pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)

with open('private_key.pem', 'wb') as f:
    f.write(pem)

pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

with open('public_key.pem', 'wb') as f:
    f.write(pem)

However, here private_key is my public key and public_key is my private key. Is there something I need to change? I've heard public keys are generally smaller but I don't understand enough of the settings to tell if they're symmetric or not.

Any advice would be appreciated.

Thanks.

EDIT: They are definitely not symmetric. As suspected, my public key is much shorter. How do I reverse this?

EDIT2: How about signing/verifying instead?

Server:

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
import base64
import uuid
import hashlib


message_id = uuid.uuid1()
with open("private_key.pem", "rb") as key_file:
    private_key = serialization.load_pem_private_key(
        key_file.read(),
        password=None,
        backend=default_backend()
    )
message = bytes(hashlib.sha3_256((str(message_id) + str(valid)).encode("utf-8")).hexdigest(), "utf-8")
encrypted_data = private_key.sign(
    message,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA3_256()),
        salt_length=padding.PSS.MAX_LENGTH
        ),
    hashes.SHA3_256()
)
encrypted_data = str(base64.b64encode(encrypted_data), "utf-8")

Client:

from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.exceptions import InvalidSignature
import hashlib
import base64


encrypted = response["encrypted"]
message_id = response["message_id"]
valid = response["valid"]
with open("public_key.pem", "rb") as key_file:
    public_key = serialization.load_pem_public_key(
        key_file.read(),
        backend=default_backend()
    )
message = bytes(hashlib.sha3_256((str(message_id) + str(valid)).encode("utf-8")).hexdigest(), "utf-8")
encrypted = base64.b64decode(encrypted)
try:
    public_key.verify(
        encrypted,
        message,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA3_256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA3_256()
    )
    print("Yes")
except InvalidSignature:
    print("No")
$\endgroup$
  • 3
    $\begingroup$ Does this answer your question? Is it more weak to encrypt with the private key RSA? $\endgroup$ – kelalaka Jan 6 at 22:40
  • $\begingroup$ @kelalaka Not exactly. It has led me to the idea of signing/verifying instead. I will post an edit, let me know what you think. $\endgroup$ – Jack P Jan 6 at 22:48
  • $\begingroup$ @kelalaka Done. $\endgroup$ – Jack P Jan 6 at 22:55
  • $\begingroup$ @mentallurg Check my edit $\endgroup$ – Jack P Jan 6 at 23:06
  • 1
    $\begingroup$ @JackP: How about signing/verifying instead? - Sure you can use RSA keys for digital signature. $\endgroup$ – mentallurg Jan 6 at 23:14
3
$\begingroup$

I am writing an application that involves checking to see if data has been created by me.

That's known as verifying data that was signed. It is good practice.

I do this by encrypting the data with the private key on my server, and then decrypting it using the public key on the client.

That's where it gets wrong. The server must sign with the private key, and then the client must verify the signature with the public key. In RSA there are commonalities between sign and decrypt (both include the private-key operation $x\mapsto y=x^d\bmod N$), and between verify and encrypt (both include the public-key operation $y\mapsto x=y^e\bmod N$), but there also are important differences. For this reason, well-designed APIs, and knowledgeable advisers, won't let you encrypt with the private key. Don't even try. Ever. Forget about it.

Since the API includes PSS, which is a signature padding, it most likely includes primitives to sign with RSASSA-PSS. We are not a code review site, thus I won't try to find where. But my reading of hazmat is that primitives in that area are not meant for direct use, I would look elsewhere.

Edit: I do not immediately see an issue with the code as it now stands, except for naming tag and variable encrypted, which I guess are for a signature. But then I do not know this API, and we are not a code review site.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Would you agree with my approach shown in my edit? $\endgroup$ – Jack P Jan 7 at 11:19
  • 1
    $\begingroup$ You've just shown signature generation & verification using PSS. Presuming that your system is otherwise secure, yes, that's what it is made for. Note that you need to trust the public key before you can verify that the data was signed by the right party. $\endgroup$ – Maarten Bodewes Jan 7 at 14:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.