]> git.codecow.com Git - Monocypher.git/commitdiff
Add streaming AEAD.
authorLoup Vaillant <loup@loup-vaillant.fr>
Sun, 25 Jul 2021 21:11:23 +0000 (23:11 +0200)
committerLoup Vaillant <loup@loup-vaillant.fr>
Tue, 10 Jan 2023 00:45:11 +0000 (01:45 +0100)
Tests not complete, no documentation yet.

I'm currently rethinking the AEAD API as a whole, and to be honest I'm
so happy with this streaming API that I believe it could replace the
regular API entirely.

One problem with the AEAD API is the sheer number of arguments.
`crypto_lock_aead()` and `crypto_unlock_aead()` currently have 8
arguments, comprising 6 pointers (all of the same type) and 2 sizes.
There are way too many opportunities to swap arguments and break stuff.

The streaming API however is divided into an init phase, which has only
3 arguments, and a read/write phase, which has 7, but "only" 4 pointers
to byte buffers.  Which I don't think we can improve much.  We could try
and use a second struct similar to what we do with Argon2, but with only
7 arguments (compared to Argon2's 15) I don't think we would gain that
much readability.

As for how to use the streaming API for single shot uses, that's obvious
enough:

- Declare the context and call Init.
- Call read/write.
- Wipe the context.

One may argue that everything else (Poly1305, Blake2b, SHA-512, and
HMAC) provide a one-shot API, and we should do so here as well.  There's
just one problem: we don't have one init function, we have _three_.

If we provide a one-shot API, orthogonality would need all 3 variants.
That's 6 functions total (3 locks, 3 unlocks), which is a bit much,
especially since at least one of them is only provided for compatibility
with a standard I don't entirely agree with.  We could of course only
provide only the single one-shot API (like we do right now), but that
leaves such an obvious hole in the API.

Stopping at just the 5 functions we need for everything (streaming,
one-shot, all 3 variants) is very tempting.

See #218

src/monocypher.c
src/monocypher.h
tests/gen/aead_8439.c [new file with mode: 0644]
tests/gen/makefile
tests/test.c

index 7fa90c5ab6f24177ec9591df864532a8d93451a4..09605741ce9f6a6965328ee75729504b69c6633f 100644 (file)
@@ -2826,54 +2826,93 @@ static void lock_auth(u8 mac[16], const u8  auth_key[32],
        crypto_poly1305_final (&poly_ctx, mac); // ...here
 }
 
-void crypto_lock_aead(u8 mac[16], u8 *cipher_text,
-                      const u8  key[32], const u8  nonce[24],
-                      const u8 *ad        , size_t ad_size,
-                      const u8 *plain_text, size_t text_size)
+void crypto_aead_init_x(crypto_aead_ctx *ctx,
+                        u8 const key[32], const u8 nonce[24])
 {
-       u8 sub_key[32];
-       u8 auth_key[64]; // "Wasting" the whole Chacha block is faster
-       crypto_chacha20_h(sub_key, key, nonce);
-       crypto_chacha20_djb(auth_key, 0, 64, sub_key, nonce + 16, 0);
+       crypto_chacha20_h(ctx->key, key, nonce);
+       COPY(ctx->nonce, nonce + 16, 8);
+       ctx->counter = 0;
+}
+
+void crypto_aead_init_djb(crypto_aead_ctx *ctx,
+                          const u8 key[32], const u8 nonce[8])
+{
+       COPY(ctx->key  , key  , 32);
+       COPY(ctx->nonce, nonce,  8);
+       ctx->counter = 0;
+}
+
+void crypto_aead_init_ietf(crypto_aead_ctx *ctx,
+                           const u8 key[32], const u8 nonce[12])
+{
+       COPY(ctx->key  , key      , 32);
+       COPY(ctx->nonce, nonce + 4,  8);
+       ctx->counter = (u64)load32_le(nonce) << 32;
+}
+
+void crypto_aead_write(crypto_aead_ctx *ctx, u8 *cipher_text, u8 mac[16],
+                       const u8 *ad,         size_t ad_size,
+                       const u8 *plain_text, size_t text_size)
+{
+       u8 auth_key[64]; // the last 32 bytes are used for rekeying.
+       crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter);
        crypto_chacha20_djb(cipher_text, plain_text, text_size,
-                           sub_key, nonce + 16, 1);
+                           ctx->key, ctx->nonce, ctx->counter + 1);
        lock_auth(mac, auth_key, ad, ad_size, cipher_text, text_size);
