[PATCH 1/4] Add SIV mode (RFC 5297)

Jussi Kivilinna jussi.kivilinna at iki.fi
Fri Aug 13 17:01:26 CEST 2021


* cipher/Makefile.am: Add 'cipher-siv.c'.
* cipher/cipher-ctr.c (_gcry_cipher_ctr_encrypt): Rename to
_gcry_cipher_ctr_encrypt_ctx and add algo context parameter.
(_gcry_cipher_ctr_encrypt): New using _gcry_cipher_ctr_encrypt_ctx.
* cipher/cipher-internal.h (gcry_cipher_handle): Add 'u_mode.siv'.
(_gcry_cipher_ctr_encrypt_ctx, _gcry_cipher_siv_encrypt)
(_gcry_cipher_siv_decrypt, _gcry_cipher_siv_set_nonce)
(_gcry_cipher_siv_authenticate, _gcry_cipher_siv_set_decryption_tag)
(_gcry_cipher_siv_get_tag, _gcry_cipher_siv_check_tag)
(_gcry_cipher_siv_setkey): New.
* cipher/cipher-siv.c: New.
* cipher/cipher.c (_gcry_cipher_open_internal, cipher_setkey)
(cipher_reset, _gcry_cipher_setup_mode_ops, _gcry_cipher_info): Add
GCRY_CIPHER_MODE_SIV handling.
(_gcry_cipher_ctl): Add GCRYCTL_SET_DECRYPTION_TAG handling.
* doc/gcrypt.texi: Add documentation for SIV mode.
* src/gcrypt.h.in (GCRYCTL_SET_DECRYPTION_TAG): New.
(GCRY_CIPHER_MODE_SIV): New.
(gcry_cipher_set_decryption_tag): New.
* tests/basic.c (check_siv_cipher): New.
(check_cipher_modes): Add call for 'check_siv_cipher'.
* tests/bench-slope.c (bench_encrypt_init): Use double size key for
SIV mode.
(bench_aead_encrypt_do_bench, bench_aead_decrypt_do_bench)
(bench_aead_authenticate_do_bench): Reset cipher context on each run.
(bench_aead_authenticate_do_bench): Support nonce-less operation.
(bench_siv_encrypt_do_bench, bench_siv_decrypt_do_bench)
(bench_siv_authenticate_do_bench, siv_encrypt_ops)
(siv_decrypt_ops, siv_authenticate_ops): New.
(cipher_modes): Add SIV mode benchmarks.
(cipher_bench_one): Restrict SIV mode testing to 16 byte block-size.
--

Signed-off-by: Jussi Kivilinna <jussi.kivilinna at iki.fi>
---
 cipher/Makefile.am       |   1 +
 cipher/cipher-ctr.c      |  21 +-
 cipher/cipher-internal.h |  57 ++++++
 cipher/cipher-ocb.c      |   3 +-
 cipher/cipher-siv.c      | 377 +++++++++++++++++++++++++++++++++++
 cipher/cipher.c          |  93 ++++++++-
 doc/gcrypt.texi          |  37 +++-
 src/gcrypt.h.in          |  14 +-
 tests/basic.c            | 421 +++++++++++++++++++++++++++++++++++++++
 tests/bench-slope.c      |  78 +++++++-
 10 files changed, 1082 insertions(+), 20 deletions(-)
 create mode 100644 cipher/cipher-siv.c

diff --git a/cipher/Makefile.am b/cipher/Makefile.am
index 52a00aa9..4d3e0d19 100644
--- a/cipher/Makefile.am
+++ b/cipher/Makefile.am
@@ -53,6 +53,7 @@ libcipher_la_SOURCES = \
 	cipher-ocb.c \
 	cipher-xts.c \
 	cipher-eax.c \
+	cipher-siv.c \
 	cipher-selftest.c cipher-selftest.h \
 	pubkey.c pubkey-internal.h pubkey-util.c \
 	md.c \
diff --git a/cipher/cipher-ctr.c b/cipher/cipher-ctr.c
index 5f0afc2f..d66c5687 100644
--- a/cipher/cipher-ctr.c
+++ b/cipher/cipher-ctr.c
@@ -31,9 +31,10 @@
 
 
 gcry_err_code_t
