From: Loup Vaillant Date: Sun, 25 Jul 2021 21:11:23 +0000 (+0200) Subject: Add streaming AEAD. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=f9f63dfd1ff21774d302fea0a245fd741e55e805;p=Monocypher.git Add streaming AEAD. 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 --- diff --git a/src/monocypher.c b/src/monocypher.c index 7fa90c5..0960574 100644 --- a/src/monocypher.c +++ b/src/monocypher.c @@ -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); diff --git a/src/monocypher.h b/src/monocypher.h index e98ac36..0aa4f1a 100644 --- a/src/monocypher.h +++ b/src/monocypher.h @@ -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 index 0000000..57edf03 --- /dev/null +++ b/tests/gen/aead_8439.c @@ -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 +// + +#include +#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; +} diff --git a/tests/gen/makefile b/tests/gen/makefile index a80cdf4..5ab2852 100644 --- a/tests/gen/makefile +++ b/tests/gen/makefile @@ -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 diff --git a/tests/test.c b/tests/test.c index 7c1de6f..70f4607 100644 --- a/tests/test.c +++ b/tests/test.c @@ -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"); } ///////////////