Monocypher

Boring crypto that simply works

Incremental Authenticated Encryption

Incremental authenticated encryption with additional data.

Synopsis

#include <monocypher.h>

void crypto_lock_init(crypto_lock_ctx *ctx,
                      const uint8_t    key[32],
                      const uint8_t    nonce[24]);

void crypto_lock_auth_ad(crypto_lock_ctx *ctx,
                         const uint8_t   *ad,
                         size_t           ad_size);

void crypto_lock_auth_message(crypto_lock_ctx *ctx,
                              const uint8_t   *plain_text,
                              size_t           text_size);

void crypto_lock_update(crypto_lock_ctx *ctx,
                        uint8_t         *cipher_text,
                        const uint8_t   *plain_text,
                        size_t           text_size);

void crypto_lock_final(crypto_lock_ctx *ctx,
                       uint8_t          mac[16]);

void crypto_unlock_init(crypto_unlock_ctx *ctx,
                        const uint8_t      key[32],
                        const uint8_t      nonce[24]);

void crypto_unlock_auth_ad(crypto_unlock_ctx *ctx,
                           const uint8_t     *ad,
                           size_t             ad_size);

void crypto_unlock_auth_message(crypto_unlock_ctx *ctx,
                                const uint8_t     *plain_text,
                                size_t             text_size);

void crypto_unlock_update(crypto_unlock_ctx *ctx,
                          uint8_t           *plain_text,
                          const uint8_t     *cipher_text,
                          size_t             text_size);

int crypto_unlock_final(crypto_unlock_ctx *ctx,
                        const uint8_t      mac[16]);

Description

These functions are variants of crypto_lock(), crypto_unlock(), crypto_lock_aead() and crypto_unlock_aead(). Prefer those simpler functions if possible.

This incremental interface can be used to encrypt and decrypt messages too large to fit in a single buffer. The arguments are the same as described for the direct interface

Encryption requires four steps:

Decryption also requires four steps:

Return values

crypto_lock_init(), crypto_unlock_init(), crypto_lock_auth_ad(), crypto_unlock_auth_ad(), crypto_lock_auth_message(), crypto_unlock_auth_message(), crypto_lock_update(), crypto_unlock_update(), and crypto_lock_final() return nothing.

crypto_unlock_final() returns 0 on success or -1 if the message was corrupted. Corruption can be caused by transmission errors, programmer error, or an attacker's interference. Always check the return value.

Examples

Encryption:

const uint8_t key        [ 32]; /* Session key                 */
const uint8_t nonce      [ 24]; /* Unique per session key      */
const uint8_t ad         [500]; /* Optional additional data    */
const uint8_t plain_text [500]; /* Secret message              */
uint8_t       cipher_text[500]; /* Encrypted message           */
uint8_t       mac        [ 16]; /* Message authentication code */

/* Set up initial context */
crypto_lock_ctx ctx;
crypto_lock_init(&ctx, key, nonce);
/* Wipe the key if it is no longer needed */
crypto_wipe(key, 32);

/* Authenticate additional data */
for (size_t i = 0; i < 500; i += 100) {
    crypto_lock_auth_ad(&ctx, ad + i, 100);
}

/* Encrypt message */
for (size_t i = 0; i < 500; i += 100) {
    crypto_lock_update(&ctx, cipher_text + i,
                       plain_text + i, 100);
    /* Wipe the secret message if it is no longer needed */
    crypto_wipe(plain_text + i, 100);
}

/* Produce the MAC */
crypto_lock_final(&ctx, mac);

To decrypt the above:

const uint8_t key        [ 32]; /* Session key              */
const uint8_t nonce      [ 24]; /* Unique per session key   */
const uint8_t mac        [ 16]; /* Transmitted MAC          */
const uint8_t ad         [500]; /* Optional additional data */
const uint8_t cipher_text[500]; /* Encrypted message        */
uint8_t       plain_text [500]; /* Secret message           */

/* Set up initial context */
crypto_unlock_ctx ctx;
crypto_unlock_init(&ctx, key, nonce);
/* Wipe the key if it is no longer needed */
crypto_wipe(key, 32);

/* Verify additional data */
for (size_t i = 0; i < 500; i += 100) {
    crypto_unlock_auth_ad(&ctx, ad + i, 100);
}

/* Decrypt message */
for (size_t i = 0; i < 500; i += 100) {
    crypto_unlock_update(&ctx, plain_text + i,
                         cipher_text + i, 100);
}

/* Check the MAC */
if (crypto_unlock_final(&ctx, mac)) {
    /* Corrupted message, abort processing */
} else {
    /* Genuine message */
}

/* Wipe the secret message if it is no longer needed */
crypto_wipe(plain_text, 500);

To authenticate the above without decrypting it:

const uint8_t key        [ 32]; /* Session key              */
const uint8_t nonce      [ 24]; /* Unique per session key   */
const uint8_t mac        [ 16]; /* Transmitted MAC          */
const uint8_t ad         [500]; /* Optional additional data */
const uint8_t cipher_text[500]; /* Encrypted message        */

/* Set up initial context */
crypto_unlock_ctx ctx;
crypto_unlock_init(&ctx, key, nonce);
/* Wipe the key if it is no longer needed */
crypto_wipe(key, 32);

/* Verify additional data */
for (size_t i = 0; i < 500; i += 100) {
    crypto_unlock_auth_ad(&ctx, ad + i, 100);
}

/* Verify message */
for (size_t i = 0; i < 500; i += 100) {
    crypto_unlock_auth_message(&ctx, cipher_text + i, 100);
}

/* Check the MAC */
if (crypto_unlock_final(&ctx, mac)) {
    /* Corrupted message */
} else {
    /* Genuine message   */
}

In-place encryption without additional data:

const uint8_t key   [ 32]; /* Session key                 */
const uint8_t nonce [ 24]; /* Unique per session key      */
uint8_t       text  [500]; /* Message                     */
uint8_t       mac   [ 16]; /* Message authentication code */

/* Set up initial context */
crypto_lock_ctx ctx;
crypto_lock_init(&ctx, key, nonce);
/* Wipe the key if it is no longer needed */
crypto_wipe(key, 32);

/* Encrypt message */
for (size_t i = 0; i < 500; i += 100) {
    crypto_lock_update(&ctx, text + i, text + i, 100);
}

/* Produce the MAC */
crypto_lock_final(&ctx, mac);

Standards

These functions implement RFC 7539, with XChacha20 instead of Chacha20. 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

Messages are not verified until the call to crypto_unlock_final(). Make sure to call it and check the return value before processing the message. Messages may be stored before they are verified, but they cannot be trusted. Processing untrusted messages increases the attack surface of the system. Doing so securely is hard. Do not process messages before calling crypto_unlock_final().

Implementation details

The incremental interface is roughly three times slower than the direct interface at identifying corrupted messages. This is because the incremental interface works in a single pass and has to interleave decryption and verification. Users who expect a high corruption rate may want to perform a first pass with crypto_unlock_auth_message() before decrypting the message.