3
votes

I have tested Public-key-cryptography by libsodium and came across a strange behavior. The encrypted message is decrypted without the private key.

Example from official site libsodium

#include "sodium.h"

#define MESSAGE         "test"
#define MESSAGE_LEN     4
#define CIPHERTEXT_LEN (crypto_box_MACBYTES + MESSAGE_LEN)

static bool TestSodium()
{
    unsigned char alice_publickey[crypto_box_PUBLICKEYBYTES];
    unsigned char alice_secretkey[crypto_box_SECRETKEYBYTES];
    crypto_box_keypair(alice_publickey, alice_secretkey);

    unsigned char bob_publickey[crypto_box_PUBLICKEYBYTES];
    unsigned char bob_secretkey[crypto_box_SECRETKEYBYTES];
    crypto_box_keypair(bob_publickey, bob_secretkey);

    unsigned char nonce[crypto_box_NONCEBYTES];
    unsigned char ciphertext[CIPHERTEXT_LEN];
    randombytes_buf(nonce, sizeof nonce);

    // message alice -> bob
    if (crypto_box_easy(ciphertext, (const unsigned char*)MESSAGE, MESSAGE_LEN, nonce, bob_publickey, alice_secretkey) != 0)
    {
        return false;
    }

    unsigned char decrypted[MESSAGE_LEN + 1];
    decrypted[MESSAGE_LEN] = 0;

    // Original!
    //if (crypto_box_open_easy(decrypted, ciphertext, CIPHERTEXT_LEN, nonce, alice_publickey, bob_secretkey) != 0)

    // Whis works without Bobs secret key!
    if (crypto_box_open_easy(decrypted, ciphertext, CIPHERTEXT_LEN, nonce, bob_publickey, alice_secretkey) != 0)
    {
        return false;
    }

    if(strcmp((const char*)decrypted, MESSAGE) != 0) return false;

    return true;
}

Using public-key authenticated encryption, Alice can encrypt a confidential message specifically for Bob, using Bob's public key.

Using Alice's public key, Bob can verify that the encrypted message was actually created by Alice and was not tampered with, before eventually decrypting it.

Bob only needs Alice's public key, the nonce and the ciphertext.

And in order to send messages to Bob, Alice only needs Bobs's public key.

In original examle Bob decrypts message from Alice by own secret key and verifies it by Alice's public key. I made a mistake in code and message was properly decrypted without Bob's private key!

How it is possible? Where is my mistake? Thanks

1

1 Answers

1
votes

Yes it is possible

In libsodium, public-key authenticated encryption is done in three separate phases, in that order:

  1. Key exchange — Use an elliptic-curve Diffie-Hellman algorithm (X25519) to generate the shared secret keys from my private key and your public key.

  2. Encryption — Apply symmetric-key encryption (XSalsa20) to the plaintext message using a shared secret key generated in step 1.

  3. Authentication — Generate a MAC (Poly1305), again relying on the keys generated in the above steps.

Using symmetric encryption in steps 2-3 means the same key must be computable by both Alice and Bob, i.e.

shared_key_computed_by_alice = crypto_box_beforenm(bob_pk, slice_sk)
shared_key_computed_by_bob = crypto_box_beforenm(alice_pk, bob_sk) 
assert(shared_key_computed_by_alice == shared_key_computed_by_bob)

Since we require both key pairs to generate the same shared secret key, it is not hard to see that both pairs can also decrypt the same message.

And this is fine

Note that in your implementation of the "wrong" decryption, you not only used Bob's public key, but also Alice's private key (which only Alice knows).

Since it is Alice that wants to send the encrypted message to Bob, this means Alice should already know the plain text message in the first place. So she can decrypt that message using her own private key is not a security problem.

The decryption routine would really fail if you used Bob's public key with another person (Eve)'s private key.

 

If you think Alice being able to decrypt her own message is a problem, you could force Alice to destroy her private key after the conversation 😛, so now only Bob can decrypt it (and no more messages can be sent from Alice).

In fact libsodium provides the sealed box API which does this (generate an ephemeral key-pair and destroys it right after encryption).