Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing function to seal a message with a recipient public key and a nonce #254

Closed
timlangner opened this issue Feb 16, 2022 · 15 comments
Closed

Comments

@timlangner
Copy link

timlangner commented Feb 16, 2022

Hi!
I would like to seal a message with a given public key and a nonce. However, it seems like that I cannot provide my own nonce.

I'm using sodium.box.seal(message: Bytes, recipientPublicKey: Box.PublicKey) right now which doesn't take a nonce as an argument.

A similar function which does this is implemented in the tweetnacl-js-sealed-box library.

Kind regards
Tim

@johnalanwoods
Copy link
Contributor

sealed-box is really just x25519 key agreement in an ephemeral:static context with XSalsa20-Poly1305.
As you're probably aware, reuse of a (key, nonce) tuple with XSalsa/XChaCha results in a catastrophic loss of security.

Swift-sodium, intentionally hides the nonce management as an implementation detail.

It's trivial to extend behaviour if you need to override, but why?

@timlangner
Copy link
Author

I agree that a reuse of a nonce results in a catastrophic loss of security. But for my purposes I need to know the exact nonce which is used to encrypt a message with a public key and add it to my encrypted file data so it supports the decryption process on my other ends.

Is there a way to achieve this?

@johnalanwoods
Copy link
Contributor

Yep just change this line to use your custom nonce:

randombytes_buf(&nonce, NonceBytes)

@timlangner
Copy link
Author

timlangner commented Feb 16, 2022

I appreciate your help!

But this would lead to the same nonce being re-used every single time right? I still want to use a new random nonce every time. I just want to either know which nonce is used to encrypt the message or be able to pass a custom nonce. And even if I would set a custom nonce in the line you provided, the function sodium.box.seal(message: Bytes, recipientPublicKey: Box.PublicKey) is not going through that nonce function which means it wouldn't work anyway.

Any other ideas?

@johnalanwoods
Copy link
Contributor

johnalanwoods commented Feb 16, 2022

Ah my mistake, anonymous boxes (unlike the authenticated variant) use the following structure:

ephemeral_pk ‖ box(m, recipient_pk, ephemeral_sk, nonce=blake2b(ephemeral_pk ‖ recipient_pk))

Which is why you don't see a nonce call, the nonce is the blake2b digest of the concatenation of the ephemeral_pk and the recipient_pk - which due to the ephemeral nature of the former will always be random.

So, you can strip the last crypto_box_curve25519xsalsa20poly1305_NONCEBYTES i.e. 24 Bytes from the anonymous cipher text, or you can reconstitute it yourself.

Alternatively there is a static:static variant which allows nonce specification, but you require key pairs for sender and recipient.

@jedisct1 - let me know if I'm off base with this advice, but I think I'm correct.

@johnalanwoods
Copy link
Contributor

EDIT: actually the better way to do it is strip from the anonymous cipher text the prefix representing ephemeral_pk.

Then hash it concatenated with your recipients pk.

Then you have the nonce.

@jedisct1
Copy link
Owner

In Swift, Box.seal() maps to the traditional crypto_box() construction, not sealed boxes.

The output is the concatenation of the nonce and the ciphertext.

So, if you want the nonce, just extract the 24 first bytes.

@johnalanwoods
Copy link
Contributor

@jedisct1 - the anonymous public key encryption maps to the c libs crypto_box_seal from what I can see.

i.e.: https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes

I think the answer here is to reconstitute the nonce the same way the c lib does, i.e.:

blake2b(epk||rpk):

https://github.com/jedisct1/libsodium/blob/6d566070b48efd2fa099bbe9822914455150aba9/src/libsodium/crypto_box/crypto_box_seal.c#L10-L20

@jedisct1
Copy link
Owner

The anonymous one, but I don't think this is what the OP is referring to.

@timlangner
Copy link
Author

Your explanation definitely clarifies some things for me. So if I understand this correctly the sodium.box.seal(message: Bytes, recipientPublicKey: Box.PublicKey) function appends the random generated nonce before the ciphertext? So if I extract the 24 first bytes from the encrypted message (ciphertext) I have the nonce, right?

@johnalanwoods
Copy link
Contributor

johnalanwoods commented Feb 16, 2022

@timlangner - first are you using the anonymous or authenticated encryption?

i.e: Anonymous Encryption (Sealed Boxes) OR Authenticated Encryption?

@jedisct1
Copy link
Owner

jedisct1 commented Feb 16, 2022

I don't think it makes a difference. The nonce is encoded in the first 24 bytes in both cases

Scratch that, I was confused with secretstream.

@timlangner
Copy link
Author

I'm using anonymous encryption

@johnalanwoods
Copy link
Contributor

johnalanwoods commented Feb 16, 2022

@timlangner in that case you need to do what I suggest above.

First 24 Bytes is only the nonce with authenticated boxes.

With sealed boxes you need to calculate it as:

nonce=blake2b(ephemeral_pk ‖ recipient_pk))

Where you know the recipient_pk (your sending to it) and you know the ephemeral_pk it's the first 32 Bytes of the ciphertext (sealed box).

i.e.

ephemeral_pkbox(m, recipient_pk, ephemeral_sk, nonce=blake2b(ephemeral_pk ‖ recipient_pk))

cc: @jedisct1

@timlangner
Copy link
Author

It's good to know that the nonce is not encoded in the first 24 bytes in both cases. That makes a difference in my decryption process.

In the tweet-nacl-js-sealed-box library used for the frontend the nonce is always 24 bytes long.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants