Blob Blame History Raw
From e8279107801bb93303b22e1b927929ce18279dc5 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Sun, 8 May 2022 16:13:13 +0100
Subject: [PATCH] luks: Refactor the filter

Move the LUKS-specific code out into a separate file.  This is mostly
pure refactoring to tidy things up.

(cherry picked from commit 2159d85e0bed1943542da58b43c91f2caa096d6c)
---
 filters/luks/Makefile.am       |   2 +
 filters/luks/luks-encryption.c | 926 +++++++++++++++++++++++++++++++++
 filters/luks/luks-encryption.h |  78 +++
 filters/luks/luks.c            | 874 ++-----------------------------
 4 files changed, 1037 insertions(+), 843 deletions(-)
 create mode 100644 filters/luks/luks-encryption.c
 create mode 100644 filters/luks/luks-encryption.h

diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am
index 2688f696..0894ac8b 100644
--- a/filters/luks/Makefile.am
+++ b/filters/luks/Makefile.am
@@ -38,6 +38,8 @@ if HAVE_GNUTLS_PBKDF2
 filter_LTLIBRARIES = nbdkit-luks-filter.la
 
 nbdkit_luks_filter_la_SOURCES = \
+	luks-encryption.c \
+	luks-encryption.h \
 	luks.c \
 	$(top_srcdir)/include/nbdkit-filter.h \
 	$(NULL)
