Added API to share precomputations in EAX.
authorThomas Pornin <pornin@bolet.org>
Thu, 8 Feb 2018 14:50:10 +0000 (15:50 +0100)
committerThomas Pornin <pornin@bolet.org>
Thu, 8 Feb 2018 14:50:10 +0000 (15:50 +0100)
inc/bearssl_aead.h
src/aead/eax.c
test/test_crypto.c

index b1e52a3..c495dc2 100644 (file)
@@ -593,6 +593,20 @@ typedef struct {
 } br_eax_context;
 
 /**
+ * \brief EAX captured state.
+ *
+ * Some internal values computed by EAX may be captured at various
+ * points, and reused for another EAX run with the same secret key,
+ * for lower per-message overhead. Captured values do not depend on
+ * the nonce.
+ */
+typedef struct {
+#ifndef BR_DOXYGEN_IGNORE
+       unsigned char st[3][16];
+#endif
+} br_eax_state;
+
+/**
  * \brief Initialize an EAX context.
  *
  * A block cipher implementation, with its initialised context
@@ -609,6 +623,21 @@ typedef struct {
 void br_eax_init(br_eax_context *ctx, const br_block_ctrcbc_class **bctx);
 
 /**
+ * \brief Capture pre-AAD state.
+ *
+ * This function precomputes key-dependent data, and stores it in the
+ * provided `st` structure. This structure should then be used with
+ * `br_eax_reset_pre_aad()`, or updated with `br_eax_get_aad_mac()`
+ * and then used with `br_eax_reset_post_aad()`.
+ *
+ * The EAX context structure is unmodified by this call.
+ *
+ * \param ctx   EAX context structure.
+ * \param st    recipient for captured state.
+ */
+void br_eax_capture(const br_eax_context *ctx, br_eax_state *st);
+
+/**
  * \brief Reset an EAX context.
  *
  * This function resets an already initialised EAX context for a new
@@ -629,6 +658,52 @@ void br_eax_init(br_eax_context *ctx, const br_block_ctrcbc_class **bctx);
 void br_eax_reset(br_eax_context *ctx, const void *nonce, size_t len);
 
 /**
+ * \brief Reset an EAX context with a pre-AAD captured state.
+ *
+ * This function is an alternative to `br_eax_reset()`, that reuses a
+ * previously captured state structure for lower per-message overhead.
+ * The state should have been populated with `br_eax_capture_state()`
+ * but not updated with `br_eax_get_aad_mac()`.
+ *
+ * After this function is called, additional authenticated data MUST
+ * be injected. At least one byte of additional authenticated data
+ * MUST be provided with `br_eax_aad_inject()`; computation result will
+ * be incorrect if `br_eax_flip()` is called right away.
+ *
+ * After injection of the AAD and call to `br_eax_flip()`, at least
+ * one message byte must be provided. Empty messages are not supported
+ * with this reset mode.
+ *
+ * \param ctx     EAX context structure.
+ * \param st      pre-AAD captured state.
+ * \param nonce   EAX nonce to use.
+ * \param len     EAX nonce length (in bytes).
+ */
+void br_eax_reset_pre_aad(br_eax_context *ctx, const br_eax_state *st,
+       const void *nonce, size_t len);
+
+/**
+ * \brief Reset an EAX context with a post-AAD captured state.
+ *
+ * This function is an alternative to `br_eax_reset()`, that reuses a
+ * previously captured state structure for lower per-message overhead.
+ * The state should have been populated with `br_eax_capture_state()`
+ * and then updated with `br_eax_get_aad_mac()`.
+ *
+ * After this function is called, message data MUST be injected. The
+ * `br_eax_flip()` function MUST NOT be called. At least one byte of
+ * message data MUST be provided with `br_eax_run()`; empty messages
+ * are not supported with this reset mode.
+ *
+ * \param ctx     EAX context structure.
+ * \param st      post-AAD captured state.
+ * \param nonce   EAX nonce to use.
+ * \param len     EAX nonce length (in bytes).
+ */
+void br_eax_reset_post_aad(br_eax_context *ctx, const br_eax_state *st,
+       const void *nonce, size_t len);
+
+/**
  * \brief Inject additional authenticated data into EAX.
  *
  * The provided data is injected into a running EAX computation. Additional
@@ -655,6 +730,24 @@ void br_eax_aad_inject(br_eax_context *ctx, const void *data, size_t len);
 void br_eax_flip(br_eax_context *ctx);
 
 /**
+ * \brief Obtain a copy of the MAC on additional authenticated data.
+ *
+ * This function may be called only after `br_eax_flip()`; it copies the
+ * AAD-specific MAC value into the provided state. The MAC value depends
+ * on the secret key and the additional data itself, but not on the
+ * nonce. The updated state `st` is meant to be used as parameter for a
+ * further `br_eax_reset_post_aad()` call.
+ *
+ * \param ctx   EAX context structure.
+ * \param st    captured state to update.
+ */
+static inline void
+br_eax_get_aad_mac(const br_eax_context *ctx, br_eax_state *st)
+{
+       memcpy(st->st[1], ctx->head, sizeof ctx->head);
+}
+
+/**
  * \brief Encrypt or decrypt some data with EAX.
  *
  * Data encryption or decryption can be done after `br_eax_flip()`
index 07b1cb9..bcc704a 100644 (file)
@@ -113,9 +113,16 @@ do_pad(br_eax_context *ctx)
 }
 
 /*
- * Apply CBC-MAC on the provided data, with buffering management. This
- * function assumes that on input, ctx->buf contains a full block of
- * unprocessed data.
+ * Apply CBC-MAC on the provided data, with buffering management.
+ *
+ * Upon entry, two situations are acceptable:
+ *
+ *   ctx->ptr == 0: there is no data to process in ctx->buf
+ *   ctx->ptr == 16: there is a full block of unprocessed data in ctx->buf
+ *
+ * Upon exit, ctx->ptr may be zero only if it was already zero on entry,
+ * and len == 0. In all other situations, ctx->ptr will be non-zero on
+ * exit (and may have value 16).
  */
 static void
 do_cbcmac_chunk(br_eax_context *ctx, const void *data, size_t len)