-       WIPE_BUFFER(sub_key);
+       COPY(ctx->key, auth_key + 32, 32);
        WIPE_BUFFER(auth_key);
 }
 
-int crypto_unlock_aead(u8 *plain_text, const u8 key[32], const u8 nonce[24],
-                       const u8  mac[16],
-                       const u8 *ad         , size_t ad_size,
-                       const u8 *cipher_text, size_t text_size)
+int crypto_aead_read(crypto_aead_ctx *ctx, u8 *plain_text, const u8 mac[16],
+                     const u8 *ad,          size_t ad_size,
+                     const u8 *cipher_text, size_t text_size)
 {
-       u8 sub_key[32];
-       u8 auth_key[64]; // "Wasting" the whole Chacha block is faster
-       crypto_chacha20_h(sub_key, key, nonce);
-       crypto_chacha20_djb(auth_key, 0, 64, sub_key, nonce + 16, 0);
+       u8 auth_key[64]; // the last 32 bytes are used for rekeying.
        u8 real_mac[16];
+       crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter);
        lock_auth(real_mac, auth_key, ad, ad_size, cipher_text, text_size);
-       WIPE_BUFFER(auth_key);
        int mismatch = crypto_verify16(mac, real_mac);
        if (!mismatch) {
                crypto_chacha20_djb(plain_text, cipher_text, text_size,
-                                   sub_key, nonce + 16, 1);
+                                   ctx->key, ctx->nonce, ctx->counter + 1);
+               COPY(ctx->key, auth_key + 32, 32);
        }
-       WIPE_BUFFER(sub_key);
+       WIPE_BUFFER(auth_key);
        WIPE_BUFFER(real_mac);
        return mismatch;
 }
 
-void crypto_lock(u8 mac[16], u8 *cipher_text,
-                 const u8 key[32], const u8 nonce[24],
-                 const u8 *plain_text, size_t text_size)
+void crypto_lock_aead(u8 mac[16], u8 *cipher_text, const u8 key[32],
+                      const u8  nonce[24], const u8 *ad, size_t ad_size,
+                      const u8 *plain_text, size_t text_size)
+{
+       crypto_aead_ctx ctx;
+       crypto_aead_init_x(&ctx, key, nonce);
+       crypto_aead_write(&ctx, cipher_text, mac, ad, ad_size,
+                         plain_text, text_size);
+       crypto_wipe(&ctx, sizeof(ctx));
+}
+
+int crypto_unlock_aead(u8 *plain_text, const u8 key[32], const u8 nonce[24],
+                       const u8  mac[16], const u8 *ad, size_t ad_size,
+                       const u8 *cipher_text, size_t text_size)
+{
+       crypto_aead_ctx ctx;
+       crypto_aead_init_x(&ctx, key, nonce);
+       int mismatch = crypto_aead_read(&ctx, plain_text, mac, ad, ad_size,
+                                       cipher_text, text_size);
+       crypto_wipe(&ctx, sizeof(ctx));
+       return mismatch;
+}
+
+void crypto_lock(u8 mac[16], u8 *cipher_text, const u8 key[32],
+                 const u8 nonce[24], const u8 *plain_text, size_t text_size)
 {
        crypto_lock_aead(mac, cipher_text, key, nonce, 0, 0, plain_text, text_size);
 }
 
