Key Exchange

Key Exchange can be used to exchange a shared key that only the two parties know (so that they can communicate using symmetric encryption), without revealing their respective private keys.

from monocypher.public import PrivateKey, Box

# Alice generates a private key; this must be kept secret!
sk_alice = PrivateKey.generate()
# This can be sent to anyone (in particular, Bob).
pk_alice = sk_alice.public_key

# Bob generates a private key, and sends his public key to Alice.
sk_bob = PrivateKey.generate()
pk_bob = sk_bob.public_key

# Bob and Alice can exchange keys --
#  1. Alice gives Bob her public key,
#  2. Bob gives Alice his public key.
box_bob   = Box(sk_bob, pk_alice)
box_alice = Box(sk_alice, pk_bob)

# They then both have the same key:
assert box_bob.shared_key() == box_alice.shared_key()

During encryption, a random nonce is automatically generated if not specified. Alternatively, you can specify an explicit nonce. It does not have to be secret or random (for instance, you can just use the message counter as the nonce in some protocols), but has to be unique.

Warning

Using the same nonce twice with the same encryption key may allow decryption and forgeries.

message = b"Hello there!"

# Automatically generated nonce:
encrypted = box_bob.encrypt(message)
assert box_alice.decrypt(encrypted) == b"Hello there!"

# Explicitly generated nonce:
from monocypher.utils import random
nonce = random(Box.NONCE_SIZE)
encrypted = box_bob.encrypt(message, nonce)
assert box_alice.decrypt(encrypted) == b"Hello there!"

The above methods perform authenticated encryption using key-exchange; this means that the messages sent can be proven to be sent by you. If we want the receipient to be unable to verify the identity of the sender (but still ensure that the message wasn’t tampered with), use a SealedBox:

from monocypher.public import SealedBox

# send a message only decryptable by Bob
sbox_alice = SealedBox(pk_bob)
ciphertext = sbox_alice.encrypt(b'the sequels are better')

# Bob can decrypt it using his private key.
sbox_bob = SealedBox(sk_bob)
assert sbox_bob.decrypt(ciphertext) == b'the ...'

Reference

class monocypher.public.PrivateKey(sk)

X25519 private key. This must be kept secret.

Parameters:

sk – The private key (bytes).

encode()

Return the private key as bytes.

Return type:

bytes

KEY_SIZE = 32

Length of a private key in bytes.

classmethod generate()

Generates a random PrivateKey object.

Return type:

PrivateKey

property public_key

Returns the corresponding PublicKey object.

Return type:

PublicKey

class monocypher.public.PublicKey(pk)

X25519 public key. This can be published.

Parameters:

pk – The public key (bytes).

encode()

Return the public key as bytes.

Return type:

bytes

KEY_SIZE = 32

Length of a public key in bytes.

class monocypher.public.Box(your_sk, their_pk)

A subclass of SecretBox object with the encryption key being the shared key computed from the key exchange. The shared key is computed using X25519 and HChaCha20. For details see Monocypher’s documentation.

Parameters:
  • your_sk – Your private key (a PrivateKey object).

  • their_pk – Their public key (a PublicKey object).

property shared_key

Returns the shared secret. This value is safe for use as the key for other symmetric ciphers.

class monocypher.public.SealedBox(receipient_key)

SealedBox enables you to send messages decryptable only by the receipient. Each time a message is encrypted, a new ephemeral keypair is generated; the ephemeral private key is used in key-exchange with the receipient_key and thrown away after encryption.

Parameters:

receipient_key – A PublicKey or PrivateKey object. If a PrivateKey is provided, then the SealedBox is able to decrypt messages. Otherwise, SealedBox can only encrypt messages.

decrypt(ciphertext)

Decrypt the given ciphertext. Returns the original message if decryption was successful, otherwise raises CryptoError. If the provided key was a PublicKey, raises a RuntimeError.

Parameters:

ciphertext – The ciphertext to decrypt (bytes-like object).

Return type:

bytes

encrypt(msg)

Encrypt the given msg. This works using a similar construction as that from libsodium’s crypto_box_seal, but using Monocypher’s high level functions.

Parameters:

msg – The message to encrypt (bytes).

Return type:

bytes

Extras

The PrivateKey and PublicKey classes both implement equality (between objects of the same type) and conversion to bytes, as well as hashing:

>>> sk_1 = PrivateKey.generate()
>>> sk_2 = PrivateKey.generate()
>>> sk_1 == sk_2
False
>>> sk_1.public_key == PublicKey(bytes(sk_1.public_key))
True
>>> hash(sk_1)
...
>>> hash(sk_1.public_key)
...

Implementation

Box’s key derivation uses crypto_key_exchange from Monocypher, which uses X25519 and HChaCha20. SealedBox uses the same algorithm, and the encryption format is as follows:

ephemeral_pk || box(ephemeral_sk, receipient_pk, msg, nonce=(24 zeroes))