diff --git a/filters/luks/luks-encryption.c b/filters/luks/luks-encryption.c
new file mode 100644
index 00000000..8ee0eb35
--- /dev/null
+++ b/filters/luks/luks-encryption.c
@@ -0,0 +1,926 @@
+/* nbdkit
+ * Copyright (C) 2018-2022 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT 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 RED HAT 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <gnutls/crypto.h>
+
+#include <nbdkit-filter.h>
+
+#include "luks-encryption.h"
+
+#include "byte-swapping.h"
+#include "cleanup.h"
+#include "isaligned.h"
+#include "rounding.h"
+
+/* LUKSv1 constants. */
+#define LUKS_MAGIC { 'L', 'U', 'K', 'S', 0xBA, 0xBE }
+#define LUKS_MAGIC_LEN 6
+#define LUKS_DIGESTSIZE 20
+#define LUKS_SALTSIZE 32
+#define LUKS_NUMKEYS 8
+#define LUKS_KEY_DISABLED 0x0000DEAD
+#define LUKS_KEY_ENABLED  0x00AC71F3
+#define LUKS_STRIPES 4000
+#define LUKS_ALIGN_KEYSLOTS 4096
+
+/* Key slot. */
+struct luks_keyslot {
+  uint32_t active;              /* LUKS_KEY_DISABLED|LUKS_KEY_ENABLED */
+  uint32_t password_iterations;
+  char password_salt[LUKS_SALTSIZE];
+  uint32_t key_material_offset;
+  uint32_t stripes;
+} __attribute__((__packed__));
+
+/* LUKS superblock. */
+struct luks_phdr {
+  char magic[LUKS_MAGIC_LEN];   /* LUKS_MAGIC */
+  uint16_t version;             /* Only 1 is supported. */
+  char cipher_name[32];
+  char cipher_mode[32];
+  char hash_spec[32];
+  uint32_t payload_offset;
+  uint32_t master_key_len;
+  uint8_t master_key_digest[LUKS_DIGESTSIZE];
+  uint8_t master_key_salt[LUKS_SALTSIZE];
+  uint32_t master_key_digest_iterations;
+  uint8_t uuid[40];
+
+  struct luks_keyslot keyslot[LUKS_NUMKEYS]; /* Key slots. */
+} __attribute__((__packed__));
+
+/* Block cipher mode of operation.
+ * https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+ */
+enum cipher_mode {
+  CIPHER_MODE_ECB, CIPHER_MODE_CBC, CIPHER_MODE_XTS, CIPHER_MODE_CTR,
+};
+
+static enum cipher_mode
+lookup_cipher_mode (const char *str)
+{
+  if (strcmp (str, "ecb") == 0)
+    return CIPHER_MODE_ECB;
+  if (strcmp (str, "cbc") == 0)
+    return CIPHER_MODE_CBC;
+  if (strcmp (str, "xts") == 0)
+    return CIPHER_MODE_XTS;
+  if (strcmp (str, "ctr") == 0)
+    return CIPHER_MODE_CTR;
+  nbdkit_error ("unknown cipher mode: %s "
+                "(expecting \"ecb\", \"cbc\", \"xts\" or \"ctr\")", str);
+  return -1;
+}
+
+static const char *
+cipher_mode_to_string (enum cipher_mode v)
+{
+  switch (v) {
+  case CIPHER_MODE_ECB: return "ecb";
+  case CIPHER_MODE_CBC: return "cbc";
+  case CIPHER_MODE_XTS: return "xts";
+  case CIPHER_MODE_CTR: return "ctr";
+  default: abort ();
+  }
+}
+
+/* Methods used by LUKS to generate initial vectors.
+ *
+ * ESSIV is a bit more complicated to implement.  It is supported by
+ * qemu but not by us.
+ */
+enum ivgen {
+  IVGEN_PLAIN, IVGEN_PLAIN64, /* IVGEN_ESSIV, */
+};
+
+static enum ivgen
+lookup_ivgen (const char *str)
+{
+  if (strcmp (str, "plain") == 0)
+    return IVGEN_PLAIN;
+  if (strcmp (str, "plain64") == 0)
+    return IVGEN_PLAIN64;
+/*
+  if (strcmp (str, "essiv") == 0)
+    return IVGEN_ESSIV;
+*/
+  nbdkit_error ("unknown IV generation algorithm: %s "
+                "(expecting \"plain\", \"plain64\" etc)", str);
+  return -1;
+}
+
+static const char *
+ivgen_to_string (enum ivgen v)
+{
+  switch (v) {
+  case IVGEN_PLAIN: return "plain";
+  case IVGEN_PLAIN64: return "plain64";
+  /*case IVGEN_ESSIV: return "essiv";*/
+  default: abort ();
+  }
+}
+
+static void
+calculate_iv (enum ivgen v, uint8_t *iv, size_t ivlen, uint64_t sector)
+{
+  size_t prefixlen;
+  uint32_t sector32;
+
+  switch (v) {
+  case IVGEN_PLAIN:
+    prefixlen = 4; /* 32 bits */
+    if (prefixlen > ivlen)
+      prefixlen = ivlen;
+    sector32 = (uint32_t) sector; /* truncate to only lower bits */
+    sector32 = htole32 (sector32);
+    memcpy (iv, &sector32, prefixlen);
+    memset (iv + prefixlen, 0, ivlen - prefixlen);
+    break;
+
+  case IVGEN_PLAIN64:
+    prefixlen = 8; /* 64 bits */
+    if (prefixlen > ivlen)
+      prefixlen = ivlen;
+    sector = htole64 (sector);
+    memcpy (iv, &sector, prefixlen);
+    memset (iv + prefixlen, 0, ivlen - prefixlen);
+    break;
+
+  /*case IVGEN_ESSIV:*/
+  default: abort ();
+  }
+}
+
+/* Cipher algorithm.
+ *
+ * qemu in theory supports many more, but with the GnuTLS backend only
+ * AES is supported.  The kernel seems to only support AES for LUKSv1.
+ */
+enum cipher_alg {
+  CIPHER_ALG_AES_128, CIPHER_ALG_AES_192, CIPHER_ALG_AES_256,
+};
+
+static enum cipher_alg
+lookup_cipher_alg (const char *str, enum cipher_mode mode, int key_bytes)
+{
+  if (mode == CIPHER_MODE_XTS)
+    key_bytes /= 2;
+
+  if (strcmp (str, "aes") == 0) {
+    if (key_bytes == 16)
+      return CIPHER_ALG_AES_128;
+    if (key_bytes == 24)
+      return CIPHER_ALG_AES_192;
+    if (key_bytes == 32)
+      return CIPHER_ALG_AES_256;
+  }
+  nbdkit_error ("unknown cipher algorithm: %s (expecting \"aes\", etc)", str);
+  return -1;
+}
+
+static const char *
+cipher_alg_to_string (enum cipher_alg v)
+{
+  switch (v) {
+  case CIPHER_ALG_AES_128: return "aes-128";
+  case CIPHER_ALG_AES_192: return "aes-192";
+  case CIPHER_ALG_AES_256: return "aes-256";
+  default: abort ();
+  }
+}
+
+#if 0
+static int
+cipher_alg_key_bytes (enum cipher_alg v)
+{
+  switch (v) {
+  case CIPHER_ALG_AES_128: return 16;
+  case CIPHER_ALG_AES_192: return 24;
+  case CIPHER_ALG_AES_256: return 32;
+  default: abort ();
+  }
+}
+#endif
+
+static int
+cipher_alg_iv_len (enum cipher_alg v, enum cipher_mode mode)
+{
+  if (CIPHER_MODE_ECB)
+    return 0;                   /* Don't need an IV in this mode. */
+
+  switch (v) {
+  case CIPHER_ALG_AES_128:
+  case CIPHER_ALG_AES_192:
+  case CIPHER_ALG_AES_256:
+    return 16;
+  default: abort ();
+  }
+}
+
+/* Hash, eg.MD5, SHA1 etc.
+ *
+ * We reuse the GnuTLS digest algorithm enum here since it supports at
+ * least all the ones that LUKSv1 does.
+ */
+static gnutls_digest_algorithm_t
+lookup_hash (const char *str)
+{
+  if (strcmp (str, "md5") == 0)
+    return GNUTLS_DIG_MD5;
+  if (strcmp (str, "sha1") == 0)
+    return GNUTLS_DIG_SHA1;
+  if (strcmp (str, "sha224") == 0)
+    return GNUTLS_DIG_SHA224;
+  if (strcmp (str, "sha256") == 0)
+    return GNUTLS_DIG_SHA256;
+  if (strcmp (str, "sha384") == 0)
+    return GNUTLS_DIG_SHA384;
+  if (strcmp (str, "sha512") == 0)
+    return GNUTLS_DIG_SHA512;
+  if (strcmp (str, "ripemd160") == 0)
+    return GNUTLS_DIG_RMD160;
+  nbdkit_error ("unknown hash algorithm: %s "
+                "(expecting \"md5\", \"sha1\", \"sha224\", etc)", str);
+  return -1;
+}
+
+static const char *
+hash_to_string (gnutls_digest_algorithm_t v)
+{
+  switch (v) {
+  case GNUTLS_DIG_UNKNOWN: return "unknown";
+  case GNUTLS_DIG_MD5: return "md5";
+  case GNUTLS_DIG_SHA1: return "sha1";
+  case GNUTLS_DIG_SHA224: return "sha224";
+  case GNUTLS_DIG_SHA256: return "sha256";
+  case GNUTLS_DIG_SHA384: return "sha384";
+  case GNUTLS_DIG_SHA512: return "sha512";
+  case GNUTLS_DIG_RMD160: return "ripemd160";
+  default: abort ();
+  }
+}
+
+#if 0
+/* See qemu & dm-crypt implementations for an explanation of what's
+ * going on here.
+ */
+enum cipher_alg
+lookup_essiv_cipher (enum cipher_alg cipher_alg,
+                     gnutls_digest_algorithm_t ivgen_hash_alg)
+{
+  int digest_bytes = gnutls_hash_get_len (ivgen_hash_alg);
+  int key_bytes = cipher_alg_key_bytes (cipher_alg);
+
+  if (digest_bytes == key_bytes)
+    return cipher_alg;
+
+  switch (cipher_alg) {
+  case CIPHER_ALG_AES_128:
+  case CIPHER_ALG_AES_192:
+  case CIPHER_ALG_AES_256:
+    if (digest_bytes == 16) return CIPHER_ALG_AES_128;
+    if (digest_bytes == 24) return CIPHER_ALG_AES_192;
+    if (digest_bytes == 32) return CIPHER_ALG_AES_256;
+    nbdkit_error ("no %s cipher available with key size %d",
+                  "AES", digest_bytes);
+    return -1;
+  default:
+    nbdkit_error ("ESSIV does not support cipher %s",
+                  cipher_alg_to_string (cipher_alg));
+    return -1;
+  }
+}
+#endif
+
+/* Per-connection data. */
+struct luks_data {
+  /* LUKS header, if necessary byte-swapped into host order. */
+  struct luks_phdr phdr;
+
+  /* Decoded algorithm etc. */
+  enum cipher_alg cipher_alg;
+  enum cipher_mode cipher_mode;
+  gnutls_digest_algorithm_t hash_alg;
+  enum ivgen ivgen_alg;
+  gnutls_digest_algorithm_t ivgen_hash_alg;
+  enum cipher_alg ivgen_cipher_alg;
+
+  /* GnuTLS algorithm. */
+  gnutls_cipher_algorithm_t gnutls_cipher;
+
+  /* If we managed to decrypt one of the keyslots using the passphrase
+   * then this contains the master key, otherwise NULL.
+   */
+  uint8_t *masterkey;
+};
+
+/* Parse the header fields containing cipher algorithm, mode, etc. */
+static int
+parse_cipher_strings (struct luks_data *h)
+{
+  char cipher_name[33], cipher_mode[33], hash_spec[33];
+  char *ivgen, *ivhash;
+
+  /* Copy the header fields locally and ensure they are \0 terminated. */
+  memcpy (cipher_name, h->phdr.cipher_name, 32);
+  cipher_name[32] = 0;
+  memcpy (cipher_mode, h->phdr.cipher_mode, 32);
+  cipher_mode[32] = 0;
+  memcpy (hash_spec, h->phdr.hash_spec, 32);
+  hash_spec[32] = 0;
+
+  nbdkit_debug ("LUKS v%" PRIu16 " cipher: %s mode: %s hash: %s "
+                "master key: %" PRIu32 " bits",
+                h->phdr.version, cipher_name, cipher_mode, hash_spec,
+                h->phdr.master_key_len * 8);
+
+  /* The cipher_mode header has the form: "ciphermode-ivgen[:ivhash]"
+   * QEmu writes: "xts-plain64"
+   */
+  ivgen = strchr (cipher_mode, '-');
+  if (!ivgen) {
+    nbdkit_error ("incorrect cipher_mode header, "
+                  "expecting mode-ivgenerator but got \"%s\"", cipher_mode);
+    return -1;
+  }
+  *ivgen = '\0';
+  ivgen++;
+
+  ivhash = strchr (ivgen, ':');
+  if (!ivhash)
+    h->ivgen_hash_alg = GNUTLS_DIG_UNKNOWN;
+  else {
+    *ivhash = '\0';
+    ivhash++;
+
+    h->ivgen_hash_alg = lookup_hash (ivhash);
+    if (h->ivgen_hash_alg == -1)
+      return -1;
+  }
+
+  h->cipher_mode = lookup_cipher_mode (cipher_mode);
+  if (h->cipher_mode == -1)
+    return -1;
+
+  h->cipher_alg = lookup_cipher_alg (cipher_name, h->cipher_mode,
+                                     h->phdr.master_key_len);
+  if (h->cipher_alg == -1)
+    return -1;
+
+  h->hash_alg = lookup_hash (hash_spec);
+  if (h->hash_alg == -1)
+    return -1;
+
+  h->ivgen_alg = lookup_ivgen (ivgen);
+  if (h->ivgen_alg == -1)
+    return -1;
+
+#if 0
+  if (h->ivgen_alg == IVGEN_ESSIV) {
+    if (!ivhash) {
+      nbdkit_error ("incorrect IV generator hash specification");
+      return -1;
+    }
+    h->ivgen_cipher_alg = lookup_essiv_cipher (h->cipher_alg,
+                                               h->ivgen_hash_alg);
+    if (h->ivgen_cipher_alg == -1)
+      return -1;
+  }
+  else
+#endif
+  h->ivgen_cipher_alg = h->cipher_alg;
+
+  nbdkit_debug ("LUKS parsed ciphers: %s %s %s %s %s %s",
+                cipher_alg_to_string (h->cipher_alg),
+                cipher_mode_to_string (h->cipher_mode),
+                hash_to_string (h->hash_alg),
+                ivgen_to_string (h->ivgen_alg),
+                hash_to_string (h->ivgen_hash_alg),
+                cipher_alg_to_string (h->ivgen_cipher_alg));
+
+  /* GnuTLS combines cipher and block mode into a single value.  Not
+   * all possible combinations are available in GnuTLS.  See:
+   * https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html
+   */
+  h->gnutls_cipher = GNUTLS_CIPHER_NULL;
+  switch (h->cipher_mode) {
+  case CIPHER_MODE_XTS:
+    switch (h->cipher_alg) {
+    case CIPHER_ALG_AES_128:
+      h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS;
+      break;
+    case CIPHER_ALG_AES_256:
+      h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS;
+      break;
+    default: break;
+    }
+    break;
+  case CIPHER_MODE_CBC:
+    switch (h->cipher_alg) {
+    case CIPHER_ALG_AES_128:
+      h->gnutls_cipher = GNUTLS_CIPHER_AES_128_CBC;
+      break;
+    case CIPHER_ALG_AES_192:
+      h->gnutls_cipher = GNUTLS_CIPHER_AES_192_CBC;
+      break;
+    case CIPHER_ALG_AES_256:
+      h->gnutls_cipher = GNUTLS_CIPHER_AES_256_CBC;
+      break;
+    default: break;
+    }
+  default: break;
+  }
+  if (h->gnutls_cipher == GNUTLS_CIPHER_NULL) {
+    nbdkit_error ("cipher algorithm %s in mode %s is not supported by GnuTLS",
+                  cipher_alg_to_string (h->cipher_alg),
+                  cipher_mode_to_string (h->cipher_mode));
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Anti-Forensic merge operation. */
+static void
+xor (const uint8_t *in1, const uint8_t *in2, uint8_t *out, size_t len)
+{
+  size_t i;
+
+  for (i = 0; i < len; ++i)
+    out[i] = in1[i] ^ in2[i];
+}
+
+static int
+af_hash (gnutls_digest_algorithm_t hash_alg, uint8_t *block, size_t len)
+{
+  size_t digest_bytes = gnutls_hash_get_len (hash_alg);
+  size_t nr_blocks, last_block_len;
+  size_t i;
+  CLEANUP_FREE uint8_t *temp = malloc (digest_bytes);
+  int r;
+  gnutls_hash_hd_t hash;
+
+  nr_blocks = len / digest_bytes;
+  last_block_len = len % digest_bytes;
+  if (last_block_len != 0)
+    nr_blocks++;
+  else
+    last_block_len = digest_bytes;
+
+  for (i = 0; i < nr_blocks; ++i) {
+    const uint32_t iv = htobe32 (i);
+    const size_t blen = i < nr_blocks - 1 ? digest_bytes : last_block_len;
+
+    /* Hash iv + i'th block into temp. */
+    r = gnutls_hash_init (&hash, hash_alg);
+    if (r != 0) {
+      nbdkit_error ("gnutls_hash_init: %s", gnutls_strerror (r));
+      return -1;
+    }
+    gnutls_hash (hash, &iv, sizeof iv);
+    gnutls_hash (hash, &block[i*digest_bytes], blen);
+    gnutls_hash_deinit (hash, temp);
+
+    memcpy (&block[i*digest_bytes], temp, blen);
+  }
+
+  return 0;
+}
+
+static int
+afmerge (gnutls_digest_algorithm_t hash_alg, uint32_t stripes,
+         const uint8_t *in, uint8_t *out, size_t outlen)
+{
+  CLEANUP_FREE uint8_t *block = calloc (1, outlen);
+  size_t i;
+
+  /* NB: input size is stripes * master_key_len where
+   * master_key_len == outlen
+   */
+  for (i = 0; i < stripes-1; ++i) {
+    xor (&in[i*outlen], block, block, outlen);
+    if (af_hash (hash_alg, block, outlen) == -1)
+      return -1;
+  }
+  xor (&in[i*outlen], block, out, outlen);
+  return 0;
+}
+
+/* Length of key material in key slot i (sectors).
+ *
+ * This is basically copied from qemu because the spec description is
+ * unintelligible and apparently doesn't match reality.
+ */
+static uint64_t
+key_material_length_in_sectors (struct luks_data *h, size_t i)
+{
+  uint64_t len, r;
+
+  len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes;
+  r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE);
+  r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE);
+  return r;
+}
+
+/* Try the passphrase in key slot i.  If this returns true then the
+ * passphrase was able to decrypt the master key, and the master key
+ * has been stored in h->masterkey.
+ */
+static int
+try_passphrase_in_keyslot (nbdkit_next *next, struct luks_data *h,
+                           size_t i, const char *passphrase)
+{
+  /* I believe this is supposed to be safe, looking at the GnuTLS
+   * header file.
+   */
+  const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg;
+  struct luks_keyslot *ks = &h->phdr.keyslot[i];
+  size_t split_key_len;
+  CLEANUP_FREE uint8_t *split_key = NULL;
+  CLEANUP_FREE uint8_t *masterkey = NULL;
+  const gnutls_datum_t key =
+    { (unsigned char *) passphrase, strlen (passphrase) };
+  const gnutls_datum_t salt =
+    { (unsigned char *) ks->password_salt, LUKS_SALTSIZE };
+  const gnutls_datum_t msalt =
+    { (unsigned char *) h->phdr.master_key_salt, LUKS_SALTSIZE };
+  gnutls_datum_t mkey;
+  gnutls_cipher_hd_t cipher;
+  int r, err = 0;
+  uint64_t start;
+  uint8_t key_digest[LUKS_DIGESTSIZE];
+
+  if (ks->active != LUKS_KEY_ENABLED)
+    return 0;
+
+  split_key_len = h->phdr.master_key_len * ks->stripes;
+  split_key = malloc (split_key_len);
+  if (split_key == NULL) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+  masterkey = malloc (h->phdr.master_key_len);
+  if (masterkey == NULL) {
+    nbdkit_error ("malloc: %m");
+    return -1;
+  }
+
+  /* Hash the passphrase to make a possible masterkey. */
+  r = gnutls_pbkdf2 (mac, &key, &salt, ks->password_iterations,
+                     masterkey, h->phdr.master_key_len);
+  if (r != 0) {
+    nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r));
+    return -1;
+  }
+
+  /* Read master key material from plugin. */
+  start = ks->key_material_offset * LUKS_SECTOR_SIZE;
+  if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) {
+    errno = err;
+    return -1;
+  }
+
+  /* Decrypt the (still AFsplit) master key material. */
+  mkey.data = (unsigned char *) masterkey;
+  mkey.size = h->phdr.master_key_len;
+  r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
+  if (r != 0) {
+    nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
+    return -1;
+  }
+
+  r = do_decrypt (h, cipher, 0, split_key, split_key_len / LUKS_SECTOR_SIZE);
+  gnutls_cipher_deinit (cipher);
+  if (r == -1)
+    return -1;
+
+  /* Decode AFsplit key to a possible masterkey. */
+  if (afmerge (h->hash_alg, ks->stripes, split_key,
+               masterkey, h->phdr.master_key_len) == -1)
+    return -1;
+
+  /* Check if the masterkey is correct by comparing hash of the
+   * masterkey with LUKS header.
+   */
+  r = gnutls_pbkdf2 (mac, &mkey, &msalt,
+                     h->phdr.master_key_digest_iterations,
+                     key_digest, LUKS_DIGESTSIZE);
+  if (r != 0) {
+    nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r));
+    return -1;
+  }
+
+  if (memcmp (key_digest, h->phdr.master_key_digest, LUKS_DIGESTSIZE) == 0) {
+    /* The passphrase is correct so save the master key in the handle. */
+    h->masterkey = malloc (h->phdr.master_key_len);
+    if (h->masterkey == NULL) {
+      nbdkit_error ("malloc: %m");
+      return -1;
+    }
+    memcpy (h->masterkey, masterkey, h->phdr.master_key_len);
+    return 1;
+  }
+
+  return 0;
+}
+
+struct luks_data *
+load_header (nbdkit_next *next, const char *passphrase)
+{
+  static const char expected_magic[] = LUKS_MAGIC;
+  struct luks_data *h;
+  int64_t size;
+  int err = 0, r;
+  size_t i;
+  struct luks_keyslot *ks;
+  char uuid[41];
+
+  h = calloc (1, sizeof *h);
+  if (h == NULL) {
+    nbdkit_error ("calloc: %m");
+    return NULL;
+  }
+
+  /* Check the struct size matches the documentation. */
+  assert (sizeof (struct luks_phdr) == 592);
+
+  /* Check this is a LUKSv1 disk. */
+  size = next->get_size (next);
+  if (size == -1) {
+    free (h);
+    return NULL;
+  }
+  if (size < 16384) {
+    nbdkit_error ("disk is too small to be LUKS-encrypted");
+    free (h);
+    return NULL;
+  }
+
+  /* Read the phdr. */
+  if (next->pread (next, &h->phdr, sizeof h->phdr, 0, 0, &err) == -1) {
+    free (h);
+    errno = err;
+    return NULL;
+  }
+
+  if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) {
+    nbdkit_error ("this disk does not contain a LUKS header");
+    return NULL;
+  }
+  h->phdr.version = be16toh (h->phdr.version);
+  if (h->phdr.version != 1) {
+    nbdkit_error ("this disk contains a LUKS version %" PRIu16 " header, "
+                  "but this filter only supports LUKSv1",
+                  h->phdr.version);
+    free (h);
+    return NULL;
+  }
+
+  /* Byte-swap the rest of the header. */
+  h->phdr.payload_offset = be32toh (h->phdr.payload_offset);
+  h->phdr.master_key_len = be32toh (h->phdr.master_key_len);
+  h->phdr.master_key_digest_iterations =
+    be32toh (h->phdr.master_key_digest_iterations);
+
+  for (i = 0; i < LUKS_NUMKEYS; ++i) {
+    ks = &h->phdr.keyslot[i];
+    ks->active = be32toh (ks->active);
+    ks->password_iterations = be32toh (ks->password_iterations);
+    ks->key_material_offset = be32toh (ks->key_material_offset);
+    ks->stripes = be32toh (ks->stripes);
+  }
+
+  /* Sanity check some fields. */
+  if (h->phdr.payload_offset >= size / LUKS_SECTOR_SIZE) {
+    nbdkit_error ("bad LUKSv1 header: payload offset points beyond "
+                  "the end of the disk");
+    free (h);
+    return NULL;
+  }
+
+  /* We derive several allocations from master_key_len so make sure
+   * it's not insane.
+   */
+  if (h->phdr.master_key_len > 1024) {
+    nbdkit_error ("bad LUKSv1 header: master key is too long");
+    free (h);
+    return NULL;
+  }
+
+  for (i = 0; i < LUKS_NUMKEYS; ++i) {
+    uint64_t start, len;
+
+    ks = &h->phdr.keyslot[i];
+    switch (ks->active) {
+    case LUKS_KEY_ENABLED:
+      if (!ks->stripes) {
+        nbdkit_error ("bad LUKSv1 header: key slot %zu is corrupted", i);
+        free (h);
+        return NULL;
+      }
+      if (ks->stripes >= 10000) {
+        nbdkit_error ("bad LUKSv1 header: key slot %zu stripes too large", i);
+        return NULL;
+      }
+      start = ks->key_material_offset;
+      len = key_material_length_in_sectors (h, i);
+      if (len > 4096) /* bound it at something reasonable */ {
+        nbdkit_error ("bad LUKSv1 header: key slot %zu key material length "
+                      "is too large", i);
+        free (h);
+        return NULL;
+      }
+      if (start * LUKS_SECTOR_SIZE >= size ||
+          (start + len) * LUKS_SECTOR_SIZE >= size) {
+        nbdkit_error ("bad LUKSv1 header: key slot %zu key material offset "
+                      "points beyond the end of the disk", i);
+        free (h);
+        return NULL;
+      }
+      /*FALLTHROUGH*/
+    case LUKS_KEY_DISABLED:
+      break;
+
+    default:
+      nbdkit_error ("bad LUKSv1 header: key slot %zu has "
+                    "an invalid active flag", i);
+      return NULL;
+    }
+  }
+
+  /* Decode the ciphers. */
+  if (parse_cipher_strings (h) == -1) {
+    free (h);
+    return NULL;
+  }
+
+  /* Dump some information about the header. */
+  memcpy (uuid, h->phdr.uuid, 40);
+  uuid[40] = 0;
+  nbdkit_debug ("LUKS UUID: %s", uuid);
+
+  for (i = 0; i < LUKS_NUMKEYS; ++i) {
+    uint64_t start, len;
+
+    ks = &h->phdr.keyslot[i];
+    if (ks->active == LUKS_KEY_ENABLED) {
+      start = ks->key_material_offset;
+      len = key_material_length_in_sectors (h, i);
+      nbdkit_debug ("LUKS key slot %zu: key material in sectors %" PRIu64
+                    "..%" PRIu64,
+                    i, start, start+len-1);
+    }
+  }
+
+  /* Now try to unlock the master key. */
+  for (i = 0; i < LUKS_NUMKEYS; ++i) {
+    r = try_passphrase_in_keyslot (next, h, i, passphrase);
+    if (r == -1) {
+      free (h);
+      return NULL;
+    }
+    if (r > 0)
+      goto unlocked;
+  }
+  nbdkit_error ("LUKS passphrase is not correct, "
+                "no key slot could be unlocked");
+  free (h);
+  return NULL;
+
+ unlocked:
+  assert (h->masterkey != NULL);
+  nbdkit_debug ("LUKS unlocked block device with passphrase");
+
+  return h;
+}
+
+/* Free fields in the handle that might have been set by load_header. */
+void
+free_luks_data (struct luks_data *h)
+{
+  if (h->masterkey) {
+    memset (h->masterkey, 0, h->phdr.master_key_len);
+    free (h->masterkey);
+  }
+  free (h);
+}
+
+uint64_t
+get_payload_offset (struct luks_data *h)
+{
+  return h->phdr.payload_offset;
+}
+
+gnutls_cipher_hd_t
+create_cipher (struct luks_data *h)
+{
+  gnutls_datum_t mkey;
+  gnutls_cipher_hd_t cipher;
+  int r;
+
+  assert (h->masterkey != NULL);
+
+  mkey.data = (unsigned char *) h->masterkey;
+  mkey.size = h->phdr.master_key_len;
+  r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
+  if (r != 0) {
+    nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
+    return NULL;
+  }
+  return cipher;
+}
+
+/* Perform decryption of a block of data in memory. */
+int
+do_decrypt (struct luks_data *h, gnutls_cipher_hd_t cipher,
+            uint64_t sector, uint8_t *buf, size_t nr_sectors)
+{
+  const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode);
+  CLEANUP_FREE uint8_t *iv = malloc (ivlen);
+  int r;
+
+  while (nr_sectors) {
+    calculate_iv (h->ivgen_alg, iv, ivlen, sector);
+    gnutls_cipher_set_iv (cipher, iv, ivlen);
+    r = gnutls_cipher_decrypt2 (cipher,
+                                buf, LUKS_SECTOR_SIZE, /* ciphertext */
+                                buf, LUKS_SECTOR_SIZE  /* plaintext */);
+    if (r != 0) {
+      nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r));
+      return -1;
+    }
+
+    buf += LUKS_SECTOR_SIZE;
+    nr_sectors--;
+    sector++;
+  }
+
+  return 0;
+}
+
+/* Perform encryption of a block of data in memory. */
+int
+do_encrypt (struct luks_data *h, gnutls_cipher_hd_t cipher,
+            uint64_t sector, uint8_t *buf, size_t nr_sectors)
+{
+  const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode);
+  CLEANUP_FREE uint8_t *iv = malloc (ivlen);
+  int r;
+
+  while (nr_sectors) {
+    calculate_iv (h->ivgen_alg, iv, ivlen, sector);
+    gnutls_cipher_set_iv (cipher, iv, ivlen);
+    r = gnutls_cipher_encrypt2 (cipher,
+                                buf, LUKS_SECTOR_SIZE, /* plaintext */
+                                buf, LUKS_SECTOR_SIZE  /* ciphertext */);
+    if (r != 0) {
+      nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r));
+      return -1;
+    }
+
+    buf += LUKS_SECTOR_SIZE;
+    nr_sectors--;
+    sector++;
+  }
+
+  return 0;
+}
diff --git a/filters/luks/luks-encryption.h b/filters/luks/luks-encryption.h
new file mode 100644
index 00000000..3f8b9c9e
--- /dev/null
+++ b/filters/luks/luks-encryption.h
@@ -0,0 +1,78 @@
+/* nbdkit
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT 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 RED HAT 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.
+ */
+
+/* This header file defines the file format used by LUKSv1.  See also:
+ * https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf
+ * Note we do not yet support LUKSv2.
+ */
+
+#ifndef NBDKIT_LUKS_ENCRYPTION_H
+#define NBDKIT_LUKS_ENCRYPTION_H
+
+#include <stdint.h>
+#include <gnutls/crypto.h>
+
+#define LUKS_SECTOR_SIZE 512
+
+/* Per-connection data. */
+struct luks_data;
+
+/* Load the LUKS header, parse the algorithms, unlock the masterkey
+ * using the passphrase, initialize all the fields in the handle.
+ *
+ * This function may call next->pread (many times).
+ */
+extern struct luks_data *load_header (nbdkit_next *next,
+                                      const char *passphrase);
+
+/* Free the handle and all fields inside it. */
+extern void free_luks_data (struct luks_data *h);
+
+/* Get the offset where the encrypted data starts (in sectors). */
+extern uint64_t get_payload_offset (struct luks_data *h);
+
+/* Create an GnuTLS cipher, initialized with the master key.  Must be
+ * freed by the caller using gnutls_cipher_deinit.
+ */
+extern gnutls_cipher_hd_t create_cipher (struct luks_data *h);
+
+/* Perform decryption/encryption of a block of memory in-place.
+ *
+ * 'sector' is the sector number on disk, used to calculate IVs.  (The
+ * keyslots also use these functions, but sector must be 0).
+ */
+extern int do_decrypt (struct luks_data *h, gnutls_cipher_hd_t cipher,
+                       uint64_t sector, uint8_t *buf, size_t nr_sectors);
+extern int do_encrypt (struct luks_data *h, gnutls_cipher_hd_t cipher,
+                       uint64_t sector, uint8_t *buf, size_t nr_sectors);
+
+#endif /* NBDKIT_LUKS_ENCRYPTION_H */
diff --git a/filters/luks/luks.c b/filters/luks/luks.c
index cc619698..8ad3f4ec 100644
--- a/filters/luks/luks.c
+++ b/filters/luks/luks.c
@@ -45,49 +45,11 @@
 
 #include <nbdkit-filter.h>
 
