w3c / webcrypto

The W3C Web Cryptography API
https://w3c.github.io/webcrypto/
Other
263 stars 53 forks source link

RSA encryption not allowed with Private Key? #315

Closed markcellus closed 1 year ago

markcellus commented 1 year ago

First, let me say thank you for creating this API. I just recently discovered it exists for a web app I'm building. I've been using it with great results so far! However I have a question (that I hope isn't just due to my lack of understanding :sweat_smile:):

I've generated a keypair using generateKey() method using the RSA-OAEP algorithm. From my understanding of the RSA, the encryption allows for it to be done both ways:

  1. encrypt with private key => decrypt with public key
  2. encrypt with public key => decrypt with private key

But the generated private key only allows for decryption and the public key only allows for encryption (regardless of the usages I specify). Is there any reason why encrypt can't be a valid usage of the private key and decrypt for public key? I'd like to encrypt each "public" message using the sender's private key, so that it can't be decrypted/read by anyone other than logged-in users (who know the sender's public key).

An alternative would be encrypting using every single user's public key across the site, but that would be less performant and nearly impossible to scale. signing is also an option, but the encryption of the message is lost. Even though, technically, the message can be decrypted by anyone who knows the public key of the person who created it, I'd still like the encryption to be applied to the message before it's stored in the database.

Any thoughts or information would be great. Thanks in advance.

tniessen commented 1 year ago

From my understanding of the RSA, the encryption allows for it to be done both ways:

  1. encrypt with private key => decrypt with public key
  2. encrypt with public key => decrypt with private key

That's true. However, the first case is almost exclusively used for digital signatures. Without going into much detail, RSA signatures are essentially encrypted by the signer and decrypted by the verifier. Beyond that, there is usually little use for encrypting data using a private key.

I'd like to encrypt each "public" message using the sender's private key, so that it can't be decrypted/read by anyone other than logged-in users (who know the sender's public key).

That might not be a good idea; any single one of the logged-in users could share the sender's public key and the whole system would be broken. As a rule of thumb, assume that any public key is truly public. Only private and secret keys are confidential.

An alternative would be encrypting using every single user's public key across the site, but that would be less performant and nearly impossible to scale. signing is also an option, but the encryption of the message is lost.

There are many alternative designs, but most boil down to encrypting the message using a symmetric algorithm (e.g., AES-GCM). As for how you manage/distribute the required secret keys, there are again many options. Each user could have an ECDH public key, which the sender could use to derive secret keys. Each user could have an RSA-OAEP public key, which the sender could use to encrypt a randomly generated secret key. The sender could additionally sign the message to ensure authenticity.

Performance is a reasonable concern, of course. A common approach would be to group messages by the set of users who should have access to them (e.g., a chat channel or a group chat). Simplified idea: When a user joins, they provide a public key (e.g., RSA-OAEP or ECDH) and receive the secret key that is used to encrypt messages within the channel/group, protected using their public key. With this mechanism, regardless of the number of recipients, a sender only needs to encrypt and sign each message once, and all receivers will be able to decrypt the message and verify the signature. If the set of users who should have access changes, simply generate a new symmetric key that is used for all future messages.

markcellus commented 1 year ago

That might not be a good idea; any single one of the logged-in users could share the sender's public key and the whole system would be broken. As a rule of thumb, assume that any public key is truly public. Only private and secret keys are confidential.

The encryption for "public" messages isn't to stop any attacker from seeing the message. It's to represent that a message is "public" (anyone can see), but viewing it is limited to those that have access to the public key, which is essentially everyone on the site. The intent of encryption in this case is solely to avoid having plaintext for these public messages sitting in the database. Like I mentioned before, I realize the messages can be decrypted by anyone who knows the sender's public key, but that's fine for my case.

Thanks for the interesting ideas. I've considered just encrypting messages using a shared secret that is encrypted by each of the recipients public keys. This is pretty easy to do, especially for messages posted to small groups. But I'm trying to avoid using this for this "site-wide public messages" approach, because it would require encrypting the shared secret with everyone's public key on the site, which (like mentioned above) is undesirable for performance reasons.

tniessen commented 1 year ago

Sorry, I'm not sure I understand your security/threat model.

Anyway, if you really want the "encrypt with private key => decrypt with public key" semantics from your first comment, you can simply use a secret AES-GCM key and couple it with an ECDSA or RSA key pair that the sender uses to sign messages. Make the AES-GCM key and the ECDSA or RSA public key available to all users, and you got the semantics you are looking for (except it has marginally more overhead). The sender can keep reusing the AES-GCM key for up to 232 messages if you generate the nonces securely.

If you want to further reduce overhead, you can even use the same AES-GCM key across all senders. A better approach might be to share some long input keying material (IKM) with all users, which they can then combine with some random salt through a key derivation function (such as HKDF) to obtain AES-GCM keys. This vastly increases the number of messages you can encrypt without distributing new IKM.

Of course, if even a single user leaks this key (by accident, through malware infection, ...), all confidentiality is lost, but it sounds like this is acceptable to you.

markcellus commented 1 year ago

If you want to further reduce overhead, you can even use the same AES-GCM key across all senders.

Yeah, this is what I was thinking as a workaround. Generate a shared AES-GCM key for site-wide use, and encrypt/decrypt public user messages with that. I know this is a little unconventional, but this would only be the case for messages that are truly meant to be public (the world can see them) (i.e. site-wide announcements, messages by users who don't care who sees the message, etc). Not for messages that are intended to be private, which will use a much more secure encryption mechanism.

All that aside, though, still hoping for an answer to the question in my original comment: Why does the spec limit RSA encryption to public keys and decryption to private keys and don't allow the reverse? The RSA encryption mechanism allows it, so the limitation does seem rather odd, regardless of the use-cases.

tniessen commented 1 year ago

Why does the spec limit RSA encryption to public keys and decryption to private keys and don't allow the reverse? The RSA encryption mechanism allows it, so the limitation does seem rather odd, regardless of the use-cases.

RSA itself (as a mathematical construct) allows it, but which padding mechanism would you use? The OAEP design does not allow this, as far as I remember. Almost all other padding mechanisms are designed for digital signatures only (e.g., PSS). There are some variants that allow signatures with message recovery but those are extremely rare.

One of the reasons being, as I said earlier:

almost exclusively used for digital signatures. Without going into much detail, RSA signatures are essentially encrypted by the signer and decrypted by the verifier. Beyond that, there is usually little use for encrypting data using a private key.

Then, depending on your padding mechanism, it will probably restrict your message sizes to the modulus length of your RSA key, while producing ciphertexts with large overheads. Even if you encrypt a single byte of data, the ciphertext will likely be as large as the modulus (e.g., 512 bytes if you use 4096-bit RSA keys).

Combining a symmetric cipher (e.g., AES) with a fast, low-overhead digital signature mechanism (e.g., ECDSA) allows almost arbitrarily large messages, it is often faster, and ciphertexts are likely smaller.

twiss commented 1 year ago

Hello :wave: I'll close this, since (as @tniessen mentioned) while RSA itself allows both signing and encrypting with the same key, none of the concrete algorithms used by this specification (RSASSA-PKCS1-v1_5, RSA-PSS, and RSA-OAEP) allow it. See RFC3447 for more details.