-_gcry_cipher_ctr_encrypt (gcry_cipher_hd_t c,
-                          unsigned char *outbuf, size_t outbuflen,
-                          const unsigned char *inbuf, size_t inbuflen)
+_gcry_cipher_ctr_encrypt_ctx (gcry_cipher_hd_t c,
+			      unsigned char *outbuf, size_t outbuflen,
+			      const unsigned char *inbuf, size_t inbuflen,
+			      void *algo_context)
 {
   size_t n;
   int i;
@@ -65,7 +66,7 @@ _gcry_cipher_ctr_encrypt (gcry_cipher_hd_t c,
   nblocks = inbuflen >> blocksize_shift;
   if (nblocks && c->bulk.ctr_enc)
     {
-      c->bulk.ctr_enc (&c->context.c, c->u_ctr.ctr, outbuf, inbuf, nblocks);
+      c->bulk.ctr_enc (algo_context, c->u_ctr.ctr, outbuf, inbuf, nblocks);
       inbuf  += nblocks << blocksize_shift;
       outbuf += nblocks << blocksize_shift;
       inbuflen -= nblocks << blocksize_shift;
@@ -80,7 +81,7 @@ _gcry_cipher_ctr_encrypt (gcry_cipher_hd_t c,
       n = blocksize;
       do
         {
-          nburn = enc_fn (&c->context.c, tmp, c->u_ctr.ctr);
+          nburn = enc_fn (algo_context, tmp, c->u_ctr.ctr);
           burn = nburn > burn ? nburn : burn;
 
 	  cipher_block_add(c->u_ctr.ctr, 1, blocksize);
@@ -118,3 +119,13 @@ _gcry_cipher_ctr_encrypt (gcry_cipher_hd_t c,
 
   return 0;
 }
+
+
+gcry_err_code_t
+_gcry_cipher_ctr_encrypt (gcry_cipher_hd_t c,
+			  unsigned char *outbuf, size_t outbuflen,
+			  const unsigned char *inbuf, size_t inbuflen)
+{
+  return _gcry_cipher_ctr_encrypt_ctx (c, outbuf, outbuflen, inbuf, inbuflen,
+				       &c->context.c);
+}
diff --git a/cipher/cipher-internal.h b/cipher/cipher-internal.h
index 0e4a90fc..e9f48a2f 100644
--- a/cipher/cipher-internal.h
+++ b/cipher/cipher-internal.h
@@ -398,6 +398,31 @@ struct gcry_cipher_handle
        * cipher context. */
       char *tweak_context;
     } xts;
+
+    /* Mode specific storage for SIV mode. */
+    struct {
+      /* Tag used for decryption. */
+      unsigned char dec_tag[GCRY_SIV_BLOCK_LEN];
+
+      /* S2V state. */
+      unsigned char s2v_d[GCRY_SIV_BLOCK_LEN];
+
+      /* Number of AAD elements processed. */
+      unsigned int aad_count:8;
+
+      /* Flags for SIV state. */
+      unsigned int dec_tag_set:1;
+
+      /* --- Following members are not cleared in gcry_cipher_reset --- */
+
+      /* S2V CMAC state. */
+      gcry_cmac_context_t s2v_cmac;
+      unsigned char s2v_zero_block[GCRY_SIV_BLOCK_LEN];
+
+      /* Pointer to CTR cipher context, allocated after actual
+       * cipher context. */
+      char *ctr_context;
+    } siv;
   } u_mode;
 
   /* What follows are two contexts of the cipher in use.  The first
@@ -453,6 +478,11 @@ gcry_err_code_t _gcry_cipher_ofb_encrypt
                  const unsigned char *inbuf, size_t inbuflen);
 
 /*-- cipher-ctr.c --*/
+gcry_err_code_t _gcry_cipher_ctr_encrypt_ctx
+/*           */ (gcry_cipher_hd_t c,
+		 unsigned char *outbuf, size_t outbuflen,
+		 const unsigned char *inbuf, size_t inbuflen,
+		 void *algo_context);
 gcry_err_code_t _gcry_cipher_ctr_encrypt
 /*           */ (gcry_cipher_hd_t c,
                  unsigned char *outbuf, size_t outbuflen,
@@ -622,6 +652,33 @@ gcry_err_code_t _gcry_cipher_xts_decrypt
 		 const unsigned char *inbuf, size_t inbuflen);
 
 
+/*-- cipher-siv.c --*/
+gcry_err_code_t _gcry_cipher_siv_encrypt
+/*           */ (gcry_cipher_hd_t c,
+                 unsigned char *outbuf, size_t outbuflen,
+                 const unsigned char *inbuf, size_t inbuflen);
+gcry_err_code_t _gcry_cipher_siv_decrypt
+/*           */ (gcry_cipher_hd_t c,
+                 unsigned char *outbuf, size_t outbuflen,
+                 const unsigned char *inbuf, size_t inbuflen);
+gcry_err_code_t _gcry_cipher_siv_set_nonce
+/*           */ (gcry_cipher_hd_t c, const unsigned char *nonce,
+                 size_t noncelen);
+gcry_err_code_t _gcry_cipher_siv_authenticate
+/*           */ (gcry_cipher_hd_t c, const unsigned char *abuf, size_t abuflen);
+gcry_err_code_t _gcry_cipher_siv_set_decryption_tag
+/*           */ (gcry_cipher_hd_t c, const byte *tag, size_t taglen);
+gcry_err_code_t _gcry_cipher_siv_get_tag
+/*           */ (gcry_cipher_hd_t c,
+                 unsigned char *outtag, size_t taglen);
+gcry_err_code_t _gcry_cipher_siv_check_tag
+/*           */ (gcry_cipher_hd_t c,
+                 const unsigned char *intag, size_t taglen);
+gcry_err_code_t _gcry_cipher_siv_setkey
+/*           */ (gcry_cipher_hd_t c,
+                 const unsigned char *ctrkey, size_t ctrkeylen);
+
+
 /* Return the L-value for block N.  Note: 'cipher_ocb.c' ensures that N
  * will never be multiple of 65536 (1 << OCB_L_TABLE_SIZE), thus N can
  * be directly passed to _gcry_ctz() function and resulting index will
diff --git a/cipher/cipher-ocb.c b/cipher/cipher-ocb.c
index 24db6a9e..bfafa4c8 100644
--- a/cipher/cipher-ocb.c
+++ b/cipher/cipher-ocb.c
@@ -107,7 +107,8 @@ ocb_get_L_big (gcry_cipher_hd_t c, u64 n, unsigned char *l_buf)
 
 
 /* Called after key has been set. Sets up L table. */
-void _gcry_cipher_ocb_setkey (gcry_cipher_hd_t c)
+void
+_gcry_cipher_ocb_setkey (gcry_cipher_hd_t c)
 {
   unsigned char ktop[OCB_BLOCK_LEN];
   unsigned int burn = 0;
diff --git a/cipher/cipher-siv.c b/cipher/cipher-siv.c
new file mode 100644
index 00000000..9a71f2ef
--- /dev/null
+++ b/cipher/cipher-siv.c
@@ -0,0 +1,377 @@
+/* cipher-siv.c  -  SIV implementation
+ * Copyright (C) 2021 Jussi Kivilinna <jussi.kivilinna at iki.fi>
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser general Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "g10lib.h"
+#include "cipher.h"
+#include "bufhelp.h"
+#include "./cipher-internal.h"
+
+
+static inline void
+s2v_double (unsigned char *d)
+{
+  u64 hi, lo, mask;
+
+  hi = buf_get_be64(d + 0);
+  lo = buf_get_be64(d + 8);
+
+  mask = -(hi >> 63);
+  hi = (hi << 1) ^ (lo >> 63);
+  lo = (lo << 1) ^ (mask & 0x87);
+
+  buf_put_be64(d + 0, hi);
+  buf_put_be64(d + 8, lo);
+}
+
+
+static void
+s2v_pad (unsigned char *out, const byte *in, size_t inlen)
+{
+  static const unsigned char padding[GCRY_SIV_BLOCK_LEN] = { 0x80 };
+
+  gcry_assert(inlen < GCRY_SIV_BLOCK_LEN);
+
+  buf_cpy (out, in, inlen);
+  buf_cpy (out + inlen, padding, GCRY_SIV_BLOCK_LEN - inlen);
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_setkey (gcry_cipher_hd_t c,
+	                 const unsigned char *ctrkey, size_t ctrkeylen)
+{
+  static const unsigned char zero[GCRY_SIV_BLOCK_LEN] = { 0 };
+  gcry_err_code_t err;
+
+  if (c->spec->blocksize != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_CIPHER_ALGO;
+
+  c->u_mode.siv.aad_count = 0;
+  c->u_mode.siv.dec_tag_set = 0;
+  c->marks.tag = 0;
+  c->marks.iv = 0;
+
+  /* Set CTR mode key. */
+  err = c->spec->setkey (c->u_mode.siv.ctr_context, ctrkey, ctrkeylen,
+			 &c->bulk);
+  if (err != 0)
+    return err;
+
+  /* Initialize S2V. */
+  memset (&c->u_mode.siv.s2v_cmac, 0, sizeof(c->u_mode.siv.s2v_cmac));
+  err = _gcry_cmac_generate_subkeys (c, &c->u_mode.siv.s2v_cmac);
+  if (err != 0)
+    return err;
+
+  err = _gcry_cmac_write (c, &c->u_mode.siv.s2v_cmac, zero, GCRY_SIV_BLOCK_LEN);
+  if (err != 0)
+    return err;
+
+  err = _gcry_cmac_final (c, &c->u_mode.siv.s2v_cmac);
+  if (err != 0)
+    return err;
+
+  memcpy (c->u_mode.siv.s2v_zero_block, c->u_mode.siv.s2v_cmac.u_iv.iv,
+	  GCRY_SIV_BLOCK_LEN);
+  memcpy (c->u_mode.siv.s2v_d, c->u_mode.siv.s2v_zero_block,
+	  GCRY_SIV_BLOCK_LEN);
+  if (err != 0)
+    return err;
+
+  return 0;
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_authenticate (gcry_cipher_hd_t c,
+                               const byte *aadbuf, size_t aadbuflen)
+{
+  gcry_err_code_t err;
+
+  if (c->spec->blocksize != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_CIPHER_ALGO;
+  if (c->marks.tag)
+    return GPG_ERR_INV_STATE;
+  if (c->marks.iv)
+    return GPG_ERR_INV_STATE;
+
+  if (c->u_mode.siv.aad_count >= 126)
+    return GPG_ERR_INV_STATE; /* Too many AD vector components. */
+
+  c->u_mode.siv.aad_count++;
+
+  _gcry_cmac_reset (&c->u_mode.siv.s2v_cmac);
+
+  err = _gcry_cmac_write (c, &c->u_mode.siv.s2v_cmac, aadbuf, aadbuflen);
+  if (err != 0)
+    return err;
+
+  err = _gcry_cmac_final (c, &c->u_mode.siv.s2v_cmac);
+  if (err != 0)
+    return err;
+
+  s2v_double (c->u_mode.siv.s2v_d);
+  cipher_block_xor_1 (c->u_mode.siv.s2v_d, c->u_mode.siv.s2v_cmac.u_iv.iv,
+		      GCRY_SIV_BLOCK_LEN);
+
+  return 0;
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_set_nonce (gcry_cipher_hd_t c, const byte *nonce,
+			    size_t noncelen)
+{
+  gcry_err_code_t err;
+
+  err = _gcry_cipher_siv_authenticate (c, nonce, noncelen);
+  if (err)
+    return err;
+
+  /* Nonce is the last AD before plaintext. */
+  c->marks.iv = 1;
+
+  return 0;
+}
+
+
+static gcry_err_code_t
+s2v_plaintext (gcry_cipher_hd_t c, const byte *plain, size_t plainlen)
+{
+  gcry_err_code_t err;
+
+  if (c->u_mode.siv.aad_count >= 127)
+    return GPG_ERR_INV_STATE; /* Too many AD vector components. */
+
+  _gcry_cmac_reset (&c->u_mode.siv.s2v_cmac);
+
+  if (plainlen >= GCRY_SIV_BLOCK_LEN)
+    {
+      err = _gcry_cmac_write (c, &c->u_mode.siv.s2v_cmac, plain,
+			      plainlen - GCRY_SIV_BLOCK_LEN);
+      if (err)
+        return err;
+
+      cipher_block_xor_1 (c->u_mode.siv.s2v_d,
+			  plain + plainlen - GCRY_SIV_BLOCK_LEN,
+			  GCRY_SIV_BLOCK_LEN);
+
+      err = _gcry_cmac_write (c, &c->u_mode.siv.s2v_cmac, c->u_mode.siv.s2v_d,
+			      GCRY_SIV_BLOCK_LEN);
+      if (err)
+        return err;
+    }
+  else
+    {
+      unsigned char pad_sn[GCRY_SIV_BLOCK_LEN];
+
+      s2v_double (c->u_mode.siv.s2v_d);
+      s2v_pad (pad_sn, plain, plainlen);
+      cipher_block_xor_1 (pad_sn, c->u_mode.siv.s2v_d, GCRY_SIV_BLOCK_LEN);
+
+      err = _gcry_cmac_write (c, &c->u_mode.siv.s2v_cmac, pad_sn,
+			      GCRY_SIV_BLOCK_LEN);
+      wipememory (pad_sn, sizeof(pad_sn));
+      if (err)
+        return err;
+    }
+
+  c->u_mode.siv.aad_count++;
+
+  return _gcry_cmac_final (c, &c->u_mode.siv.s2v_cmac);
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_encrypt (gcry_cipher_hd_t c,
+                          byte *outbuf, size_t outbuflen,
+                          const byte *inbuf, size_t inbuflen)
+{
+  gcry_err_code_t err;
+  u64 q_lo;
+
+  if (c->spec->blocksize != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_CIPHER_ALGO;
+  if (outbuflen < inbuflen)
+    return GPG_ERR_BUFFER_TOO_SHORT;
+  if (c->marks.tag)
+    return GPG_ERR_INV_STATE;
+  if (c->u_mode.siv.dec_tag_set)
+    return GPG_ERR_INV_STATE;
+
+  /* Pass plaintext to S2V. */
+  err = s2v_plaintext (c, inbuf, inbuflen);
+  if (err != 0)
+    return err;
+
+  /* Clear 31th and 63th bits. */
+  memcpy (c->u_ctr.ctr, c->u_mode.siv.s2v_cmac.u_iv.iv, GCRY_SIV_BLOCK_LEN);
+  q_lo = buf_get_be64(c->u_ctr.ctr + 8);
+  q_lo &= ~((u64)1 << 31);
+  q_lo &= ~((u64)1 << 63);
+  buf_put_be64(c->u_ctr.ctr + 8, q_lo);
+
+  /* Encrypt plaintext. */
+  err = _gcry_cipher_ctr_encrypt_ctx(c, outbuf, outbuflen, inbuf, inbuflen,
+				     c->u_mode.siv.ctr_context);
+  if (err != 0)
+    return err;
+
+  c->marks.tag = 1;
+
+  return 0;
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_set_decryption_tag (gcry_cipher_hd_t c,
+				     const byte *tag, size_t taglen)
+{
+  if (taglen != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_INV_ARG;
+  if (c->spec->blocksize != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_CIPHER_ALGO;
+  if (c->marks.tag)
+    return GPG_ERR_INV_STATE;
+
+  memcpy (&c->u_mode.siv.dec_tag, tag, GCRY_SIV_BLOCK_LEN);
+  c->u_mode.siv.dec_tag_set = 1;
+
+  return 0;
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_decrypt (gcry_cipher_hd_t c,
+                          byte *outbuf, size_t outbuflen,
+                          const byte *inbuf, size_t inbuflen)
+{
+  gcry_err_code_t err;
+  u64 q_lo;
+
+  if (c->spec->blocksize != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_CIPHER_ALGO;
+  if (outbuflen < inbuflen)
+    return GPG_ERR_BUFFER_TOO_SHORT;
+  if (c->marks.tag)
+    return GPG_ERR_INV_STATE;
+  if (!c->u_mode.siv.dec_tag_set)
+    return GPG_ERR_INV_STATE;
+
+  /* Clear 31th and 63th bits. */
+  memcpy (c->u_ctr.ctr, c->u_mode.siv.dec_tag, GCRY_SIV_BLOCK_LEN);
+  q_lo = buf_get_be64(c->u_ctr.ctr + 8);
+  q_lo &= ~((u64)1 << 31);
+  q_lo &= ~((u64)1 << 63);
+  buf_put_be64(c->u_ctr.ctr + 8, q_lo);
+
+  /* Decrypt ciphertext. */
+  err = _gcry_cipher_ctr_encrypt_ctx(c, outbuf, outbuflen, inbuf, inbuflen,
+				     c->u_mode.siv.ctr_context);
+  if (err != 0)
+    return err;
+
+  /* Pass plaintext to S2V. */
+  err = s2v_plaintext (c, outbuf, inbuflen);
+  if (err != 0)
+    return err;
+
+  c->marks.tag = 1;
+
+  if (!buf_eq_const(c->u_mode.siv.s2v_cmac.u_iv.iv, c->u_mode.siv.dec_tag,
+		    GCRY_SIV_BLOCK_LEN))
+    {
+      wipememory (outbuf, inbuflen);
+      return GPG_ERR_CHECKSUM;
+    }
+
+  return 0;
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_get_tag (gcry_cipher_hd_t c, unsigned char *outbuf,
+                          size_t outbuflen)
+{
+  gcry_err_code_t err;
+
+  if (c->spec->blocksize != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_CIPHER_ALGO;
+  if (c->u_mode.siv.dec_tag_set)
+    return GPG_ERR_INV_STATE;
+
+  if (!c->marks.tag)
+    {
+      /* Finalize SIV with zero-length plaintext. */
+      err = s2v_plaintext (c, NULL, 0);
+      if (err != 0)
+        return err;
+
+      c->marks.tag = 1;
+    }
+
+  if (outbuflen > GCRY_SIV_BLOCK_LEN)
+    outbuflen = GCRY_SIV_BLOCK_LEN;
+
+  /* We already checked that OUTBUF is large enough to hold
+   * the result or has valid truncated length.  */
+  memcpy (outbuf, c->u_mode.siv.s2v_cmac.u_iv.iv, outbuflen);
+
+  return 0;
+}
+
+
+gcry_err_code_t
+_gcry_cipher_siv_check_tag (gcry_cipher_hd_t c, const unsigned char *intag,
+                            size_t taglen)
+{
+  gcry_err_code_t err;
+  size_t n;
+
+  if (c->spec->blocksize != GCRY_SIV_BLOCK_LEN)
+    return GPG_ERR_CIPHER_ALGO;
+
+  if (!c->marks.tag)
+    {
+      /* Finalize SIV with zero-length plaintext. */
+      err = s2v_plaintext (c, NULL, 0);
+      if (err != 0)
+        return err;
+
+      c->marks.tag = 1;
+    }
+
+  n = GCRY_SIV_BLOCK_LEN;
+  if (taglen < n)
+    n = taglen;
+
+  if (!buf_eq_const(c->u_mode.siv.s2v_cmac.u_iv.iv, intag, n)
+      || GCRY_SIV_BLOCK_LEN != taglen)
+    {
+      return GPG_ERR_CHECKSUM;
+    }
+
+  return 0;
+}
diff --git a/cipher/cipher.c b/cipher/cipher.c
index 1039dff7..a274466f 100644
--- a/cipher/cipher.c
+++ b/cipher/cipher.c
@@ -550,6 +550,7 @@ _gcry_cipher_open_internal (gcry_cipher_hd_t *handle,
       case GCRY_CIPHER_MODE_CMAC:
       case GCRY_CIPHER_MODE_EAX:
       case GCRY_CIPHER_MODE_GCM:
+      case GCRY_CIPHER_MODE_SIV:
 	if (!spec->encrypt || !spec->decrypt)
 	  err = GPG_ERR_INV_CIPHER_MODE;
 	break;
@@ -609,6 +610,7 @@ _gcry_cipher_open_internal (gcry_cipher_hd_t *handle,
       switch (mode)
 	{
 	case GCRY_CIPHER_MODE_XTS:
+	case GCRY_CIPHER_MODE_SIV:
 	  /* Additional cipher context for tweak. */
 	  size += 2 * spec->contextsize + 15;
 	  break;
@@ -661,7 +663,12 @@ _gcry_cipher_open_internal (gcry_cipher_hd_t *handle,
 	      tc = h->context.c + spec->contextsize * 2;
 	      tc += (16 - (uintptr_t)tc % 16) % 16;
 	      h->u_mode.xts.tweak_context = tc;
+	      break;
 
+	    case GCRY_CIPHER_MODE_SIV:
+	      tc = h->context.c + spec->contextsize * 2;
+	      tc += (16 - (uintptr_t)tc % 16) % 16;
+	      h->u_mode.siv.ctr_context = tc;
 	      break;
 
             default:
@@ -731,6 +738,13 @@ cipher_setkey (gcry_cipher_hd_t c, byte *key, size_t keylen)
 	    return GPG_ERR_WEAK_KEY;
 	}
     }
+  else if (c->mode == GCRY_CIPHER_MODE_SIV)
+    {
+      /* SIV uses two keys. */
+      if (keylen % 2)
+	return GPG_ERR_INV_KEYLEN;
+      keylen /= 2;
+    }
 
   rc = c->spec->setkey (&c->context.c, key, keylen, &c->bulk);
   if (!rc || (c->marks.allow_weak_key && rc == GPG_ERR_WEAK_KEY))
@@ -777,9 +791,22 @@ cipher_setkey (gcry_cipher_hd_t c, byte *key, size_t keylen)
 	    c->marks.key = 0;
 	  break;
 
+        case GCRY_CIPHER_MODE_SIV:
+	  /* Setup CTR cipher with second part of SIV key. */
+          rc = _gcry_cipher_siv_setkey (c, key + keylen, keylen);
+	  if (!rc || (c->marks.allow_weak_key && rc == GPG_ERR_WEAK_KEY))
+	    {
+	      /* Duplicate initial CTR context.  */
+	      memcpy (c->u_mode.siv.ctr_context + c->spec->contextsize,
+		      c->u_mode.siv.ctr_context, c->spec->contextsize);
+	    }
+	  else
+	    c->marks.key = 0;
+          break;
+
         default:
           break;
-        };
+        }
     }
   else
     c->marks.key = 0;
@@ -876,14 +903,25 @@ cipher_reset (gcry_cipher_hd_t c)
       break;
 
     case GCRY_CIPHER_MODE_OCB:
-      /* Do not clear precalculated L-values */
       {
+	const size_t table_maxblks = 1 << OCB_L_TABLE_SIZE;
 	byte *u_mode_head_pos = (void *)&c->u_mode.ocb;
 	byte *u_mode_tail_pos = (void *)&c->u_mode.ocb.tag;
 	size_t u_mode_head_length = u_mode_tail_pos - u_mode_head_pos;
 	size_t u_mode_tail_length = sizeof(c->u_mode.ocb) - u_mode_head_length;
 
-	memset (u_mode_tail_pos, 0, u_mode_tail_length);
+	if (c->u_mode.ocb.aad_nblocks < table_maxblks)
+	  {
+	    /* Precalculated L-values are still ok after reset, no need
+	     * to clear. */
+	    memset (u_mode_tail_pos, 0, u_mode_tail_length);
+	  }
+	else
+	  {
+	    /* Reinitialize L table. */
+	    memset (&c->u_mode.ocb, 0, sizeof(c->u_mode.ocb));
+	    _gcry_cipher_ocb_setkey (c);
+	  }
 
 	/* Setup default taglen.  */
 	c->u_mode.ocb.taglen = 16;
@@ -896,6 +934,24 @@ cipher_reset (gcry_cipher_hd_t c)
 	      c->spec->contextsize);
       break;
 
+    case GCRY_CIPHER_MODE_SIV:
+      /* Only clear head of u_mode, keep s2v_cmac and ctr_context. */
+      {
+        byte *u_mode_pos = (void *)&c->u_mode;
+        byte *tail_pos = (void *)&c->u_mode.siv.s2v_cmac;
+        size_t u_mode_head_length = tail_pos - u_mode_pos;
+
+        memset (&c->u_mode, 0, u_mode_head_length);
+
+	memcpy (c->u_mode.siv.ctr_context,
+		c->u_mode.siv.ctr_context + c->spec->contextsize,
+		c->spec->contextsize);
+
+	memcpy (c->u_mode.siv.s2v_d, c->u_mode.siv.s2v_zero_block,
+		GCRY_SIV_BLOCK_LEN);
+      }
+      break;
+
     default:
       break; /* u_mode unused by other modes. */
     }
@@ -1314,6 +1370,11 @@ _gcry_cipher_setup_mode_ops(gcry_cipher_hd_t c, int mode)
       c->mode_ops.decrypt = _gcry_cipher_xts_decrypt;
       break;
 
+    case GCRY_CIPHER_MODE_SIV:
+      c->mode_ops.encrypt = _gcry_cipher_siv_encrypt;
+      c->mode_ops.decrypt = _gcry_cipher_siv_decrypt;
+      break;
+
     default:
       c->mode_ops.encrypt = do_encrypt_none_unknown;
       c->mode_ops.decrypt = do_decrypt_none_unknown;
@@ -1343,6 +1404,10 @@ _gcry_cipher_setup_mode_ops(gcry_cipher_hd_t c, int mode)
       c->mode_ops.setiv = _gcry_cipher_ocb_set_nonce;
       break;
 
+    case GCRY_CIPHER_MODE_SIV:
+      c->mode_ops.setiv = _gcry_cipher_siv_set_nonce;
+      break;
+
     default:
       c->mode_ops.setiv = cipher_setiv;
       break;
@@ -1388,6 +1453,12 @@ _gcry_cipher_setup_mode_ops(gcry_cipher_hd_t c, int mode)
       c->mode_ops.check_tag    = _gcry_cipher_ocb_check_tag;
       break;
 
+    case GCRY_CIPHER_MODE_SIV:
+      c->mode_ops.authenticate = _gcry_cipher_siv_authenticate;
+      c->mode_ops.get_tag      = _gcry_cipher_siv_get_tag;
+      c->mode_ops.check_tag    = _gcry_cipher_siv_check_tag;
+      break;
+
     default:
       c->mode_ops.authenticate = NULL;
       c->mode_ops.get_tag      = NULL;
@@ -1462,6 +1533,18 @@ _gcry_cipher_ctl (gcry_cipher_hd_t h, int cmd, void *buffer, size_t buflen)
       }
       break;
 
+    case GCRYCTL_SET_DECRYPTION_TAG:
+      {
+        if (!buffer)
+          return GPG_ERR_INV_ARG;
+
+        if (h->mode == GCRY_CIPHER_MODE_SIV)
+          rc = _gcry_cipher_siv_set_decryption_tag (h, buffer, buflen);
+        else
+          rc = GPG_ERR_INV_CIPHER_MODE;
+      }
+      break;
+
     case GCRYCTL_SET_TAGLEN:
       if (!h || !buffer || buflen != sizeof(int) )
 	return GPG_ERR_INV_ARG;
@@ -1595,6 +1678,10 @@ _gcry_cipher_info (gcry_cipher_hd_t h, int cmd, void *buffer, size_t *nbytes)
               *nbytes = POLY1305_TAGLEN;
               break;
 
+            case GCRY_CIPHER_MODE_SIV:
+              *nbytes = GCRY_SIV_BLOCK_LEN;
+              break;
+
             default:
               rc = GPG_ERR_INV_CIPHER_MODE;
               break;
diff --git a/doc/gcrypt.texi b/doc/gcrypt.texi
index 148a5fa2..e5c4b64e 100644
--- a/doc/gcrypt.texi
+++ b/doc/gcrypt.texi
@@ -1760,6 +1760,28 @@ EAX is an Authenticated Encryption with Associated Data (AEAD) block cipher
 mode by Bellare, Rogaway, and Wagner (see
 @uref{http://web.cs.ucdavis.edu/~rogaway/papers/eax.html}).
 
+ at item  GCRY_CIPHER_MODE_SIV
+ at cindex SIV, SIV mode
+Synthetic Initialization Vector (SIV) is an Authenticated Encryption
+with Associated Data (AEAD) block cipher mode, which is specified in
+RFC-5297.  This mode works with block ciphers with block size of 128
+bits and uses tag length of 128 bits.  Depending on how it is used,
+SIV achieves either the goal of deterministic authenticated encryption
+or the goal of nonce-based, misuse-resistant authenticated encryption.
+
+The SIV mode requires doubling key-length, for example, using 512-bit
+key with AES-256 (@code{GCRY_CIPHER_AES256}). Multiple AD instances can
+be passed to SIV mode with separate calls to
+ at code{gcry_cipher_authenticate}. Nonce may be passed either through
+ at code{gcry_cipher_setiv} or in the last call to
+ at code{gcry_cipher_authenticate}. Note that use of @code{gcry_cipher_setiv}
+blocks any further calls to @code{gcry_cipher_authenticate} as nonce needs
+to be the last AD element with the SIV mode. When encrypting or decrypting,
+full-sized plaintext or ciphertext needs to be passed to
+ at code{gcry_cipher_encrypt} or @code{gcry_cipher_decrypt}. Decryption tag
+needs to be given to SIV mode before decryption using
+ at code{gcry_cipher_set_decryption_tag}.
+
 @end table
 
 @node Working with cipher handles
@@ -1794,8 +1816,9 @@ ChaCha20 stream cipher. The block cipher modes
 @code{GCRY_CIPHER_MODE_CTR} and @code{GCRY_CIPHER_MODE_EAX}) will work
 with any block cipher algorithm.  GCM mode
 (@code{GCRY_CIPHER_MODE_GCM}), CCM mode (@code{GCRY_CIPHER_MODE_CCM}),
-OCB mode (@code{GCRY_CIPHER_MODE_OCB}), and XTS mode
-(@code{GCRY_CIPHER_MODE_XTS}) will only work with block cipher
+OCB mode (@code{GCRY_CIPHER_MODE_OCB}), XTS mode
+(@code{GCRY_CIPHER_MODE_XTS}) and SIV mode
+(@code{GCRY_CIPHER_MODE_SIV}) will only work with block cipher
 algorithms which have the block size of 16 bytes.
 
 The third argument @var{flags} can either be passed as @code{0} or as
@@ -1988,6 +2011,16 @@ implemented as a macro.
 @end deftypefun
 
 
+The SIV mode requires decryption tag to be input before decryption.
+This is done with:
+
+ at deftypefun gcry_error_t gcry_cipher_set_decryption_tag (gcry_cipher_hd_t @var{h}, const void *@var{tag}, size_t @var{taglen})
+
+Set decryption tag for the SIV mode decryption.  This is implemented
+as a macro.
+ at end deftypefun
+
+
 OpenPGP (as defined in RFC-4880) requires a special sync operation in
 some places.  The following function is used for this:
 
diff --git a/src/gcrypt.h.in b/src/gcrypt.h.in
index 882f4387..99b21276 100644
--- a/src/gcrypt.h.in
+++ b/src/gcrypt.h.in
@@ -334,7 +334,8 @@ enum gcry_ctl_cmds
     GCRYCTL_GET_TAGLEN = 76,
     GCRYCTL_REINIT_SYSCALL_CLAMP = 77,
     GCRYCTL_AUTO_EXPAND_SECMEM = 78,
-    GCRYCTL_SET_ALLOW_WEAK_KEY = 79
+    GCRYCTL_SET_ALLOW_WEAK_KEY = 79,
+    GCRYCTL_SET_DECRYPTION_TAG = 80
   };
 
 /* Perform various operations defined by CMD. */
@@ -975,7 +976,8 @@ enum gcry_cipher_modes
     GCRY_CIPHER_MODE_OCB      = 11,  /* OCB3 mode.  */
     GCRY_CIPHER_MODE_CFB8     = 12,  /* Cipher feedback (8 bit mode). */
     GCRY_CIPHER_MODE_XTS      = 13,  /* XTS mode.  */
-    GCRY_CIPHER_MODE_EAX      = 14   /* EAX mode.  */
+    GCRY_CIPHER_MODE_EAX      = 14,  /* EAX mode.  */
+    GCRY_CIPHER_MODE_SIV      = 15   /* SIV mode.  */
   };
 
 /* Flags used with the open function. */
@@ -999,6 +1001,9 @@ enum gcry_cipher_flags
 /* XTS works only with blocks of 128 bits.  */
 #define GCRY_XTS_BLOCK_LEN  (128 / 8)
 
+/* SIV works only with blocks of 128 bits */
+#define GCRY_SIV_BLOCK_LEN  (128 / 8)
+
 /* Create a handle for algorithm ALGO to be used in MODE.  FLAGS may
    be given as an bitwise OR of the gcry_cipher_flags values. */
 gcry_error_t gcry_cipher_open (gcry_cipher_hd_t *handle,
@@ -1101,6 +1106,11 @@ size_t gcry_cipher_get_algo_blklen (int algo);
 #define gcry_cipher_test_algo(a) \
             gcry_cipher_algo_info( (a), GCRYCTL_TEST_ALGO, NULL, NULL )
 
+/* Setup tag for decryption (for SIV mode). */
+#define gcry_cipher_set_decryption_tag(a, tag, taglen) \
+            gcry_cipher_ctl ((a), GCRYCTL_SET_DECRYPTION_TAG, \
+                             (void *)(tag), (taglen))
+
 
 /************************************
  *                                  *
diff --git a/tests/basic.c b/tests/basic.c
index 8d29c14e..989a5aca 100644
--- a/tests/basic.c
+++ b/tests/basic.c
@@ -4800,6 +4800,426 @@ check_eax_cipher (void)
 }
 
 
+static void
+check_siv_cipher (void)
+{
+  static const struct tv
+  {
+    int algo;
+    char key[MAX_DATA_LEN];
+    char ad1[MAX_DATA_LEN];
+    int ad1len;
+    char ad2[MAX_DATA_LEN];
+    int ad2len;
+    char nonce[MAX_DATA_LEN];
+    int noncelen;
+    unsigned char plaintext[MAX_DATA_LEN];
+    int inlen;
+    char tag[MAX_DATA_LEN];
+    char out[MAX_DATA_LEN];
+  } tv[] =
+    {
+      /* Test vectors from RFC5297 */
+      {
+	GCRY_CIPHER_AES128,
+	"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf0"
+	"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+	"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+	"\x20\x21\x22\x23\x24\x25\x26\x27",
+	24,
+	"",
+	-1,
+	"",
+	-1,
+	"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee",
+	14,
+	"\x85\x63\x2d\x07\xc6\xe8\xf3\x7f\x95\x0a\xcd\x32\x0a\x2e\xcc\x93",
+	"\x40\xc0\x2b\x96\x90\xc4\xdc\x04\xda\xef\x7f\x6a\xfe\x5c"
+      },
+      {
+	GCRY_CIPHER_AES128,
+	"\x7f\x7e\x7d\x7c\x7b\x7a\x79\x78\x77\x76\x75\x74\x73\x72\x71\x70"
+	"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
+	"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff"
+	"\xde\xad\xda\xda\xde\xad\xda\xda\xff\xee\xdd\xcc\xbb\xaa\x99\x88"
+	"\x77\x66\x55\x44\x33\x22\x11\x00",
+	40,
+	"\x10\x20\x30\x40\x50\x60\x70\x80\x90\xa0",
+	10,
+	"\x09\xf9\x11\x02\x9d\x74\xe3\x5b\xd8\x41\x56\xc5\x63\x56\x88\xc0",
+	16,
+	"\x74\x68\x69\x73\x20\x69\x73\x20\x73\x6f\x6d\x65\x20\x70\x6c\x61"
+	"\x69\x6e\x74\x65\x78\x74\x20\x74\x6f\x20\x65\x6e\x63\x72\x79\x70"
+	"\x74\x20\x75\x73\x69\x6e\x67\x20\x53\x49\x56\x2d\x41\x45\x53",
+	47,
+	"\x7b\xdb\x6e\x3b\x43\x26\x67\xeb\x06\xf4\xd1\x4b\xff\x2f\xbd\x0f",
+	"\xcb\x90\x0f\x2f\xdd\xbe\x40\x43\x26\x60\x19\x65\xc8\x89\xbf\x17"
+	"\xdb\xa7\x7c\xeb\x09\x4f\xa6\x63\xb7\xa3\xf7\x48\xba\x8a\xf8\x29"
+	"\xea\x64\xad\x54\x4a\x27\x2e\x9c\x48\x5b\x62\xa3\xfd\x5c\x0d"
+      },
+      /* From libaes_siv */
+      {
+	GCRY_CIPHER_AES256,
+	"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf0"
+	"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
+	"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
+	"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf0",
+	"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+	"\x20\x21\x22\x23\x24\x25\x26\x27",
+	24,
+	"",
+	-1,
+	"",
+	-1,
+	"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee",
+	14,
+	"\x72\x4d\xfb\x2e\xaf\x94\xdb\xb1\x9b\x0b\xa3\xa2\x99\xa0\x80\x1e",
+	"\xf3\xb0\x5a\x55\x49\x8e\xc2\x55\x26\x90\xb8\x98\x10\xe4"
+      },
+      /* From https://github.com/cryptomator/siv-mode */
+      { GCRY_CIPHER_AES128,
+	"\x90\xe5\x90\xae\xca\x19\x70\xed\xd1\x9f\xe5\x0f\xa6\x91\xae\x12"
+	"\x34\x2c\x49\x7a\x22\xc2\x4f\xaa\x9e\x87\x19\x2e\x34\x00\xfb\xce",
+	"\x2d\xdf\x87\xac\x97\x5d\x0c",
+	7,
+	"",
+	-1,
+	"",
+	-1,
+	"\x44",
+	1,
+	"\x7b\x0d\xdd\x88\x74\x39\x43\xc6\x44\xc1\xd1\xa2\x18\xa3\x1e\xdf",
+	"\x2e"
+      },
+      {
+	GCRY_CIPHER_AES128,
+	"\xf6\xde\x98\x19\x31\x1b\xd3\xde\x0b\xd1\x98\x70\x9d\xea\x9f\xdf"
+	"\xb8\x2e\x80\x44\xe4\x00\x13\x2a\x90\xff\xe9\xa9\xde\x81\x44\x75",
+	"\x7b\xd3\x6f\x24\x09\xfc\xd0\x0f\x5c\xcd\x9a\xf2\xe3\xf5\x76\x45"
+	"\xf7\xc5\x3f\x39\xf7\xad\xcb\xf0\x7a\x0e\x43\x30\x7e\x55\xa2\x53"
+	"\x47\x49\x48\x20\x20\x27\x6c\x8a\x20\x44\x22\xcd\x26\xbf\x7e\x89"
+	"\x88\x38\x0d\x94\xff\x12\xc5\x18\xfd\x20\x2c\x2a\x1b\x00\xb3",
+	63,
+	"",
+	-1,
+	"",
+	-1,
+	"",
+	0,
+	"\x4c\x0e\xc2\xcc\x61\x59\xb1\x17\xdb\x98\x6d\x9a\xa5\xb4\xa0\x11",
+	""
+      },
+      /* From https://github.com/RustCrypto/AEADs */
+      {
+	GCRY_CIPHER_AES128,
+	"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf0"
+	"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+	"",
+	-1,
+	"",
+	-1,
+	"",
+	-1,
+	"",
+	0,
+	"\xf2\x00\x7a\x5b\xeb\x2b\x89\x00\xc5\x88\xa7\xad\xf5\x99\xf1\x72",
+	""
+      },
+      {
+	GCRY_CIPHER_AES128,
+	"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf0"
+	"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+	"",
+	-1,
+	"",
+	-1,
+	"",
+	-1,
+	"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff",
+	16,
+	"\xf3\x04\xf9\x12\x86\x3e\x30\x3d\x5b\x54\x0e\x50\x57\xc7\x01\x0c",
+	"\x94\x2f\xfa\xf4\x5b\x0e\x5c\xa5\xfb\x9a\x56\xa5\x26\x3b\xb0\x65"
+      },
+      /* From nettle */
+      {
+	GCRY_CIPHER_AES128,
+	"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf0"
+	"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+	"",
+	0,
+	"",
+	-1,
+	"\x01",
+	1,
+	"",
+	0,
+	"\xc6\x96\xf8\x4f\xdf\x92\xab\xa3\xc3\x1c\x23\xd5\xf2\x08\x75\x13",
+	""
+      },
+      /* From botan */
+      {
+	GCRY_CIPHER_AES128,
+	"\x2a\x83\xf6\x10\xa1\xd1\x77\xec\x2e\x00\x89\x80\xdc\x02\xa6\x6e"
+	"\xeb\x75\xaf\x6c\xba\x44\xa4\xe0\x9f\x3d\x93\xea\x1f\xa2\x88\x67",
+	"",
+	0,
+	"",
+	-1,
+	"",
+	-1,
+	"",
+	0,
+	"\x6b\xc5\xca\x86\x32\x29\x66\x75\x18\xa9\xab\xbd\x5a\xe6\xc1\xd5",
+	""
+      },
+      {
+	GCRY_CIPHER_AES128,
+	"\x97\xef\x57\xd4\xe2\xe9\x2f\x14\xdf\x73\x31\xfb\xa3\xd9\xf3\x58"
+	"\x87\xdd\xe7\xad\x86\x91\xfb\x80\x17\x68\x58\xd6\x59\x20\x14\x27",
+	"",
+	0,
+	"",
+	-1,
+	"",
+	-1,
+	"\x75\x73\x97\x4d\x6f\xa7\x65\xbc\xd0\xe6\x23\x2c\x24\x0e\x82\x7e",
+	16,
+	"\x68\x60\xa9\xc7\xbf\x4a\x6b\x21\x92\x44\xd7\xa8\xea\xa1\xf5\x0c",
+	"\x6f\x97\x93\x82\xcd\xe6\x8d\xe6\x0a\xb2\xad\x09\x53\x60\x64\x85"
+      }
+    };
+
+  gcry_cipher_hd_t hde, hdd;
+  unsigned char out[MAX_DATA_LEN];
+  unsigned char tag[16];
+  int i, keylen;
+  gcry_error_t err = 0;
+  size_t taglen2;
+
+  if (verbose)
+    fprintf (stderr, "  Starting SIV checks.\n");
+
+  for (i = 0; i < sizeof (tv) / sizeof (tv[0]); i++)
+    {
+      if (gcry_cipher_test_algo (tv[i].algo) && in_fips_mode)
+	{
+	  if (verbose)
+	    fprintf (stderr, "  algorithm %d not available in fips mode\n",
+		     tv[i].algo);
+	  continue;
+	}
+
+      if (verbose)
+	fprintf (stderr, "    checking SIV mode for %s [%i]\n",
+		 gcry_cipher_algo_name (tv[i].algo),
+		 tv[i].algo);
+      err = gcry_cipher_open (&hde, tv[i].algo, GCRY_CIPHER_MODE_SIV, 0);
+      if (!err)
+	err = gcry_cipher_open (&hdd, tv[i].algo, GCRY_CIPHER_MODE_SIV, 0);
+      if (err)
+	{
+	  fail ("aes-siv, gcry_cipher_open failed: %s\n", gpg_strerror (err));
+	  return;
+	}
+
+      keylen = gcry_cipher_get_algo_keylen (tv[i].algo) * 2;
+      if (!keylen)
+	{
+	  fail ("aes-siv, gcry_cipher_get_algo_keylen failed\n");
+	  return;
+	}
+
+      err = gcry_cipher_setkey (hde, tv[i].key, keylen);
+      if (!err)
+	err = gcry_cipher_setkey (hdd, tv[i].key, keylen);
+      if (err)
+	{
+	  fail ("aes-siv, gcry_cipher_setkey failed: %s\n",
+		gpg_strerror (err));
+	  gcry_cipher_close (hde);
+	  gcry_cipher_close (hdd);
+	  return;
+	}
+
+      if (tv[i].ad1len >= 0)
+	{
+	  err = gcry_cipher_authenticate (hde, tv[i].ad1, tv[i].ad1len);
+	  if (!err)
+	    err = gcry_cipher_authenticate (hdd, tv[i].ad1, tv[i].ad1len);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_authenticate failed: %s\n",
+		    gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+	}
+
+      if (tv[i].ad2len >= 0)
+	{
+	  err = gcry_cipher_authenticate (hde, tv[i].ad2, tv[i].ad2len);
+	  if (!err)
+	    err = gcry_cipher_authenticate (hdd, tv[i].ad2, tv[i].ad2len);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_authenticate failed: %s\n",
+		    gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+	}
+
+      if (tv[i].noncelen >= 0)
+	{
+	  err = gcry_cipher_setiv (hde, tv[i].nonce, tv[i].noncelen);
+	  if (!err)
+	    err = gcry_cipher_setiv (hdd, tv[i].nonce, tv[i].noncelen);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_setiv failed: %s\n",
+		    gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+
+	  /* Further AD not allowed after setting nonce. */
+	  err = gcry_cipher_authenticate (hde, tv[i].nonce, tv[i].noncelen);
+	  if (!err)
+	    {
+	      fail ("aes-siv, gcry_cipher_authenticate after setiv did not fail\n");
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+	}
+
+      err = gcry_cipher_info (hde, GCRYCTL_GET_TAGLEN, NULL, &taglen2);
+      if (err)
+	{
+	  fail ("cipher-siv, gcryctl_get_taglen failed (tv %d): %s\n",
+		i, gpg_strerror (err));
+	  gcry_cipher_close (hde);
+	  gcry_cipher_close (hdd);
+	  return;
+	}
+      if (taglen2 != 16)
+	{
+	  fail ("cipher-siv, gcryctl_get_taglen returned bad length"
+		" (tv %d): got=%zu want=%d\n",
+		i, taglen2, 16);
+	  gcry_cipher_close (hde);
+	  gcry_cipher_close (hdd);
+	  return;
+	}
+
+      if (tv[i].inlen)
+	{
+	  err = gcry_cipher_encrypt (hde, out, tv[i].inlen,
+				     tv[i].plaintext, tv[i].inlen);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_encrypt (%d) failed: %s\n",
+		    i, gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+
+	  if (memcmp (tv[i].out, out, tv[i].inlen))
+	    {
+	      mismatch (tv[i].out, tv[i].inlen, out, tv[i].inlen);
+	      fail ("aes-siv, encrypt mismatch entry %d\n", i);
+	    }
+
+	  err = gcry_cipher_gettag (hde, tag, taglen2);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_gettag(%d) failed: %s\n",
+		    i, gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+
+	  if (memcmp (tv[i].tag, tag, taglen2))
+	    {
+	      mismatch (tv[i].tag, taglen2, tag, taglen2);
+	      fail ("aes-siv, tag mismatch entry %d\n", i);
+	    }
+
+	  err = gcry_cipher_set_decryption_tag (hdd, tag, taglen2);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_set_decryption_tag (%d) failed: %s\n",
+		    i, gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+
+	  err = gcry_cipher_decrypt (hdd, out, tv[i].inlen, NULL, 0);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_decrypt (%d) failed: %s\n",
+		    i, gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+
+	  if (memcmp (tv[i].plaintext, out, tv[i].inlen))
+	    fail ("aes-siv, decrypt mismatch entry %d\n", i);
+
+	  err = gcry_cipher_checktag (hdd, tag, taglen2);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_checktag (%d) failed: %s\n",
+		    i, gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+	}
+      else
+	{
+	  err = gcry_cipher_gettag (hde, tag, taglen2);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_gettag(%d) failed: %s\n",
+		    i, gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+
+	  if (memcmp (tv[i].tag, tag, taglen2))
+	    {
+	      mismatch (tv[i].tag, taglen2, tag, taglen2);
+	      fail ("aes-siv, tag mismatch entry %d\n", i);
+	    }
+
+	  err = gcry_cipher_checktag (hdd, tv[i].tag, taglen2);
+	  if (err)
+	    {
+	      fail ("aes-siv, gcry_cipher_checktag (%d) failed: %s\n",
+		    i, gpg_strerror (err));
+	      gcry_cipher_close (hde);
+	      gcry_cipher_close (hdd);
+	      return;
+	    }
+	}
+
+      gcry_cipher_close (hde);
+      gcry_cipher_close (hdd);
+    }
+  if (verbose)
+    fprintf (stderr, "  Completed SIV checks.\n");
+}
+
+
 static void
 _check_poly1305_cipher (unsigned int step)
 {
@@ -10133,6 +10553,7 @@ check_cipher_modes(void)
   check_ocb_cipher ();
   check_xts_cipher ();
   check_eax_cipher ();
+  check_siv_cipher ();
   check_gost28147_cipher ();
   check_stream_cipher ();
   check_stream_cipher_large_block ();
diff --git a/tests/bench-slope.c b/tests/bench-slope.c
index d1b7f24f..91eb7cc5 100644
--- a/tests/bench-slope.c
+++ b/tests/bench-slope.c
@@ -966,6 +966,11 @@ bench_encrypt_init (struct bench_obj *obj)
     }
 
   keylen = gcry_cipher_get_algo_keylen (mode->algo);
+  if (mode->mode == GCRY_CIPHER_MODE_SIV)
+    {
+      keylen *= 2;
+    }
+
   if (keylen)
     {
       char key[keylen];
@@ -1290,6 +1295,7 @@ bench_aead_encrypt_do_bench (struct bench_obj *obj, void *buf, size_t buflen,
   int err;
   char tag[16];
 
+  gcry_cipher_reset (hd);
   gcry_cipher_setiv (hd, nonce, noncelen);
 
   gcry_cipher_final (hd);
@@ -1320,13 +1326,18 @@ bench_aead_decrypt_do_bench (struct bench_obj *obj, void *buf, size_t buflen,
   int err;
   char tag[16] = { 0, };
 
+  gcry_cipher_reset (hd);
+  gcry_cipher_set_decryption_tag (hd, tag, 16);
+
   gcry_cipher_setiv (hd, nonce, noncelen);
 
   gcry_cipher_final (hd);
   err = gcry_cipher_decrypt (hd, buf, buflen, buf, buflen);
+  if (gpg_err_code (err) == GPG_ERR_CHECKSUM)
+    err = gpg_error (GPG_ERR_NO_ERROR);
   if (err)
     {
-      fprintf (stderr, PGM ": gcry_cipher_encrypt failed: %s\n",
+      fprintf (stderr, PGM ": gcry_cipher_decrypt failed: %s\n",
            gpg_strerror (err));
       gcry_cipher_close (hd);
       exit (1);
@@ -1354,13 +1365,18 @@ bench_aead_authenticate_do_bench (struct bench_obj *obj, void *buf,
   char tag[16] = { 0, };
   char data = 0xff;
 
-  err = gcry_cipher_setiv (hd, nonce, noncelen);
-  if (err)
+  gcry_cipher_reset (hd);
+
+  if (noncelen > 0)
     {
-      fprintf (stderr, PGM ": gcry_cipher_setiv failed: %s\n",
-           gpg_strerror (err));
-      gcry_cipher_close (hd);
-      exit (1);
+      err = gcry_cipher_setiv (hd, nonce, noncelen);
+      if (err)
+	{
+	  fprintf (stderr, PGM ": gcry_cipher_setiv failed: %s\n",
+	       gpg_strerror (err));
+	  gcry_cipher_close (hd);
+	  exit (1);
+	}
     }
 
   err = gcry_cipher_authenticate (hd, buf, buflen);
@@ -1487,6 +1503,47 @@ static struct bench_ops ocb_authenticate_ops = {
   &bench_ocb_authenticate_do_bench
 };
 
+
+static void
+bench_siv_encrypt_do_bench (struct bench_obj *obj, void *buf,
+			    size_t buflen)
+{
+  bench_aead_encrypt_do_bench (obj, buf, buflen, NULL, 0);
+}
+
+static void
+bench_siv_decrypt_do_bench (struct bench_obj *obj, void *buf,
+			    size_t buflen)
+{
+  bench_aead_decrypt_do_bench (obj, buf, buflen, NULL, 0);
+}
+
+static void
+bench_siv_authenticate_do_bench (struct bench_obj *obj, void *buf,
+				 size_t buflen)
+{
+  bench_aead_authenticate_do_bench (obj, buf, buflen, NULL, 0);
+}
+
+static struct bench_ops siv_encrypt_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_siv_encrypt_do_bench
+};
+
+static struct bench_ops siv_decrypt_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_siv_decrypt_do_bench
+};
+
+static struct bench_ops siv_authenticate_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_siv_authenticate_do_bench
+};
+
+
 static void
 bench_eax_encrypt_do_bench (struct bench_obj *obj, void *buf,
 			    size_t buflen)
@@ -1603,6 +1660,9 @@ static struct bench_cipher_mode cipher_modes[] = {
   {GCRY_CIPHER_MODE_OCB, "OCB enc",  &ocb_encrypt_ops},
   {GCRY_CIPHER_MODE_OCB, "OCB dec",  &ocb_decrypt_ops},
   {GCRY_CIPHER_MODE_OCB, "OCB auth", &ocb_authenticate_ops},
+  {GCRY_CIPHER_MODE_SIV, "SIV enc", &siv_encrypt_ops},
+  {GCRY_CIPHER_MODE_SIV, "SIV dec", &siv_decrypt_ops},
+  {GCRY_CIPHER_MODE_SIV, "SIV auth", &siv_authenticate_ops},
   {GCRY_CIPHER_MODE_POLY1305, "POLY1305 enc", &poly1305_encrypt_ops},
   {GCRY_CIPHER_MODE_POLY1305, "POLY1305 dec", &poly1305_decrypt_ops},
   {GCRY_CIPHER_MODE_POLY1305, "POLY1305 auth", &poly1305_authenticate_ops},
@@ -1651,6 +1711,10 @@ cipher_bench_one (int algo, struct bench_cipher_mode *pmode)
   if (mode.mode == GCRY_CIPHER_MODE_XTS && blklen != GCRY_XTS_BLOCK_LEN)
     return;
 
+  /* SIV has restrictions for block-size */
+  if (mode.mode == GCRY_CIPHER_MODE_SIV && blklen != GCRY_SIV_BLOCK_LEN)
+    return;
+
   /* Our OCB implementation has restrictions for block-size.  */
   if (mode.mode == GCRY_CIPHER_MODE_OCB && blklen != GCRY_OCB_BLOCK_LEN)
     return;
-- 
2.30.2




More information about the Gcrypt-devel mailing list