@@ -132,7 +139,10 @@ do_cbcmac_chunk(br_eax_context *ctx, const void *data, size_t len)
        } else {
                len -= ptr;
        }
-       (*ctx->bctx)->mac(ctx->bctx, ctx->cbcmac, ctx->buf, sizeof ctx->buf);
+       if (ctx->ptr == 16) {
+               (*ctx->bctx)->mac(ctx->bctx, ctx->cbcmac,
+                       ctx->buf, sizeof ctx->buf);
+       }
        (*ctx->bctx)->mac(ctx->bctx, ctx->cbcmac, data, len);
        memcpy(ctx->buf, (const unsigned char *)data + len, ptr);
        ctx->ptr = ptr;
@@ -159,6 +169,27 @@ br_eax_init(br_eax_context *ctx, const br_block_ctrcbc_class **bctx)
 
 /* see bearssl_aead.h */
 void
+br_eax_capture(const br_eax_context *ctx, br_eax_state *st)
+{
+       /*
+        * We capture the three OMAC* states _after_ processing the
+        * initial block (assuming that nonce, message and AAD are
+        * all non-empty).
+        */
+       int i;
+
+       memset(st->st, 0, sizeof st->st);
+       for (i = 0; i < 3; i ++) {
+               unsigned char tmp[16];
+
+               memset(tmp, 0, sizeof tmp);
+               tmp[15] = (unsigned char)i;
+               (*ctx->bctx)->mac(ctx->bctx, st->st[i], tmp, sizeof tmp);
+       }
+}
+
+/* see bearssl_aead.h */
+void
 br_eax_reset(br_eax_context *ctx, const void *nonce, size_t len)
 {
        /*
@@ -173,6 +204,62 @@ br_eax_reset(br_eax_context *ctx, const void *nonce, size_t len)
         * Start OMAC^1 for the AAD ("header" in the EAX specification).
         */
        omac_start(ctx, 1);
+
+       /*
+        * We use ctx->head[0] as temporary flag to mark that we are
+        * using a "normal" reset().
+        */
+       ctx->head[0] = 0;
+}
+
+/* see bearssl_aead.h */
+void
+br_eax_reset_pre_aad(br_eax_context *ctx, const br_eax_state *st,
+       const void *nonce, size_t len)
+{
+       if (len == 0) {
+               omac_start(ctx, 0);
+       } else {
+               memcpy(ctx->cbcmac, st->st[0], sizeof ctx->cbcmac);
+               ctx->ptr = 0;
+               do_cbcmac_chunk(ctx, nonce, len);
+       }
+       do_pad(ctx);
+       memcpy(ctx->nonce, ctx->cbcmac, sizeof ctx->cbcmac);
+
+       memcpy(ctx->cbcmac, st->st[1], sizeof ctx->cbcmac);
+       ctx->ptr = 0;
+
+       memcpy(ctx->ctr, st->st[2], sizeof ctx->ctr);
+
+       /*
+        * We use ctx->head[0] as a flag to indicate that we use a
+        * a recorded state, with ctx->ctr containing the preprocessed
+        * first block for OMAC^2.
+        */
+       ctx->head[0] = 1;
+}
+
+/* see bearssl_aead.h */
+void
+br_eax_reset_post_aad(br_eax_context *ctx, const br_eax_state *st,
+       const void *nonce, size_t len)
+{
+       if (len == 0) {
+               omac_start(ctx, 0);
+       } else {
+               memcpy(ctx->cbcmac, st->st[0], sizeof ctx->cbcmac);
+               ctx->ptr = 0;
+               do_cbcmac_chunk(ctx, nonce, len);
+       }
+       do_pad(ctx);
+       memcpy(ctx->nonce, ctx->cbcmac, sizeof ctx->cbcmac);
+       memcpy(ctx->ctr, ctx->nonce, sizeof ctx->nonce);
+
+       memcpy(ctx->head, st->st[1], sizeof ctx->head);
+
+       memcpy(ctx->cbcmac, st->st[2], sizeof ctx->cbcmac);
+       ctx->ptr = 0;
 }
 
 /* see bearssl_aead.h */