-int crypto_unlock(u8 *plain_text,
-                  const u8 key[32], const u8 nonce[24], const u8 mac[16],
-                  const u8 *cipher_text, size_t text_size)
+int crypto_unlock(u8 *plain_text, const u8 key[32], const u8 nonce[24],
+                  const u8 mac[16], const u8 *cipher_text, size_t text_size)
 {
        return crypto_unlock_aead(plain_text, key, nonce, mac, 0, 0,
                                  cipher_text, text_size);
index e98ac360742e972ea1cf8cdabc984c6dccfc57e8..0aa4f1a0772878e13030a5766c42181e89bf3b27 100644 (file)
@@ -108,6 +108,32 @@ int crypto_unlock_aead(uint8_t       *plain_text,
                        const uint8_t *ad         , size_t ad_size,
                        const uint8_t *cipher_text, size_t text_size);
 
+// Authenticated stream
+// --------------------
+typedef struct {
+    uint64_t counter;
+    uint8_t  key[32];
+    uint8_t  nonce[8];
+} crypto_aead_ctx;
+
+void crypto_aead_init_x(crypto_aead_ctx *ctx,
+                        const uint8_t key[32], const uint8_t nonce[24]);
+void crypto_aead_init_djb(crypto_aead_ctx *ctx,
+                          const uint8_t key[32], const uint8_t nonce[8]);
+void crypto_aead_init_ietf(crypto_aead_ctx *ctx,
+                           const uint8_t key[32], const uint8_t nonce[12]);
+
+void crypto_aead_write(crypto_aead_ctx *ctx,
+                       uint8_t         *cipher_text,
+                       uint8_t          mac[16],
+                       const uint8_t   *ad        , size_t ad_size,
+                       const uint8_t   *plain_text, size_t text_size);
+int crypto_aead_read(crypto_aead_ctx *ctx,
+                     uint8_t         *plain_text,
+                     const uint8_t    mac[16],
+                     const uint8_t   *ad        , size_t ad_size,
+                     const uint8_t   *cipher_text, size_t text_size);
+
 
 // General purpose hash (BLAKE2b)
 // ------------------------------
diff --git a/tests/gen/aead_8439.c b/tests/gen/aead_8439.c
new file mode 100644 (file)
index 0000000..57edf03
--- /dev/null
@@ -0,0 +1,83 @@
+// This file is dual-licensed.  Choose whichever licence you want from
+// the two licences listed below.
+//
+// The first licence is a regular 2-clause BSD licence.  The second licence
+// is the CC-0 from Creative Commons. It is intended to release Monocypher
+// to the public domain.  The BSD licence serves as a fallback option.
+//
+// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
+//
+// ------------------------------------------------------------------------
+//
+// Copyright (c) 2022, Loup Vaillant
+// All rights reserved.
+//
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the
+//    distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ------------------------------------------------------------------------
+//
+// Written in 2022 by Loup Vaillant
+//
+// To the extent possible under law, the author(s) have dedicated all copyright
+// and related neighboring rights to this software to the public domain
+// worldwide.  This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this software.  If not, see
+// <https://creativecommons.org/publicdomain/zero/1.0/>
+
+#include <sodium.h>
+#include "utils.h"
+
+static void test(size_t text_size, size_t ad_size)
+{
+    RANDOM_INPUT(key  ,  32);
+    RANDOM_INPUT(nonce,  12);
+    RANDOM_INPUT(ad   ,  32); // ad size   <=  32
+    RANDOM_INPUT(text , 128); // text_size <= 128
+    u8 out[16 + 128];         // text_size <= 128
+
+    crypto_aead_chacha20poly1305_ietf_encrypt_detached(
+        out + 16, out, 0, text, text_size, ad, ad_size, 0, nonce, key);
+
+    print_vector(key   , 32);
+    print_vector(nonce , 12);
+    print_vector(ad    , ad_size);
+    print_vector(text  , text_size);
+    print_vector(out   , text_size + 16);
+    printf("\n");
+}
+
+int main(void)
+{
+    SODIUM_INIT;
+    // regular tests
+    FOR (text_size, 0, 128) { test(text_size, 0); }
+    FOR (text_size, 0, 128) { test(text_size, 4); }
+    FOR (  ad_size, 0,  32) { test(0,   ad_size); }
+    FOR (  ad_size, 0,  32) { test(16,  ad_size); }
+    return 0;
+}
index a80cdf485de90ce4a0e30db3101a0f1cdbe56445..5ab2852014d6eecfd9299db711cdabedc341a1e8 100644 (file)
@@ -54,7 +54,8 @@ CFLAGS = -pedantic -Wall -Wextra
 
 .PHONY: all clean
 
-VEC     = chacha20  hchacha20  xchacha20     ietf_chacha20 aead_ietf      \
+VEC     = chacha20  hchacha20  xchacha20     ietf_chacha20                \
+          aead_ietf aead_8439                                             \
           poly1305  blake2b    sha512        hmac_sha512   argon2         \
           edDSA     edDSA_pk   ed_25519      ed_25519_pk   ed_25519_check \
           x25519    x25519_pk  elligator_inv elligator_dir
@@ -113,6 +114,7 @@ hchacha20.all.vec     : hchacha20.vec
 xchacha20.all.vec     : xchacha20.vec
 ietf_chacha20.all.vec : ietf_chacha20.vec
 aead_ietf.all.vec     : aead_ietf.vec
+aead_8439.all.vec     : aead_8439.vec
 blake2b.all.vec       : blake2b.vec       vectors/blake2b
 sha512.all.vec        : sha512.vec
 hmac_sha512.all.vec   : hmac_sha512.vec   vectors/hmac_sha512
index 7c1de6f8639b28c727b8f4349f2b3b9c0fae27b9..70f460709b765b47614bff97bf4ace879bf92471 100644 (file)
@@ -315,9 +315,23 @@ static void aead_ietf(vector_reader *reader)
                         ad.buf, ad.size, text.buf, text.size);
 }
 