-#include "byte-swapping.h"
+#include "luks-encryption.h"
+
 #include "cleanup.h"
 #include "isaligned.h"
 #include "minmax.h"
-#include "rounding.h"
-
-/* LUKSv1 constants. */
-#define LUKS_MAGIC { 'L', 'U', 'K', 'S', 0xBA, 0xBE }
-#define LUKS_MAGIC_LEN 6
-#define LUKS_DIGESTSIZE 20
-#define LUKS_SALTSIZE 32
-#define LUKS_NUMKEYS 8
-#define LUKS_KEY_DISABLED 0x0000DEAD
-#define LUKS_KEY_ENABLED  0x00AC71F3
-#define LUKS_STRIPES 4000
-#define LUKS_ALIGN_KEYSLOTS 4096
-#define LUKS_SECTOR_SIZE 512
-
-/* Key slot. */
-struct luks_keyslot {
-  uint32_t active;              /* LUKS_KEY_DISABLED|LUKS_KEY_ENABLED */
-  uint32_t password_iterations;
-  char password_salt[LUKS_SALTSIZE];
-  uint32_t key_material_offset;
-  uint32_t stripes;
-} __attribute__((__packed__));
-
-/* LUKS superblock. */
-struct luks_phdr {
-  char magic[LUKS_MAGIC_LEN];   /* LUKS_MAGIC */
-  uint16_t version;             /* Only 1 is supported. */
-  char cipher_name[32];
-  char cipher_mode[32];
-  char hash_spec[32];
-  uint32_t payload_offset;
-  uint32_t master_key_len;
-  uint8_t master_key_digest[LUKS_DIGESTSIZE];
-  uint8_t master_key_salt[LUKS_SALTSIZE];
-  uint32_t master_key_digest_iterations;
-  uint8_t uuid[40];
-
-  struct luks_keyslot keyslot[LUKS_NUMKEYS]; /* Key slots. */
-} __attribute__((__packed__));
 
 static char *passphrase = NULL;
 
