pyca / pynacl

Python binding to the Networking and Cryptography (NaCl) library
https://pynacl.readthedocs.io/
Apache License 2.0
1.07k stars 230 forks source link

Secret key in Aead and/or SecretBox #833

Open ConYel opened 2 months ago

ConYel commented 2 months ago

I would like to use pynacl in a project in which I want to encrypt a url. I would like to provide back the key of the encryption but I see that it cannot be decoded back with utf-8.

import nacl.secret
import nacl.utils
key = nacl.utils.random(nacl.secret.Aead.KEY_SIZE)
box = nacl.secret.Aead(key)
key.decode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc0 in position 1: invalid start byte

How is it possible to decode the key in a "readable" format that can be copy pasted easily? Maybe my approach is not correct and I should be using something different?

gonatienza commented 2 months ago

Mind me asking what do you mean by provide back the secret key? You probably should not transmit the secret key for symmetric encryption to the receiver. Anyone intercepting that would have the means to decrypt.

It is technically possible, though, encoding with base64 like this ->

import nacl.secret
import nacl.utils
key = nacl.utils.random(nacl.secret.Aead.KEY_SIZE)
box = nacl.secret.Aead(key)

import base64

# regular base64
key_base64 = base64.b64encode(key)
key_base64_str = key_base64.decode('utf-8')

# url safe base64
key_base64_url_safe = base64.urlsafe_b64encode(key)
key_base64_str_url_safe = key_base64_url_safe.decode('utf-8')

key_copy_1 = base64.b64decode(key_base64)
key_copy_2 = base64.urlsafe_b64decode(key_base64_url_safe)

assert key == key_copy_1 == key_copy_2

If you are trying to encrypt something to be decrypted by the receiver, and the receiver to receive the means to compute a key for decryption, you should look into public key encryption. You would only share the public keys and never the secret keys. And both sides would compute the same shared key.

HTH.

ConYel commented 2 months ago

Thank you very much! That's what I was looking for. The issue is that the users might not have any knowledge regarding pke and that is why I would prefer to provide just the key. I understand the issue of mitm/intercepting but if both sides are https should be fine? If the user is hacked or being sniffed I would guess that public key would not save him. Let me know if I am completely wrong, as I don't have much knowledge in that field.

gonatienza commented 2 months ago

I would personally never share a secret key. Not even over tls. There are better ways. With libsodium I would do Curve25519 and use a safe established key exchange method.

The easiest way for that is the nacl.public.Box() wrapper. You could use the low level bindings too, but I would recommend the high level wrapper to avoid any nonce reuse accidents. With this, the public keys are exchanged (transmitted) and each side's secret key with the remote public key compute the same secret key. So no private key nor secret shared key is ever transmitted. If a public key is compromised, it will not allow you to encrypt nor decrypt (computing the shared secret key requires a private key).

Another option is using nacl.public.SealedBox. This way there's only one key pair. The receiver has a private key and public key. The sender of the encrypted message uses the receiver's public key for the encryption of the message and the receiver uses its private key for the decryption. Once again, if the public key is compromised, it will not allow you to decrypt the ciphertext. You can only do that having the private key, which was never shared. Anyone with that public key could encrypt a new message.

Both examples are posted in the link I shared.

Happy coding.

ConYel commented 2 months ago

Again thank you very much for this information and the detailed answer!

I would look into this and try to see if I can use them but maybe is better to discuss how I am using the current method currently.

The point is to provide user with the possibility to encrypt a long url while I give them back a short url (yeap it's just a url shortener with encryption). I don't want to keep any passwords on my db, that is why I want to give directly back the encryption key along with the short url. Considering that the user would just put a long url and get back a generated key (and the short url) I am not sure how it would be easy for a layman to have to use public key crypto on a website.

I will try to see if there is a way for pke but for now I haven't though/found something else. Cheers!