@@ -211,6 +298,15 @@ br_eax_aad_inject(br_eax_context *ctx, const void *data, size_t len)
 void
 br_eax_flip(br_eax_context *ctx)
 {
+       int from_capture;
+
+       /*
+        * ctx->head[0] may be non-zero if the context was reset with
+        * a pre-AAD captured state. In that case, ctx->ctr[] contains
+        * the state for OMAC^2 _after_ processing the first block.
+        */
+       from_capture = ctx->head[0];
+
        /*
         * Complete the OMAC computation on the AAD.
         */
@@ -219,8 +315,15 @@ br_eax_flip(br_eax_context *ctx)
 
        /*
         * Start OMAC^2 for the encrypted data.
+        * If the context was initialized from a captured state, then
+        * the OMAC^2 value is in the ctr[] array.
         */
-       omac_start(ctx, 2);
+       if (from_capture) {
+               memcpy(ctx->cbcmac, ctx->ctr, sizeof ctx->cbcmac);
+               ctx->ptr = 0;
+       } else {
+               omac_start(ctx, 2);
+       }
 
        /*
         * Initial counter value for CTR is the processed nonce.
@@ -245,7 +348,12 @@ br_eax_run(br_eax_context *ctx, int encrypt, void *data, size_t len)
        dbuf = data;
        ptr = ctx->ptr;
 
-       if (ptr != 16) {
+       /*
+        * We may have ptr == 0 here if we initialized from a captured
+        * state. In that case, there is no partially consumed block
+        * or unprocessed data.
+        */
+       if (ptr != 0 && ptr != 16) {
                /*
                 * We have a partially consumed block.
                 */
@@ -282,8 +390,12 @@ br_eax_run(br_eax_context *ctx, int encrypt, void *data, size_t len)
        /*
         * We now have a complete encrypted block in buf[] that must still
         * be processed with OMAC, and this is not the final buf.
+        * Exception: when ptr == 0, no block has been produced yet.
         */
-       (*ctx->bctx)->mac(ctx->bctx, ctx->cbcmac, ctx->buf, sizeof ctx->buf);
+       if (ptr != 0) {
+               (*ctx->bctx)->mac(ctx->bctx, ctx->cbcmac,
+                       ctx->buf, sizeof ctx->buf);
+       }
 
        /*
         * Do CTR encryption or decryption and CBC-MAC for all full blocks
index e37034c..46f208c 100644 (file)
@@ -5526,6 +5526,7 @@ test_EAX_inner(const char *name, const br_block_ctrcbc_class *vt)
                size_t plain_len, key_len, nonce_len, aad_len;
                br_aes_gen_ctrcbc_keys bc;
                br_eax_context ec;
+               br_eax_state st;
                unsigned char tmp[100], out[16];
                size_t v, tag_len;
 
@@ -5649,6 +5650,63 @@ test_EAX_inner(const char *name, const br_block_ctrcbc_class *vt)
 
                printf(".");
                fflush(stdout);
+
+               /*
+                * For capture tests, we need the message to be non-empty.
+                */
+               if (plain_len == 0) {
+                       continue;
+               }
+
+               /*
+                * Captured state, pre-AAD. This requires the AAD and the
+                * message to be non-empty.
+                */
+               br_eax_capture(&ec, &st);
+
+               if (aad_len > 0) {
+                       br_eax_reset_pre_aad(&ec, &st, nonce, nonce_len);
+                       br_eax_aad_inject(&ec, aad, aad_len);
+                       br_eax_flip(&ec);
+                       memcpy(tmp, plain, plain_len);
+                       br_eax_run(&ec, 1, tmp, plain_len);
+                       br_eax_get_tag(&ec, out);
+                       check_equals("KAT EAX 9", tmp, cipher, plain_len);
+                       check_equals("KAT EAX 10", out, tag, 16);
+
+                       br_eax_reset_pre_aad(&ec, &st, nonce, nonce_len);
+                       br_eax_aad_inject(&ec, aad, aad_len);
+                       br_eax_flip(&ec);
+                       br_eax_run(&ec, 0, tmp, plain_len);
+                       br_eax_get_tag(&ec, out);
+                       check_equals("KAT EAX 11", tmp, plain, plain_len);
+                       check_equals("KAT EAX 12", out, tag, 16);
+               }
+
+               /*
+                * Captured state, post-AAD. This requires the message to
+                * be non-empty.
+                */
+               br_eax_reset(&ec, nonce, nonce_len);
+               br_eax_aad_inject(&ec, aad, aad_len);
+               br_eax_flip(&ec);
+               br_eax_get_aad_mac(&ec, &st);
+
+               br_eax_reset_post_aad(&ec, &st, nonce, nonce_len);
+               memcpy(tmp, plain, plain_len);
+               br_eax_run(&ec, 1, tmp, plain_len);
+               br_eax_get_tag(&ec, out);
+               check_equals("KAT EAX 13", tmp, cipher, plain_len);
+               check_equals("KAT EAX 14", out, tag, 16);
+
+               br_eax_reset_post_aad(&ec, &st, nonce, nonce_len);
+               br_eax_run(&ec, 0, tmp, plain_len);
+               br_eax_get_tag(&ec, out);
+               check_equals("KAT EAX 15", tmp, plain, plain_len);
+               check_equals("KAT EAX 16", out, tag, 16);
+
+               printf(".");
+               fflush(stdout);
        }
 
        printf(" done.\n");