+static void aead_8439(vector_reader *reader)
+{
+    vector key   = next_input(reader);
+    vector nonce = next_input(reader);
+    vector ad    = next_input(reader);
+    vector text  = next_input(reader);
+    vector out   = next_output(reader);
+    crypto_aead_ctx ctx;
+    crypto_aead_init_ietf(&ctx, key.buf, nonce.buf);
+    crypto_aead_write(&ctx, out.buf + 16, out.buf, ad.buf, ad.size,
+                      text.buf, text.size);
+}
+
 static void test_aead()
 {
        VECTORS(aead_ietf);
+       VECTORS(aead_8439);
 
        printf("\taead (roundtrip)\n");
        FOR (i, 0, 1000) {
@@ -349,6 +363,46 @@ static void test_aead()
                crypto_lock_aead(box2, box2 + 16, key, nonce, 0, 0, plaintext, 8);
                ASSERT_EQUAL(box, box2, 24);
        }
+
+       printf("\taead incr (roundtrip)\n");
+       FOR (i, 0, 50) {
+               RANDOM_INPUT(key      , 32);
+               RANDOM_INPUT(nonce    , 24);
+               crypto_aead_ctx ctx_xa;
+               crypto_aead_ctx ctx_xb;
+               crypto_aead_ctx ctx_da;
+               crypto_aead_ctx ctx_db;
+               crypto_aead_ctx ctx_ia;
+               crypto_aead_ctx ctx_ib;
+               crypto_aead_init_x   (&ctx_xa, key, nonce);
+               crypto_aead_init_x   (&ctx_xb, key, nonce);
+               crypto_aead_init_djb (&ctx_da, key, nonce);
+               crypto_aead_init_djb (&ctx_db, key, nonce);
+               crypto_aead_init_ietf(&ctx_ia, key, nonce);
+               crypto_aead_init_ietf(&ctx_ib, key, nonce);
+
+               FOR (j, 0, 10) {
+                       RANDOM_INPUT(ad,  4); // additional data
+                       RANDOM_INPUT(pt,  8); // plaintext
+                       u8 mac[16];
+                       u8 ct [ 8];
+                       u8 pt2[ 8];
+                       // AEAD roundtrip (happy path)
+                       crypto_aead_write         (&ctx_xa, ct , mac, ad, 4, pt, 8);
+                       ASSERT_OK(crypto_aead_read(&ctx_xb, pt2, mac, ad, 4, ct, 8));
+                       ASSERT_EQUAL(pt, pt2, 8);
+                       ASSERT_EQUAL(&ctx_xa, &ctx_xb, sizeof(crypto_aead_ctx));
+
+                       crypto_aead_write         (&ctx_da, ct , mac, ad, 4, pt, 8);
+                       ASSERT_OK(crypto_aead_read(&ctx_db, pt2, mac, ad, 4, ct, 8));
+                       ASSERT_EQUAL(pt, pt2, 8);
+
+                       crypto_aead_write         (&ctx_ia, ct , mac, ad, 4, pt, 8);
+                       ASSERT_OK(crypto_aead_read(&ctx_ib, pt2, mac, ad, 4, ct, 8));
+                       ASSERT_EQUAL(pt, pt2, 8);
+               }
+       }
+       printf("\n");
 }
 
 ///////////////