@@ -135,251 +97,9 @@ luks_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata)
 #define luks_config_help \
   "passphrase=<SECRET>      Secret passphrase."
 
-enum cipher_mode {
-  CIPHER_MODE_ECB, CIPHER_MODE_CBC, CIPHER_MODE_XTS, CIPHER_MODE_CTR,
-};
-
-static enum cipher_mode
-lookup_cipher_mode (const char *str)
-{
-  if (strcmp (str, "ecb") == 0)
-    return CIPHER_MODE_ECB;
-  if (strcmp (str, "cbc") == 0)
-    return CIPHER_MODE_CBC;
-  if (strcmp (str, "xts") == 0)
-    return CIPHER_MODE_XTS;
-  if (strcmp (str, "ctr") == 0)
-    return CIPHER_MODE_CTR;
-  nbdkit_error ("unknown cipher mode: %s "
-                "(expecting \"ecb\", \"cbc\", \"xts\" or \"ctr\")", str);
-  return -1;
-}
-
-static const char *
-cipher_mode_to_string (enum cipher_mode v)
-{
-  switch (v) {
-  case CIPHER_MODE_ECB: return "ecb";
-  case CIPHER_MODE_CBC: return "cbc";
-  case CIPHER_MODE_XTS: return "xts";
-  case CIPHER_MODE_CTR: return "ctr";
-  default: abort ();
-  }
-}
-
-enum ivgen {
-  IVGEN_PLAIN, IVGEN_PLAIN64, /* IVGEN_ESSIV, */
-};
-
-static enum ivgen
-lookup_ivgen (const char *str)
-{
-  if (strcmp (str, "plain") == 0)
-    return IVGEN_PLAIN;
-  if (strcmp (str, "plain64") == 0)
-    return IVGEN_PLAIN64;
-/*
-  if (strcmp (str, "essiv") == 0)
-    return IVGEN_ESSIV;
-*/
-  nbdkit_error ("unknown IV generation algorithm: %s "
-                "(expecting \"plain\", \"plain64\" etc)", str);
-  return -1;
-}
-
-static const char *
-ivgen_to_string (enum ivgen v)
-{
-  switch (v) {
-  case IVGEN_PLAIN: return "plain";
-  case IVGEN_PLAIN64: return "plain64";
-  /*case IVGEN_ESSIV: return "essiv";*/
-  default: abort ();
-  }
-}
-
-static void
-calculate_iv (enum ivgen v, uint8_t *iv, size_t ivlen, uint64_t sector)
-{
-  size_t prefixlen;
-  uint32_t sector32;
-
-  switch (v) {
-  case IVGEN_PLAIN:
-    prefixlen = 4; /* 32 bits */
-    if (prefixlen > ivlen)
-      prefixlen = ivlen;
-    sector32 = (uint32_t) sector; /* truncate to only lower bits */
-    sector32 = htole32 (sector32);
-    memcpy (iv, &sector32, prefixlen);
-    memset (iv + prefixlen, 0, ivlen - prefixlen);
-    break;
-
-  case IVGEN_PLAIN64:
-    prefixlen = 8; /* 64 bits */
-    if (prefixlen > ivlen)
-      prefixlen = ivlen;
-    sector = htole64 (sector);
-    memcpy (iv, &sector, prefixlen);
-    memset (iv + prefixlen, 0, ivlen - prefixlen);
-    break;
-
-  /*case IVGEN_ESSIV:*/
-  default: abort ();
-  }
-}
-
-enum cipher_alg {
-  CIPHER_ALG_AES_128, CIPHER_ALG_AES_192, CIPHER_ALG_AES_256,
-};
-
-static enum cipher_alg
-lookup_cipher_alg (const char *str, enum cipher_mode mode, int key_bytes)
-{
-  if (mode == CIPHER_MODE_XTS)
-    key_bytes /= 2;
-
-  if (strcmp (str, "aes") == 0) {
-    if (key_bytes == 16)
-      return CIPHER_ALG_AES_128;
-    if (key_bytes == 24)
-      return CIPHER_ALG_AES_192;
-    if (key_bytes == 32)
-      return CIPHER_ALG_AES_256;
-  }
-  nbdkit_error ("unknown cipher algorithm: %s (expecting \"aes\", etc)", str);
-  return -1;
-}
-
-static const char *
-cipher_alg_to_string (enum cipher_alg v)
-{
-  switch (v) {
-  case CIPHER_ALG_AES_128: return "aes-128";
-  case CIPHER_ALG_AES_192: return "aes-192";
-  case CIPHER_ALG_AES_256: return "aes-256";
-  default: abort ();
-  }
-}
-
-#if 0
-static int
-cipher_alg_key_bytes (enum cipher_alg v)
-{
-  switch (v) {
-  case CIPHER_ALG_AES_128: return 16;
-  case CIPHER_ALG_AES_192: return 24;
-  case CIPHER_ALG_AES_256: return 32;
-  default: abort ();
-  }
-}
-#endif
-
-static int
-cipher_alg_iv_len (enum cipher_alg v, enum cipher_mode mode)
-{
-  if (CIPHER_MODE_ECB)
-    return 0;                   /* Don't need an IV in this mode. */
-
-  switch (v) {
-  case CIPHER_ALG_AES_128:
-  case CIPHER_ALG_AES_192:
-  case CIPHER_ALG_AES_256:
-    return 16;
-  default: abort ();
-  }
-}
-
-static gnutls_digest_algorithm_t
-lookup_hash (const char *str)
-{
-  if (strcmp (str, "md5") == 0)
-    return GNUTLS_DIG_MD5;
-  if (strcmp (str, "sha1") == 0)
-    return GNUTLS_DIG_SHA1;
-  if (strcmp (str, "sha224") == 0)
-    return GNUTLS_DIG_SHA224;
-  if (strcmp (str, "sha256") == 0)
-    return GNUTLS_DIG_SHA256;
-  if (strcmp (str, "sha384") == 0)
-    return GNUTLS_DIG_SHA384;
-  if (strcmp (str, "sha512") == 0)
-    return GNUTLS_DIG_SHA512;
-  if (strcmp (str, "ripemd160") == 0)
-    return GNUTLS_DIG_RMD160;
-  nbdkit_error ("unknown hash algorithm: %s "
-                "(expecting \"md5\", \"sha1\", \"sha224\", etc)", str);
-  return -1;
-}
-
-static const char *
-hash_to_string (gnutls_digest_algorithm_t v)
-{
-  switch (v) {
-  case GNUTLS_DIG_UNKNOWN: return "unknown";
-  case GNUTLS_DIG_MD5: return "md5";
-  case GNUTLS_DIG_SHA1: return "sha1";
-  case GNUTLS_DIG_SHA224: return "sha224";
-  case GNUTLS_DIG_SHA256: return "sha256";
-  case GNUTLS_DIG_SHA384: return "sha384";
-  case GNUTLS_DIG_SHA512: return "sha512";
-  case GNUTLS_DIG_RMD160: return "ripemd160";
-  default: abort ();
-  }
-}
-
-#if 0
-/* See qemu & dm-crypt implementations for an explanation of what's
- * going on here.
- */
-static enum cipher_alg
-lookup_essiv_cipher (enum cipher_alg cipher_alg,
-                     gnutls_digest_algorithm_t ivgen_hash_alg)
-{
-  int digest_bytes = gnutls_hash_get_len (ivgen_hash_alg);
-  int key_bytes = cipher_alg_key_bytes (cipher_alg);
-
-  if (digest_bytes == key_bytes)
-    return cipher_alg;
-
-  switch (cipher_alg) {
-  case CIPHER_ALG_AES_128:
-  case CIPHER_ALG_AES_192:
-  case CIPHER_ALG_AES_256:
-    if (digest_bytes == 16) return CIPHER_ALG_AES_128;
-    if (digest_bytes == 24) return CIPHER_ALG_AES_192;
-    if (digest_bytes == 32) return CIPHER_ALG_AES_256;
-    nbdkit_error ("no %s cipher available with key size %d",
-                  "AES", digest_bytes);
-    return -1;
-  default:
-    nbdkit_error ("ESSIV does not support cipher %s",
-                  cipher_alg_to_string (cipher_alg));
-    return -1;
-  }
-}
-#endif
-
 /* Per-connection handle. */
 struct handle {
-  /* LUKS header, if necessary byte-swapped into host order. */
-  struct luks_phdr phdr;
-
-  /* Decoded algorithm etc. */
-  enum cipher_alg cipher_alg;
-  enum cipher_mode cipher_mode;
-  gnutls_digest_algorithm_t hash_alg;
-  enum ivgen ivgen_alg;
-  gnutls_digest_algorithm_t ivgen_hash_alg;
-  enum cipher_alg ivgen_cipher_alg;
-
-  /* GnuTLS algorithm. */
-  gnutls_cipher_algorithm_t gnutls_cipher;
-
-  /* If we managed to decrypt one of the keyslots using the passphrase
-   * then this contains the master key, otherwise NULL.
-   */
-  uint8_t *masterkey;
+  struct luks_data *h;
 };
 
 static void *
