Monocypher

Boring crypto that simply works

Chacha20

Chacha20 and XChacha20 encryption

Synopsis

#include <monocypher.h>

void crypto_chacha20_init(crypto_chacha_ctx *ctx,
                          const uint8_t      key[32],
                          const uint8_t      nonce[8]);

void crypto_chacha20_x_init(crypto_chacha_ctx *ctx,
                            const uint8_t      key[32],
                            const uint8_t      nonce[24]);

void crypto_chacha20_encrypt(crypto_chacha_ctx *ctx,
                             uint8_t           *cipher_text,
                             const uint8_t     *plain_text,
                             size_t             text_size);

void crypto_chacha20_stream(crypto_chacha_ctx *ctx,
                            uint8_t           *stream,
                            size_t             stream_size);

void crypto_chacha20_set_ctr(crypto_chacha_ctx *ctx,
                             uint64_t           ctr);

Description

These functions provide an incremental interface for the Chacha20 encryption primitive.

Chacha20 is a low-level primitive. Consider using authenticated encryption.

The arguments are:

The key and nonce buffers may overlap. plain_text and cipher_text must either be the same buffer (for in-place encryption), or non-overlapping.

crypto_chacha20_init() initialises the crypto_chacha_ctx context. It uses an 8-byte nonce, which is too small to be selected at random. Use a counter.

crypto_chacha20_x_init() initialises the crypto_chacha_ctx context. It uses a 24-byte nonce, which is big enough to be selected at random.

crypto_chacha20_x_init() is recommended over crypto_chacha20_init(). The ability to use random nonces makes it easier to use securely, and the performance hit is negligible in practice.

The following functions need an initialised context to work properly. Calling them with an uninitialised context triggers undefined behaviour.

crypto_chacha20_encrypt() encrypts plain_text by XORing it with a pseudo-random stream of numbers, seeded by the provided crypto_chacha_ctx context. You may call crypto_chacha20_encrypt repeatedly with the same context struct to encrypt a message incrementally. The plain_text pointer is allowed to be NULL, in which case it will be interpreted as an all zero input.

Since XOR is its own inverse, decryption is the same operation as encryption. To decrypt the cipher text, encrypt it again with the same key and nonce. You will likely want to wipe the key and context when you are done with encryption or decryption. Wipe them with crypto_wipe().

crypto_chacha20_stream() is the same as crypto_chacha20_encrypt() with plain_text being NULL. This is useful as a user space random number generator. While this must not be used as a cryptographic random number generator, it can be handy outside of a security context. Deterministic procedural generation and reproducible property-based tests come to mind.

crypto_chacha20_set_ctr resets the internal counter of the crypto_chacha_ctx to the value specified in ctr. Resuming the encryption will use the stream at the block ctr (which is the byte ctr × 64). This can be used to encrypt (or decrypt) part of a long message, or to implement some AEAD constructions such as the one described in RFC 7539. Be careful when using this not to accidentally reuse parts of the random stream as that would destroy confidentiality.

Return values

These functions return nothing.

Examples

Simple encryption:

const uint8_t key        [ 32]; /* Secret random key              */
const uint8_t nonce      [ 24]; /* Unique nonce (possibly random) */
const uint8_t plain_text [500]; /* Message to be encrypted        */
uint8_t       cipher_text[500]; /* Will be the encrypted message  */
crypto_chacha_ctx ctx;
crypto_chacha20_x_init(&ctx, key, nonce);
crypto_chacha20_encrypt(&ctx, cipher_text, plain_text, 500);
/* Wipe secrets if they are no longer needed */
crypto_wipe(key,        32);
crypto_wipe(&ctx,       sizeof(ctx));
crypto_wipe(plain_text, 500);

To decrypt the above:

const uint8_t key        [ 32]; /* Same key as above              */
const uint8_t nonce      [ 24]; /* Same nonce as above            */
const uint8_t cipher_text[500]; /* Encrypted message              */
uint8_t plain_text       [500]; /* Will be the decrypted message  */
crypto_chacha_ctx ctx;
crypto_chacha20_x_init(&ctx, key, nonce);
crypto_chacha20_encrypt(&ctx, plain_text, cipher_text, 500);
/* Wipe secrets if they are no longer needed */
crypto_wipe(key,  32);
crypto_wipe(&ctx, sizeof(ctx));
/* The plain text likely needs to be processed before you wipe it */
crypto_wipe(plain_text, 500);