@@ -405,536 +125,21 @@ luks_close (void *handle)
 {
   struct handle *h = handle;
 
-  if (h->masterkey) {
-    memset (h->masterkey, 0, h->phdr.master_key_len);
-    free (h->masterkey);
-  }
+  free_luks_data (h->h);
   free (h);
 }
 
-/* Perform decryption of a block of data in memory. */
-static int
-do_decrypt (struct handle *h, gnutls_cipher_hd_t cipher,
-            uint64_t offset, uint8_t *buf, size_t len)
-{
-  const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode);
-  uint64_t sector = offset / LUKS_SECTOR_SIZE;
-  CLEANUP_FREE uint8_t *iv = malloc (ivlen);
-  int r;
-
-  assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE));
-  assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE));
-
-  while (len) {
-    calculate_iv (h->ivgen_alg, iv, ivlen, sector);
-    gnutls_cipher_set_iv (cipher, iv, ivlen);
-    r = gnutls_cipher_decrypt2 (cipher,
-                                buf, LUKS_SECTOR_SIZE, /* ciphertext */
-                                buf, LUKS_SECTOR_SIZE  /* plaintext */);
-    if (r != 0) {
-      nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r));
-      return -1;
-    }
-
-    buf += LUKS_SECTOR_SIZE;
-    offset += LUKS_SECTOR_SIZE;
-    len -= LUKS_SECTOR_SIZE;
-    sector++;
-  }
-
-  return 0;
-}
-
-/* Perform encryption of a block of data in memory. */
-static int
-do_encrypt (struct handle *h, gnutls_cipher_hd_t cipher,
-            uint64_t offset, uint8_t *buf, size_t len)
-{
-  const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode);
-  uint64_t sector = offset / LUKS_SECTOR_SIZE;
-  CLEANUP_FREE uint8_t *iv = malloc (ivlen);
-  int r;
-
-  assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE));
-  assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE));
-
-  while (len) {
-    calculate_iv (h->ivgen_alg, iv, ivlen, sector);
-    gnutls_cipher_set_iv (cipher, iv, ivlen);
-    r = gnutls_cipher_encrypt2 (cipher,
-                                buf, LUKS_SECTOR_SIZE, /* plaintext */
-                                buf, LUKS_SECTOR_SIZE  /* ciphertext */);
-    if (r != 0) {
-      nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r));
-      return -1;
-    }
-
-    buf += LUKS_SECTOR_SIZE;
-    offset += LUKS_SECTOR_SIZE;
-    len -= LUKS_SECTOR_SIZE;
-    sector++;
-  }
-
-  return 0;
-}
-
-/* Parse the header fields containing cipher algorithm, mode, etc. */
-static int
-parse_cipher_strings (struct handle *h)
-{
-  char cipher_name[33], cipher_mode[33], hash_spec[33];
-  char *ivgen, *ivhash;
-
-  /* Copy the header fields locally and ensure they are \0 terminated. */
-  memcpy (cipher_name, h->phdr.cipher_name, 32);
-  cipher_name[32] = 0;
-  memcpy (cipher_mode, h->phdr.cipher_mode, 32);
-  cipher_mode[32] = 0;
-  memcpy (hash_spec, h->phdr.hash_spec, 32);
-  hash_spec[32] = 0;
-
-  nbdkit_debug ("LUKS v%" PRIu16 " cipher: %s mode: %s hash: %s "
-                "master key: %" PRIu32 " bits",
-                h->phdr.version, cipher_name, cipher_mode, hash_spec,
-                h->phdr.master_key_len * 8);
-
-  /* The cipher_mode header has the form: "ciphermode-ivgen[:ivhash]"
-   * QEmu writes: "xts-plain64"
-   */
-  ivgen = strchr (cipher_mode, '-');
-  if (!ivgen) {
-    nbdkit_error ("incorrect cipher_mode header, "
-                  "expecting mode-ivgenerator but got \"%s\"", cipher_mode);
-    return -1;
-  }
-  *ivgen = '\0';
-  ivgen++;
-
-  ivhash = strchr (ivgen, ':');
-  if (!ivhash)
-    h->ivgen_hash_alg = GNUTLS_DIG_UNKNOWN;
-  else {
-    *ivhash = '\0';
-    ivhash++;
-
-    h->ivgen_hash_alg = lookup_hash (ivhash);
-    if (h->ivgen_hash_alg == -1)
-      return -1;
-  }
-
-  h->cipher_mode = lookup_cipher_mode (cipher_mode);
-  if (h->cipher_mode == -1)
-    return -1;
-
-  h->cipher_alg = lookup_cipher_alg (cipher_name, h->cipher_mode,
-                                     h->phdr.master_key_len);
-  if (h->cipher_alg == -1)
-    return -1;
-
-  h->hash_alg = lookup_hash (hash_spec);
-  if (h->hash_alg == -1)
-    return -1;
-
-  h->ivgen_alg = lookup_ivgen (ivgen);
-  if (h->ivgen_alg == -1)
-    return -1;
-
-#if 0
-  if (h->ivgen_alg == IVGEN_ESSIV) {
-    if (!ivhash) {
-      nbdkit_error ("incorrect IV generator hash specification");
-      return -1;
-    }
-    h->ivgen_cipher_alg = lookup_essiv_cipher (h->cipher_alg,
-                                               h->ivgen_hash_alg);
-    if (h->ivgen_cipher_alg == -1)
-      return -1;
-  }
-  else
-#endif
-  h->ivgen_cipher_alg = h->cipher_alg;
-
-  nbdkit_debug ("LUKS parsed ciphers: %s %s %s %s %s %s",
-                cipher_alg_to_string (h->cipher_alg),
-                cipher_mode_to_string (h->cipher_mode),
-                hash_to_string (h->hash_alg),
-                ivgen_to_string (h->ivgen_alg),
-                hash_to_string (h->ivgen_hash_alg),
-                cipher_alg_to_string (h->ivgen_cipher_alg));
-
-  /* GnuTLS combines cipher and block mode into a single value.  Not
-   * all possible combinations are available in GnuTLS.  See:
-   * https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html
-   */
-  h->gnutls_cipher = GNUTLS_CIPHER_NULL;
-  switch (h->cipher_mode) {
-  case CIPHER_MODE_XTS:
-    switch (h->cipher_alg) {
-    case CIPHER_ALG_AES_128:
-      h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS;
-      break;
-    case CIPHER_ALG_AES_256:
-      h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS;
-      break;
-    default: break;
-    }
-    break;
-  case CIPHER_MODE_CBC:
-    switch (h->cipher_alg) {
-    case CIPHER_ALG_AES_128:
-      h->gnutls_cipher = GNUTLS_CIPHER_AES_128_CBC;
-      break;
-    case CIPHER_ALG_AES_192:
-      h->gnutls_cipher = GNUTLS_CIPHER_AES_192_CBC;
-      break;
-    case CIPHER_ALG_AES_256:
-      h->gnutls_cipher = GNUTLS_CIPHER_AES_256_CBC;
-      break;
-    default: break;
-    }
-  default: break;
-  }
-  if (h->gnutls_cipher == GNUTLS_CIPHER_NULL) {
-    nbdkit_error ("cipher algorithm %s in mode %s is not supported by GnuTLS",
-                  cipher_alg_to_string (h->cipher_alg),
-                  cipher_mode_to_string (h->cipher_mode));
-    return -1;
-  }
-
-  return 0;
-}
-
-/* Anti-Forensic merge operation. */
-static void
-xor (const uint8_t *in1, const uint8_t *in2, uint8_t *out, size_t len)
-{
-  size_t i;
-
-  for (i = 0; i < len; ++i)
-    out[i] = in1[i] ^ in2[i];
-}
-
-static int
-af_hash (gnutls_digest_algorithm_t hash_alg, uint8_t *block, size_t len)
-{
-  size_t digest_bytes = gnutls_hash_get_len (hash_alg);
-  size_t nr_blocks, last_block_len;
-  size_t i;
-  CLEANUP_FREE uint8_t *temp = malloc (digest_bytes);
-  int r;
-  gnutls_hash_hd_t hash;
-
-  nr_blocks = len / digest_bytes;
-  last_block_len = len % digest_bytes;
-  if (last_block_len != 0)
-    nr_blocks++;
-  else
-    last_block_len = digest_bytes;
-
-  for (i = 0; i < nr_blocks; ++i) {
-    const uint32_t iv = htobe32 (i);
-    const size_t blen = i < nr_blocks - 1 ? digest_bytes : last_block_len;
-
-    /* Hash iv + i'th block into temp. */
-    r = gnutls_hash_init (&hash, hash_alg);
-    if (r != 0) {
-      nbdkit_error ("gnutls_hash_init: %s", gnutls_strerror (r));
-      return -1;
-    }
-    gnutls_hash (hash, &iv, sizeof iv);
-    gnutls_hash (hash, &block[i*digest_bytes], blen);
-    gnutls_hash_deinit (hash, temp);
-
-    memcpy (&block[i*digest_bytes], temp, blen);
-  }
-
-  return 0;
-}
-
-static int
-afmerge (gnutls_digest_algorithm_t hash_alg, uint32_t stripes,
-         const uint8_t *in, uint8_t *out, size_t outlen)
-{
-  CLEANUP_FREE uint8_t *block = calloc (1, outlen);
-  size_t i;
-
-  /* NB: input size is stripes * master_key_len where
-   * master_key_len == outlen
-   */
-  for (i = 0; i < stripes-1; ++i) {
-    xor (&in[i*outlen], block, block, outlen);
-    if (af_hash (hash_alg, block, outlen) == -1)
-      return -1;
-  }
-  xor (&in[i*outlen], block, out, outlen);
-  return 0;
-}
-
-/* Length of key material in key slot i (sectors).
- *
- * This is basically copied from qemu because the spec description is
- * unintelligible and apparently doesn't match reality.
- */
-static uint64_t
-key_material_length_in_sectors (struct handle *h, size_t i)
-{
-  uint64_t len, r;
-
-  len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes;
-  r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE);
-  r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE);
-  return r;
-}
-
-/* Try the passphrase in key slot i.  If this returns true then the
- * passphrase was able to decrypt the master key, and the master key
- * has been stored in h->masterkey.
- */
-static int
-try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i)
-{
-  /* I believe this is supposed to be safe, looking at the GnuTLS
-   * header file.
-   */
-  const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg;
-  struct luks_keyslot *ks = &h->phdr.keyslot[i];
-  size_t split_key_len;
-  CLEANUP_FREE uint8_t *split_key = NULL;
-  CLEANUP_FREE uint8_t *masterkey = NULL;
-  const gnutls_datum_t key =
-    { (unsigned char *) passphrase, strlen (passphrase) };
-  const gnutls_datum_t salt =
-    { (unsigned char *) ks->password_salt, LUKS_SALTSIZE };
-  const gnutls_datum_t msalt =
-    { (unsigned char *) h->phdr.master_key_salt, LUKS_SALTSIZE };
-  gnutls_datum_t mkey;
-  gnutls_cipher_hd_t cipher;
-  int r, err = 0;
-  uint64_t start;
-  uint8_t key_digest[LUKS_DIGESTSIZE];
-
-  if (ks->active != LUKS_KEY_ENABLED)
-    return 0;
-
-  split_key_len = h->phdr.master_key_len * ks->stripes;
-  split_key = malloc (split_key_len);
-  if (split_key == NULL) {
-    nbdkit_error ("malloc: %m");
-    return -1;
-  }
-  masterkey = malloc (h->phdr.master_key_len);
-  if (masterkey == NULL) {
-    nbdkit_error ("malloc: %m");
-    return -1;
-  }
-
-  /* Hash the passphrase to make a possible masterkey. */
-  r = gnutls_pbkdf2 (mac, &key, &salt, ks->password_iterations,
-                     masterkey, h->phdr.master_key_len);
-  if (r != 0) {
-    nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r));
-    return -1;
-  }
-
-  /* Read master key material from plugin. */
-  start = ks->key_material_offset * LUKS_SECTOR_SIZE;
-  if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) {
-    errno = err;
-    return -1;
-  }
-
-  /* Decrypt the (still AFsplit) master key material. */
-  mkey.data = (unsigned char *) masterkey;
-  mkey.size = h->phdr.master_key_len;
-  r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
-  if (r != 0) {
-    nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
-    return -1;
-  }
-
-  r = do_decrypt (h, cipher, 0, split_key, split_key_len);
-  gnutls_cipher_deinit (cipher);
-  if (r == -1)
-    return -1;
-
-  /* Decode AFsplit key to a possible masterkey. */
-  if (afmerge (h->hash_alg, ks->stripes, split_key,
-               masterkey, h->phdr.master_key_len) == -1)
-    return -1;
-
-  /* Check if the masterkey is correct by comparing hash of the
-   * masterkey with LUKS header.
-   */
-  r = gnutls_pbkdf2 (mac, &mkey, &msalt,
-                     h->phdr.master_key_digest_iterations,
-                     key_digest, LUKS_DIGESTSIZE);
-  if (r != 0) {
-    nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r));
-    return -1;
-  }
-
-  if (memcmp (key_digest, h->phdr.master_key_digest, LUKS_DIGESTSIZE) == 0) {
-    /* The passphrase is correct so save the master key in the handle. */
-    h->masterkey = malloc (h->phdr.master_key_len);
-    if (h->masterkey == NULL) {
-      nbdkit_error ("malloc: %m");
-      return -1;
-    }
-    memcpy (h->masterkey, masterkey, h->phdr.master_key_len);
-    return 1;
-  }
-
-  return 0;
-}
-
 static int
 luks_prepare (nbdkit_next *next, void *handle, int readonly)
 {
-  static const char expected_magic[] = LUKS_MAGIC;
   struct handle *h = handle;
-  int64_t size;
-  int err = 0, r;
-  size_t i;
-  struct luks_keyslot *ks;
-  char uuid[41];
 
   /* Check we haven't been called before, this should never happen. */
-  assert (h->phdr.version == 0);
+  assert (h->h == NULL);
 
-  /* Check the struct size matches the documentation. */
-  assert (sizeof (struct luks_phdr) == 592);
-
-  /* Check this is a LUKSv1 disk. */
-  size = next->get_size (next);
-  if (size == -1)
-    return -1;
-  if (size < 16384) {
-    nbdkit_error ("disk is too small to be LUKS-encrypted");
-    return -1;
-  }
-
-  /* Read the phdr. */
-  if (next->pread (next, &h->phdr, sizeof h->phdr, 0, 0, &err) == -1) {
-    errno = err;
-    return -1;
-  }
-
-  if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) {
-    nbdkit_error ("this disk does not contain a LUKS header");
+  h->h = load_header (next, passphrase);
+  if (h->h == NULL)
     return -1;
-  }
-  h->phdr.version = be16toh (h->phdr.version);
-  if (h->phdr.version != 1) {
-    nbdkit_error ("this disk contains a LUKS version %" PRIu16 " header, "
-                  "but this filter only supports LUKSv1",
-                  h->phdr.version);
-    return -1;
-  }
-
-  /* Byte-swap the rest of the header. */
-  h->phdr.payload_offset = be32toh (h->phdr.payload_offset);
-  h->phdr.master_key_len = be32toh (h->phdr.master_key_len);
-  h->phdr.master_key_digest_iterations =
-    be32toh (h->phdr.master_key_digest_iterations);
-
-  for (i = 0; i < LUKS_NUMKEYS; ++i) {
-    ks = &h->phdr.keyslot[i];
-    ks->active = be32toh (ks->active);
-    ks->password_iterations = be32toh (ks->password_iterations);
-    ks->key_material_offset = be32toh (ks->key_material_offset);
-    ks->stripes = be32toh (ks->stripes);
-  }
-
-  /* Sanity check some fields. */
-  if (h->phdr.payload_offset >= size / LUKS_SECTOR_SIZE) {
-    nbdkit_error ("bad LUKSv1 header: payload offset points beyond "
-                  "the end of the disk");
-    return -1;
-  }
-
-  /* We derive several allocations from master_key_len so make sure
-   * it's not insane.
-   */
-  if (h->phdr.master_key_len > 1024) {
-    nbdkit_error ("bad LUKSv1 header: master key is too long");
-    return -1;
-  }
-
-  for (i = 0; i < LUKS_NUMKEYS; ++i) {
-    uint64_t start, len;
-
-    ks = &h->phdr.keyslot[i];
-    switch (ks->active) {
-    case LUKS_KEY_ENABLED:
-      if (!ks->stripes) {
-        nbdkit_error ("bad LUKSv1 header: key slot %zu is corrupted", i);
-        return -1;
-      }
-      if (ks->stripes >= 10000) {
-        nbdkit_error ("bad LUKSv1 header: key slot %zu stripes too large", i);
-        return -1;
-      }
-      start = ks->key_material_offset;
-      len = key_material_length_in_sectors (h, i);
-      if (len > 4096) /* bound it at something reasonable */ {
-        nbdkit_error ("bad LUKSv1 header: key slot %zu key material length "
-                      "is too large", i);
-        return -1;
-      }
-      if (start * LUKS_SECTOR_SIZE >= size ||
-          (start + len) * LUKS_SECTOR_SIZE >= size) {
-        nbdkit_error ("bad LUKSv1 header: key slot %zu key material offset "
-                      "points beyond the end of the disk", i);
-        return -1;
-      }
-      /*FALLTHROUGH*/
-    case LUKS_KEY_DISABLED:
-      break;
-
-    default:
-      nbdkit_error ("bad LUKSv1 header: key slot %zu has "
-                    "an invalid active flag", i);
-      return -1;
-    }
-  }
-
-  /* Decode the ciphers. */
-  if (parse_cipher_strings (h) == -1)
-    return -1;
-
-  /* Dump some information about the header. */
-  memcpy (uuid, h->phdr.uuid, 40);
-  uuid[40] = 0;
-  nbdkit_debug ("LUKS UUID: %s", uuid);
-
-  for (i = 0; i < LUKS_NUMKEYS; ++i) {
-    uint64_t start, len;
-
-    ks = &h->phdr.keyslot[i];
-    if (ks->active == LUKS_KEY_ENABLED) {
-      start = ks->key_material_offset;
-      len = key_material_length_in_sectors (h, i);
-      nbdkit_debug ("LUKS key slot %zu: key material in sectors %" PRIu64
-                    "..%" PRIu64,
-                    i, start, start+len-1);
-    }
-  }
-
-  /* Now try to unlock the master key. */
-  for (i = 0; i < LUKS_NUMKEYS; ++i) {
-    r = try_passphrase_in_keyslot (next, h, i);
-    if (r == -1)
-      return -1;
-    if (r > 0)
-      goto unlocked;
-  }
-  nbdkit_error ("LUKS passphrase is not correct, "
-                "no key slot could be unlocked");
-  return -1;
-
- unlocked:
-  assert (h->masterkey != NULL);
-  nbdkit_debug ("LUKS unlocked block device with passphrase");
 
   return 0;
 }
@@ -946,19 +151,20 @@ luks_get_size (nbdkit_next *next, void *handle)
   int64_t size;
 
   /* Check that prepare has been called already. */
-  assert (h->phdr.version > 0);
+  assert (h->h != NULL);
+
+  const uint64_t payload_offset = get_payload_offset (h->h) * LUKS_SECTOR_SIZE;
 
   size = next->get_size (next);
   if (size == -1)
     return -1;
 
-  if (size < h->phdr.payload_offset * LUKS_SECTOR_SIZE) {
+  if (size < payload_offset) {
     nbdkit_error ("disk too small, or contains an incomplete LUKS partition");
     return -1;
   }
 
-  size -= h->phdr.payload_offset * LUKS_SECTOR_SIZE;
-  return size;
+  return size - payload_offset;
 }
 
 /* Whatever the plugin says, several operations are not supported by
@@ -1031,15 +237,12 @@ luks_pread (nbdkit_next *next, void *handle,
             uint32_t flags, int *err)
 {
   struct handle *h = handle;
-  const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE;
+  const uint64_t payload_offset = get_payload_offset (h->h) * LUKS_SECTOR_SIZE;
   CLEANUP_FREE uint8_t *sector = NULL;
   uint64_t sectnum, sectoffs;
-  const gnutls_datum_t mkey =
-    { (unsigned char *) h->masterkey, h->phdr.master_key_len };
   gnutls_cipher_hd_t cipher;
-  int r;
 
-  if (!h->masterkey) {
+  if (!h->h) {
     *err = EIO;
     return -1;
   }
@@ -1053,16 +256,13 @@ luks_pread (nbdkit_next *next, void *handle,
     }
   }
 
-  r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
-  if (r != 0) {
-    nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
-    *err = EIO;
-    return -1;
-  }
-
   sectnum = offset / LUKS_SECTOR_SIZE;  /* sector number */
   sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */
 