Incremental encryption:

const uint8_t key        [ 32]; /* Secret random key              */
const uint8_t nonce      [ 24]; /* Unique nonce (possibly random) */
const uint8_t plain_text [500]; /* Message to be encrypted        */
uint8_t       cipher_text[500]; /* Will be the encrypted message  */
crypto_chacha_ctx ctx;
crypto_chacha20_x_init(&ctx, key, nonce);
for(int i = 0; i < 500; i += 100) {
    crypto_chacha20_encrypt(&ctx, cipher_text+i, plain_text+i, 100);
}
/* Wipe secrets if they are no longer needed */
crypto_wipe(key,        32);
crypto_wipe(&ctx,       sizeof(ctx));
crypto_wipe(plain_text, 500);

Simple encryption with a small, not random nonce:

const uint8_t key        [ 32]; /* Secret, random key             */
const uint8_t nonce      [  8]; /* Unique nonce (NOT random)      */
const uint8_t plain_text [500]; /* Message to be encrypted        */
uint8_t       cipher_text[500]; /* Will be the encrypted message  */
crypto_chacha_ctx ctx;
crypto_chacha20_init(&ctx, key, nonce);
crypto_chacha20_encrypt(&ctx, cipher_text, plain_text, 500);
/* Wipe secrets if they are no longer needed */
crypto_wipe(key,        32);
crypto_wipe(&ctx,       sizeof(ctx));
crypto_wipe(plain_text, 500);

Encryption by jumping around (do not do that, this is only meant to show how crypto_chacha20_set_ctr() works):

const uint8_t key        [ 32]; /* Secret random key              */
const uint8_t nonce      [ 24]; /* Unique nonce (possibly random) */
const uint8_t plain_text [500]; /* Message to be encrypted        */
uint8_t       cipher_text[500]; /* Will be the encrypted message  */
crypto_chacha_ctx ctx;
crypto_chacha20_x_init(&ctx, key, nonce);
/* Encrypt the second part of the message first... */
crypto_chacha20_set_ctr(&ctx, 3);
crypto_chacha20_encrypt(&ctx,
                        cipher_text + (3 * 64),
                        plain_text  + (3 * 64),
                        500         - (3 * 64));
/* ...then encrypt the first part */
crypto_chacha20_set_ctr(&ctx, 0);
crypto_chacha20_encrypt(&ctx, cipher_text, plain_text, 3 * 64);
/* Wipe secrets if they are no longer needed */
crypto_wipe(key,        32);
crypto_wipe(&ctx,       sizeof(ctx));
crypto_wipe(plain_text, 500);

Standards

These functions implement Chacha20 and XChacha20. Chacha20 is described in RFC 7539. XChacha20 derives from Chacha20 the same way XSalsa20 derives from Salsa20, and benefits from the same security reduction (proven secure as long as Chacha20 itself is secure).

Security considerations

Encrypted does not mean secure.

Chacha20 only protects against eavesdropping, not forgeries. Most applications need protection against forgeries to be properly secure. To ensure the integrity of a message, use Blake2b in keyed mode, or authenticated encryption.

Nonce reuse

Repeating a nonce with the same key exposes the XOR of two or more plain text messages, effectively destroying confidentiality.

For the same reason, do not select small nonces at random. The crypto_chacha20_init nonce spans only 64 bits, which is small enough to trigger accidental reuses. A counter should be used instead. If multiple parties send out messages, Each can start with an initial nonce of 0, 1 .. n-1 respectively, and increment them by n for each new message. Make sure the counters never wrap around.

Secure random number generation

Do not use these functions as a cryptographic random number generator. Always use the operating system's random number generator for cryptographic purposes, see the introduction.

Protection against side channels

Secrets should not dwell in memory longer than needed. Wipe secrets you no longer need with crypto_wipe(). For Chacha20, this means the context, the key, and in some cases the plain text itself.