+  cipher = create_cipher (h->h);
+  if (!cipher)
+    return -1;
+
   /* Unaligned head */
   if (sectoffs) {
     uint64_t n = MIN (LUKS_SECTOR_SIZE - sectoffs, count);
@@ -1073,15 +273,13 @@ luks_pread (nbdkit_next *next, void *handle,
                      flags, err) == -1)
       goto err;
 
-    if (do_decrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE,
-                    sector, LUKS_SECTOR_SIZE) == -1)
+    if (do_decrypt (h->h, cipher, sectnum, sector, 1) == -1)
       goto err;
 
     memcpy (buf, &sector[sectoffs], n);
 
     buf += n;
     count -= n;
-    offset += n;
     sectnum++;
   }
 
@@ -1092,12 +290,11 @@ luks_pread (nbdkit_next *next, void *handle,
                      flags, err) == -1)
       goto err;
 
-    if (do_decrypt (h, cipher, offset, buf, LUKS_SECTOR_SIZE) == -1)
+    if (do_decrypt (h->h, cipher, sectnum, buf, 1) == -1)
       goto err;
 
     buf += LUKS_SECTOR_SIZE;
     count -= LUKS_SECTOR_SIZE;
-    offset += LUKS_SECTOR_SIZE;
     sectnum++;
   }
 
@@ -1109,7 +306,7 @@ luks_pread (nbdkit_next *next, void *handle,
                      flags, err) == -1)
       goto err;
 
-    if (do_decrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1)
+    if (do_decrypt (h->h, cipher, sectnum, sector, 1) == -1)
       goto err;
 
     memcpy (buf, sector, count);
@@ -1120,7 +317,7 @@ luks_pread (nbdkit_next *next, void *handle,
 
  err:
   gnutls_cipher_deinit (cipher);
-  return -1;
+  goto err;
 }
 
 /* Lock preventing read-modify-write cycles from overlapping. */
@@ -1133,15 +330,12 @@ luks_pwrite (nbdkit_next *next, void *handle,
              uint32_t flags, int *err)
 {
   struct handle *h = handle;
-  const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE;
+  const uint64_t payload_offset = get_payload_offset (h->h) * LUKS_SECTOR_SIZE;
   CLEANUP_FREE uint8_t *sector = NULL;
   uint64_t sectnum, sectoffs;
-  const gnutls_datum_t mkey =
-    { (unsigned char *) h->masterkey, h->phdr.master_key_len };
   gnutls_cipher_hd_t cipher;
-  int r;
 
-  if (!h->masterkey) {
+  if (!h->h) {
     *err = EIO;
     return -1;
   }
@@ -1153,16 +347,13 @@ luks_pwrite (nbdkit_next *next, void *handle,
     return -1;
   }
 
-  r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL);
-  if (r != 0) {
-    nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r));
-    *err = EIO;
-    return -1;
-  }
-
   sectnum = offset / LUKS_SECTOR_SIZE;  /* sector number */
   sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */
 
+  cipher = create_cipher (h->h);
+  if (!cipher)
+    return -1;
+
   /* Unaligned head */
   if (sectoffs) {
     ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&read_modify_write_lock);
@@ -1176,8 +367,7 @@ luks_pwrite (nbdkit_next *next, void *handle,
 
     memcpy (&sector[sectoffs], buf, n);
 
-    if (do_encrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE,
-                    sector, LUKS_SECTOR_SIZE) == -1)
+    if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1)
       goto err;
 
     if (next->pwrite (next, sector, LUKS_SECTOR_SIZE,
@@ -1187,7 +377,6 @@ luks_pwrite (nbdkit_next *next, void *handle,
 
     buf += n;
     count -= n;
-    offset += n;
     sectnum++;
   }
 
@@ -1195,7 +384,7 @@ luks_pwrite (nbdkit_next *next, void *handle,
   while (count >= LUKS_SECTOR_SIZE) {
     memcpy (sector, buf, LUKS_SECTOR_SIZE);
 
-    if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1)
+    if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1)
       goto err;
 
     if (next->pwrite (next, sector, LUKS_SECTOR_SIZE,
@@ -1205,7 +394,6 @@ luks_pwrite (nbdkit_next *next, void *handle,
 
     buf += LUKS_SECTOR_SIZE;
     count -= LUKS_SECTOR_SIZE;
-    offset += LUKS_SECTOR_SIZE;
     sectnum++;
   }
 
@@ -1220,7 +408,7 @@ luks_pwrite (nbdkit_next *next, void *handle,
 
     memcpy (sector, buf, count);
 
-    if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1)
+    if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1)
       goto err;
 
     if (next->pwrite (next, sector, LUKS_SECTOR_SIZE,
-- 
2.31.1