From 700bab46aa6772cd56cc697d2d68daf45de68d56 Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Sat, 27 Jun 2020 22:09:25 +0900
Subject: [PATCH] Reuse cipher context

---
 Makefile.am                            |   2 +-
 crypto/gnutls/gnutls.c                 | 158 ++++++++-----
 crypto/includes/ngtcp2/ngtcp2_crypto.h | 117 ++++++++--
 crypto/openssl/openssl.c               | 167 ++++++++-----
 crypto/shared.c                        | 264 ++++++++++++++++-----
 crypto/shared.h                        |  23 +-
 examples/client.cc                     |  34 ++-
 examples/client.h                      |   5 +-
 examples/server.cc                     | 104 +++++++--
 examples/server.h                      |   5 +-
 lib/includes/ngtcp2/ngtcp2.h           | 294 ++++++++++++++++-------
 lib/ngtcp2_conn.c                      | 309 ++++++++++++++++---------
 lib/ngtcp2_conn.h                      |  16 +-
 lib/ngtcp2_crypto.c                    |  18 +-
 lib/ngtcp2_crypto.h                    |  10 +-
 lib/ngtcp2_pkt.c                       |  22 +-
 lib/ngtcp2_pkt.h                       |   3 +-
 lib/ngtcp2_ppe.c                       |   5 +-
 tests/ngtcp2_conn_test.c               | 223 ++++++++++--------
 tests/ngtcp2_pkt_test.c                |  15 +-
 tests/ngtcp2_test_helper.c             |  29 +--
 tests/ngtcp2_test_helper.h             |   4 +-
 22 files changed, 1238 insertions(+), 589 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 6c91d570..223f112a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -39,6 +39,6 @@ clang-format:
 	CLANGFORMAT=`git config --get clangformat.binary`; \
 	test -z $${CLANGFORMAT} && CLANGFORMAT="clang-format"; \
 	$${CLANGFORMAT} -i lib/*.{c,h} tests/*.{c,h} lib/includes/ngtcp2/*.h \
-	crypto/*.c crypto/openssl/*.c  crypto/gnutls/*.c \
+	crypto/*.{c,h} crypto/openssl/*.c  crypto/gnutls/*.c \
 	crypto/includes/ngtcp2/*.h \
 	examples/*.{cc,h}
diff --git a/crypto/gnutls/gnutls.c b/crypto/gnutls/gnutls.c
index 3d4dc8ed..4d19f860 100644
--- a/crypto/gnutls/gnutls.c
+++ b/crypto/gnutls/gnutls.c
@@ -145,6 +145,82 @@ size_t ngtcp2_crypto_aead_taglen(const ngtcp2_crypto_aead *aead) {
       (gnutls_cipher_algorithm_t)aead->native_handle);
 }
 
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  gnutls_cipher_algorithm_t cipher =
+      (gnutls_cipher_algorithm_t)aead->native_handle;
+  gnutls_aead_cipher_hd_t hd;
+  gnutls_datum_t _key;
+
+  (void)noncelen;
+
+  _key.data = (void *)key;
+  _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead);
+
+  if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = hd;
+
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  gnutls_cipher_algorithm_t cipher =
+      (gnutls_cipher_algorithm_t)aead->native_handle;
+  gnutls_aead_cipher_hd_t hd;
+  gnutls_datum_t _key;
+
+  (void)noncelen;
+
+  _key.data = (void *)key;
+  _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead);
+
+  if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = hd;
+
+  return 0;
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    gnutls_aead_cipher_deinit(aead_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  gnutls_cipher_algorithm_t _cipher =
+      (gnutls_cipher_algorithm_t)cipher->native_handle;
+  gnutls_cipher_hd_t hd;
+  gnutls_datum_t _key;
+
+  _key.data = (void *)key;
+  _key.size = (unsigned int)gnutls_cipher_get_key_size(_cipher);
+
+  if (gnutls_cipher_init(&hd, _cipher, &_key, NULL) != 0) {
+    return -1;
+  }
+
+  cipher_ctx->native_handle = hd;
+
+  return 0;
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (cipher_ctx->native_handle) {
+    gnutls_cipher_deinit(cipher_ctx->native_handle);
+  }
+}
+
 int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
                                const uint8_t *secret, size_t secretlen,
                                const uint8_t *salt, size_t saltlen) {
@@ -175,42 +251,34 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
 }
 
 int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
                           const uint8_t *plaintext, size_t plaintextlen,
-                          const uint8_t *key, const uint8_t *nonce,
-                          size_t noncelen, const uint8_t *ad, size_t adlen) {
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *ad, size_t adlen) {
   gnutls_cipher_algorithm_t cipher =
       (gnutls_cipher_algorithm_t)aead->native_handle;
-  gnutls_aead_cipher_hd_t hd = NULL;
-  gnutls_datum_t _key;
-  size_t taglen = ngtcp2_crypto_aead_taglen(aead);
+  gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle;
+  size_t taglen = gnutls_cipher_get_tag_size(cipher);
   size_t ciphertextlen = plaintextlen + taglen;
-  int rv = 0;
-
-  _key.data = (void *)key;
-  _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead);
 
-  if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0 ||
-      gnutls_aead_cipher_encrypt(hd, nonce, noncelen, ad, adlen, taglen,
+  if (gnutls_aead_cipher_encrypt(hd, nonce, noncelen, ad, adlen, taglen,
                                  plaintext, plaintextlen, dest,
                                  &ciphertextlen) != 0) {
-    rv = -1;
+    return -1;
   }
 
-  gnutls_aead_cipher_deinit(hd);
-
-  return rv;
+  return 0;
 }
 
 int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
                           const uint8_t *ciphertext, size_t ciphertextlen,
-                          const uint8_t *key, const uint8_t *nonce,
-                          size_t noncelen, const uint8_t *ad, size_t adlen) {
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *ad, size_t adlen) {
   gnutls_cipher_algorithm_t cipher =
       (gnutls_cipher_algorithm_t)aead->native_handle;
+  gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle;
   size_t taglen = gnutls_cipher_get_tag_size(cipher);
-  gnutls_aead_cipher_hd_t hd = NULL;
-  gnutls_datum_t _key;
-  int rv = 0;
   size_t plaintextlen;
 
   if (taglen > ciphertextlen) {
@@ -219,81 +287,59 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
 
   plaintextlen = ciphertextlen - taglen;
 
-  _key.data = (void *)key;
-  _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead);
-
-  if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0 ||
-      gnutls_aead_cipher_decrypt(hd, nonce, noncelen, ad, adlen, taglen,
+  if (gnutls_aead_cipher_decrypt(hd, nonce, noncelen, ad, adlen, taglen,
                                  ciphertext, ciphertextlen, dest,
                                  &plaintextlen) != 0) {
-    rv = -1;
+    return -1;
   }
 
-  gnutls_aead_cipher_deinit(hd);
-
-  return rv;
+  return 0;
 }
 
 int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-                          const uint8_t *hp_key, const uint8_t *sample) {
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
   gnutls_cipher_algorithm_t cipher =
       (gnutls_cipher_algorithm_t)hp->native_handle;
-  int rv = 0;
+  gnutls_cipher_hd_t hd = hp_ctx->native_handle;
 
   switch (cipher) {
   case GNUTLS_CIPHER_AES_128_CBC:
   case GNUTLS_CIPHER_AES_256_CBC: {
-    gnutls_cipher_hd_t hd = NULL;
-    gnutls_datum_t _hp_key;
     uint8_t iv[16];
-    gnutls_datum_t _iv;
     uint8_t buf[16];
 
-    _hp_key.data = (void *)hp_key;
-    _hp_key.size = (unsigned int)gnutls_cipher_get_key_size(cipher);
-
     /* Emulate one block AES-ECB by invalidating the effect of IV */
     memset(iv, 0, sizeof(iv));
-    _iv.data = iv;
-    _iv.size = 16;
 
-    if (gnutls_cipher_init(&hd, cipher, &_hp_key, &_iv) != 0 ||
-        gnutls_cipher_encrypt2(hd, sample, 16, buf, sizeof(buf)) != 0) {
-      rv = -1;
+    gnutls_cipher_set_iv(hd, iv, sizeof(iv));
+
+    if (gnutls_cipher_encrypt2(hd, sample, 16, buf, sizeof(buf)) != 0) {
+      return -1;
     }
 
     memcpy(dest, buf, 5);
-    gnutls_cipher_deinit(hd);
   } break;
 
   case GNUTLS_CIPHER_CHACHA20_32: {
-    gnutls_cipher_hd_t hd = NULL;
     static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
-    gnutls_datum_t _hp_key;
-    gnutls_datum_t _iv;
     uint8_t buf[5 + 16];
     size_t buflen = sizeof(buf);
 
-    _hp_key.data = (void *)hp_key;
-    _hp_key.size = (unsigned int)gnutls_cipher_get_key_size(cipher);
-
-    _iv.data = (void *)sample;
-    _iv.size = 16;
+    gnutls_cipher_set_iv(hd, (void *)sample, 16);
 
-    if (gnutls_cipher_init(&hd, cipher, &_hp_key, &_iv) != 0 ||
-        gnutls_cipher_encrypt2(hd, PLAINTEXT, sizeof(PLAINTEXT) - 1, buf,
+    if (gnutls_cipher_encrypt2(hd, PLAINTEXT, sizeof(PLAINTEXT) - 1, buf,
                                buflen) != 0) {
-      rv = -1;
+      return -1;
     }
 
     memcpy(dest, buf, 5);
-    gnutls_cipher_deinit(hd);
   } break;
   default:
     assert(0);
   }
 
-  return rv;
+  return 0;
 }
 
 static gnutls_record_encryption_level_t
diff --git a/crypto/includes/ngtcp2/ngtcp2_crypto.h b/crypto/includes/ngtcp2/ngtcp2_crypto.h
index 3197c5dc..4b6885a0 100644
--- a/crypto/includes/ngtcp2/ngtcp2_crypto.h
+++ b/crypto/includes/ngtcp2/ngtcp2_crypto.h
@@ -210,8 +210,9 @@ NGTCP2_EXTERN int ngtcp2_crypto_derive_packet_protection_key(
  */
 NGTCP2_EXTERN int ngtcp2_crypto_encrypt(uint8_t *dest,
                                         const ngtcp2_crypto_aead *aead,
+                                        const ngtcp2_crypto_aead_ctx *aead_ctx,
                                         const uint8_t *plaintext,
-                                        size_t plaintextlen, const uint8_t *key,
+                                        size_t plaintextlen,
                                         const uint8_t *nonce, size_t noncelen,
                                         const uint8_t *ad, size_t adlen);
 
@@ -227,9 +228,10 @@ NGTCP2_EXTERN int ngtcp2_crypto_encrypt(uint8_t *dest,
  */
 NGTCP2_EXTERN int
 ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
                          const uint8_t *plaintext, size_t plaintextlen,
-                         const uint8_t *key, const uint8_t *nonce,
-                         size_t noncelen, const uint8_t *ad, size_t adlen);
+                         const uint8_t *nonce, size_t noncelen,
+                         const uint8_t *ad, size_t adlen);
 
 /**
  * @function
@@ -243,11 +245,13 @@ ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
  *
  * This function returns 0 if it succeeds, or -1.
  */
-NGTCP2_EXTERN int
-ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
-                      const uint8_t *ciphertext, size_t ciphertextlen,
-                      const uint8_t *key, const uint8_t *nonce, size_t noncelen,
-                      const uint8_t *ad, size_t adlen);
+NGTCP2_EXTERN int ngtcp2_crypto_decrypt(uint8_t *dest,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const uint8_t *ciphertext,
+                                        size_t ciphertextlen,
+                                        const uint8_t *nonce, size_t noncelen,
+                                        const uint8_t *ad, size_t adlen);
 
 /**
  * @function
@@ -261,9 +265,10 @@ ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
  */
 NGTCP2_EXTERN int
 ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
                          const uint8_t *ciphertext, size_t ciphertextlen,
-                         const uint8_t *key, const uint8_t *nonce,
-                         size_t noncelen, const uint8_t *ad, size_t adlen);
+                         const uint8_t *nonce, size_t noncelen,
+                         const uint8_t *ad, size_t adlen);
 
 /**
  * @function
@@ -277,7 +282,7 @@ ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
  */
 NGTCP2_EXTERN int ngtcp2_crypto_hp_mask(uint8_t *dest,
                                         const ngtcp2_crypto_cipher *hp,
-                                        const uint8_t *key,
+                                        const ngtcp2_crypto_cipher_ctx *hp_ctx,
                                         const uint8_t *sample);
 
 /**
@@ -290,10 +295,10 @@ NGTCP2_EXTERN int ngtcp2_crypto_hp_mask(uint8_t *dest,
  * This function returns 0 if it succeeds, or
  * :enum:`NGTCP2_ERR_CALLBACK_FAILURE`.
  */
-NGTCP2_EXTERN int ngtcp2_crypto_hp_mask_cb(uint8_t *dest,
-                                           const ngtcp2_crypto_cipher *hp,
-                                           const uint8_t *key,
-                                           const uint8_t *sample);
+NGTCP2_EXTERN int
+ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                         const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                         const uint8_t *sample);
 
 /**
  * @function
@@ -381,10 +386,12 @@ NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_tx_key(
  * The derived packet protection key for decryption is written to the
  * buffer pointed by |rx_key|.  The derived packet protection IV for
  * decryption is written to the buffer pointed by |rx_iv|.
+ * |rx_aead_ctx| must be constructed with |rx_key|.
  *
  * The derived packet protection key for encryption is written to the
  * buffer pointed by |tx_key|.  The derived packet protection IV for
  * encryption is written to the buffer pointed by |tx_iv|.
+ * |tx_aead_ctx| must be constructed with |rx_key|.
  *
  * |current_rx_secret| and |current_tx_secret| are the current traffic
  * secrets for decryption and encryption.  |secretlen| specifies the
@@ -397,12 +404,12 @@ NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_tx_key(
  *
  * This function returns 0 if it succeeds, or -1.
  */
-NGTCP2_EXTERN int
-ngtcp2_crypto_update_key(ngtcp2_conn *conn, uint8_t *rx_secret,
-                         uint8_t *tx_secret, uint8_t *rx_key, uint8_t *rx_iv,
-                         uint8_t *tx_key, uint8_t *tx_iv,
-                         const uint8_t *current_rx_secret,
-                         const uint8_t *current_tx_secret, size_t secretlen);
+NGTCP2_EXTERN int ngtcp2_crypto_update_key(
+    ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+    ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv,
+    ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv,
+    const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+    size_t secretlen);
 
 /**
  * @function
@@ -415,8 +422,9 @@ ngtcp2_crypto_update_key(ngtcp2_conn *conn, uint8_t *rx_secret,
  * :enum:`NGTCP2_ERR_CALLBACK_FAILURE`.
  */
 NGTCP2_EXTERN int ngtcp2_crypto_update_key_cb(
-    ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, uint8_t *rx_key,
-    uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
+    ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+    ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+    ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
     const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
     size_t secretlen, void *user_data);
 
@@ -543,6 +551,69 @@ ngtcp2_crypto_write_retry(uint8_t *dest, size_t destlen, const ngtcp2_cid *dcid,
                           const ngtcp2_cid *scid, const ngtcp2_cid *odcid,
                           const uint8_t *token, size_t tokenlen);
 
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_ctx_encrypt_init` initializes |aead_ctx| with
+ * new AEAD cipher context object for encryption which is constructed
+ * to use |key| as encryption key.  |aead| specifies AEAD cipher to
+ * use.  |noncelen| is the length of nonce.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                    const ngtcp2_crypto_aead *aead,
+                                    const uint8_t *key, size_t noncelen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_ctx_decrypt_init` initializes |aead_ctx| with
+ * new AEAD cipher context object for decryption which is constructed
+ * to use |key| as encryption key.  |aead| specifies AEAD cipher to
+ * use.  |noncelen| is the length of nonce.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                    const ngtcp2_crypto_aead *aead,
+                                    const uint8_t *key, size_t noncelen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_ctx_free` frees up resources used by
+ * |aead_ctx|.  This function does not free the memory pointed by
+ * |aead_ctx| itself.
+ */
+NGTCP2_EXTERN void
+ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_delete_crypto_aead_ctx_cb` deletes the given |aead_ctx|.
+ *
+ * This function can be directly passed to delete_crypto_aead_ctx
+ * field in ngtcp2_callbacks.
+ */
+NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_aead_ctx_cb(
+    ngtcp2_conn *conn, ngtcp2_crypto_aead_ctx *aead_ctx, void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_delete_crypto_cipher_ctx_cb` deletes the given
+ * |cipher_ctx|.
+ *
+ * This function can be directly passed to delete_crypto_cipher_ctx
+ * field in ngtcp2_callbacks.
+ */
+NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_cipher_ctx_cb(
+    ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/crypto/openssl/openssl.c b/crypto/openssl/openssl.c
index 60e7250e..d1937441 100644
--- a/crypto/openssl/openssl.c
+++ b/crypto/openssl/openssl.c
@@ -173,6 +173,92 @@ size_t ngtcp2_crypto_aead_taglen(const ngtcp2_crypto_aead *aead) {
   return crypto_aead_taglen(aead->native_handle);
 }
 
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  EVP_CIPHER_CTX *actx;
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+  if (!EVP_EncryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
+      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen,
+                           NULL) ||
+      (cipher == EVP_aes_128_ccm() &&
+       !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG,
+                            (int)crypto_aead_taglen(cipher), NULL)) ||
+      !EVP_EncryptInit_ex(actx, NULL, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  EVP_CIPHER_CTX *actx;
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+  if (!EVP_DecryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
+      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen,
+                           NULL) ||
+      (cipher == EVP_aes_128_ccm() &&
+       !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG,
+                            (int)crypto_aead_taglen(cipher), NULL)) ||
+      !EVP_DecryptInit_ex(actx, NULL, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    EVP_CIPHER_CTX_free(aead_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  EVP_CIPHER_CTX *actx;
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+  if (!EVP_EncryptInit_ex(actx, cipher->native_handle, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  cipher_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (cipher_ctx->native_handle) {
+    EVP_CIPHER_CTX_free(cipher_ctx->native_handle);
+  }
+}
+
 int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
                                const uint8_t *secret, size_t secretlen,
                                const uint8_t *salt, size_t saltlen) {
@@ -226,26 +312,18 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
 }
 
 int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
                           const uint8_t *plaintext, size_t plaintextlen,
-                          const uint8_t *key, const uint8_t *nonce,
-                          size_t noncelen, const uint8_t *ad, size_t adlen) {
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *ad, size_t adlen) {
   const EVP_CIPHER *cipher = aead->native_handle;
   size_t taglen = crypto_aead_taglen(cipher);
-  EVP_CIPHER_CTX *actx;
-  int rv = 0;
+  EVP_CIPHER_CTX *actx = aead_ctx->native_handle;
   int len;
 
-  actx = EVP_CIPHER_CTX_new();
-  if (actx == NULL) {
-    return -1;
-  }
+  (void)noncelen;
 
-  if (!EVP_EncryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
-      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen,
-                           NULL) ||
-      (cipher == EVP_aes_128_ccm() &&
-       !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen, NULL)) ||
-      !EVP_EncryptInit_ex(actx, NULL, NULL, key, nonce) ||
+  if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, nonce) ||
       (cipher == EVP_aes_128_ccm() &&
        !EVP_EncryptUpdate(actx, NULL, &len, NULL, (int)plaintextlen)) ||
       !EVP_EncryptUpdate(actx, NULL, &len, ad, (int)adlen) ||
@@ -253,25 +331,25 @@ int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
       !EVP_EncryptFinal_ex(actx, dest + len, &len) ||
       !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG, (int)taglen,
                            dest + plaintextlen)) {
-    rv = -1;
+    return -1;
   }
 
-  EVP_CIPHER_CTX_free(actx);
-
-  return rv;
+  return 0;
 }
 
 int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
                           const uint8_t *ciphertext, size_t ciphertextlen,
-                          const uint8_t *key, const uint8_t *nonce,
-                          size_t noncelen, const uint8_t *ad, size_t adlen) {
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *ad, size_t adlen) {
   const EVP_CIPHER *cipher = aead->native_handle;
   size_t taglen = crypto_aead_taglen(cipher);
-  EVP_CIPHER_CTX *actx;
-  int rv = 0;
+  EVP_CIPHER_CTX *actx = aead_ctx->native_handle;
   int len;
   const uint8_t *tag;
 
+  (void)noncelen;
+
   if (taglen > ciphertextlen) {
     return -1;
   }
@@ -279,56 +357,37 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
   ciphertextlen -= taglen;
   tag = ciphertext + ciphertextlen;
 
-  actx = EVP_CIPHER_CTX_new();
-  if (actx == NULL) {
-    return -1;
-  }
-
-  if (!EVP_DecryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
-      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen,
-                           NULL) ||
-      (cipher == EVP_aes_128_ccm() &&
-       !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen,
-                            (uint8_t *)tag)) ||
-      !EVP_DecryptInit_ex(actx, NULL, NULL, key, nonce) ||
+  if (!EVP_DecryptInit_ex(actx, NULL, NULL, NULL, nonce) ||
+      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen,
+                           (uint8_t *)tag) ||
       (cipher == EVP_aes_128_ccm() &&
        !EVP_DecryptUpdate(actx, NULL, &len, NULL, (int)ciphertextlen)) ||
       !EVP_DecryptUpdate(actx, NULL, &len, ad, (int)adlen) ||
       !EVP_DecryptUpdate(actx, dest, &len, ciphertext, (int)ciphertextlen) ||
       (cipher != EVP_aes_128_ccm() &&
-       (!EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen,
-                             (uint8_t *)tag) ||
-        !EVP_DecryptFinal_ex(actx, dest + ciphertextlen, &len)))) {
-    rv = -1;
+       !EVP_DecryptFinal_ex(actx, dest + ciphertextlen, &len))) {
+    return -1;
   }
 
-  EVP_CIPHER_CTX_free(actx);
-
-  return rv;
+  return 0;
 }
 
 int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-                          const uint8_t *hp_key, const uint8_t *sample) {
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
   static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
-  const EVP_CIPHER *cipher = hp->native_handle;
-  EVP_CIPHER_CTX *actx;
-  int rv = 0;
+  EVP_CIPHER_CTX *actx = hp_ctx->native_handle;
   int len;
 
-  actx = EVP_CIPHER_CTX_new();
-  if (actx == NULL) {
-    return -1;
-  }
+  (void)hp;
 
-  if (!EVP_EncryptInit_ex(actx, cipher, NULL, hp_key, sample) ||
+  if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) ||
       !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT) - 1) ||
       !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len)) {
-    rv = -1;
+    return -1;
   }
 
-  EVP_CIPHER_CTX_free(actx);
-
-  return rv;
+  return 0;
 }
 
 static OSSL_ENCRYPTION_LEVEL
diff --git a/crypto/shared.c b/crypto/shared.c
index bea088e8..08aa5e2b 100644
--- a/crypto/shared.c
+++ b/crypto/shared.c
@@ -153,9 +153,11 @@ int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key,
   const ngtcp2_crypto_ctx *ctx;
   const ngtcp2_crypto_aead *aead;
   const ngtcp2_crypto_md *md;
+  const ngtcp2_crypto_cipher *hp;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
   void *tls = ngtcp2_conn_get_tls_native_handle(conn);
   uint8_t keybuf[64], ivbuf[64], hp_keybuf[64];
-  size_t keylen;
   size_t ivlen;
   int rv;
 
@@ -185,7 +187,7 @@ int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key,
 
   aead = &ctx->aead;
   md = &ctx->md;
-  keylen = ngtcp2_crypto_aead_keylen(aead);
+  hp = &ctx->hp;
   ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
 
   if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, aead, md,
@@ -193,40 +195,54 @@ int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key,
     return -1;
   }
 
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, aead, key, ivlen) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) {
+    goto fail;
+  }
+
   switch (level) {
   case NGTCP2_CRYPTO_LEVEL_EARLY:
-    rv = ngtcp2_conn_install_early_key(conn, key, iv, hp_key, keylen, ivlen);
+    rv = ngtcp2_conn_install_early_key(conn, &aead_ctx, iv, ivlen, &hp_ctx);
     if (rv != 0) {
-      return -1;
+      goto fail;
     }
     break;
   case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
-    rv = ngtcp2_conn_install_rx_handshake_key(conn, key, iv, hp_key, keylen,
-                                              ivlen);
+    rv = ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, iv, ivlen,
+                                              &hp_ctx);
     if (rv != 0) {
-      return -1;
+      goto fail;
     }
     break;
   case NGTCP2_CRYPTO_LEVEL_APP:
     if (!ngtcp2_conn_is_server(conn)) {
       rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
       if (rv != 0) {
-        return -1;
+        goto fail;
       }
     }
 
-    rv = ngtcp2_conn_install_rx_key(conn, secret, key, iv, hp_key, secretlen,
-                                    keylen, ivlen);
+    rv = ngtcp2_conn_install_rx_key(conn, secret, secretlen, &aead_ctx, iv,
+                                    ivlen, &hp_ctx);
     if (rv != 0) {
-      return -1;
+      goto fail;
     }
 
     break;
   default:
-    return -1;
+    goto fail;
   }
 
   return 0;
+
+fail:
+  ngtcp2_crypto_cipher_ctx_free(&hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  return -1;
 }
 
 int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key,
@@ -237,9 +253,11 @@ int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key,
   const ngtcp2_crypto_ctx *ctx;
   const ngtcp2_crypto_aead *aead;
   const ngtcp2_crypto_md *md;
+  const ngtcp2_crypto_cipher *hp;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
   void *tls = ngtcp2_conn_get_tls_native_handle(conn);
   uint8_t keybuf[64], ivbuf[64], hp_keybuf[64];
-  size_t keylen;
   size_t ivlen;
   int rv;
 
@@ -269,7 +287,7 @@ int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key,
 
   aead = &ctx->aead;
   md = &ctx->md;
-  keylen = ngtcp2_crypto_aead_keylen(aead);
+  hp = &ctx->hp;
   ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
 
   if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, aead, md,
@@ -277,40 +295,54 @@ int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key,
     return -1;
   }
 
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, aead, key, ivlen) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) {
+    goto fail;
+  }
+
   switch (level) {
   case NGTCP2_CRYPTO_LEVEL_EARLY:
-    rv = ngtcp2_conn_install_early_key(conn, key, iv, hp_key, keylen, ivlen);
+    rv = ngtcp2_conn_install_early_key(conn, &aead_ctx, iv, ivlen, &hp_ctx);
     if (rv != 0) {
-      return -1;
+      goto fail;
     }
     break;
   case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
     if (ngtcp2_conn_is_server(conn)) {
       rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
       if (rv != 0) {
-        return -1;
+        goto fail;
       }
     }
 
-    rv = ngtcp2_conn_install_tx_handshake_key(conn, key, iv, hp_key, keylen,
-                                              ivlen);
+    rv = ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, iv, ivlen,
+                                              &hp_ctx);
     if (rv != 0) {
-      return -1;
+      goto fail;
     }
     break;
   case NGTCP2_CRYPTO_LEVEL_APP:
-    rv = ngtcp2_conn_install_tx_key(conn, secret, key, iv, hp_key, secretlen,
-                                    keylen, ivlen);
+    rv = ngtcp2_conn_install_tx_key(conn, secret, secretlen, &aead_ctx, iv,
+                                    ivlen, &hp_ctx);
     if (rv != 0) {
-      return -1;
+      goto fail;
     }
 
     break;
   default:
-    return -1;
+    goto fail;
   }
 
   return 0;
+
+fail:
+  ngtcp2_crypto_cipher_ctx_free(&hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  return -1;
 }
 
 int ngtcp2_crypto_derive_and_install_initial_key(
@@ -329,7 +361,13 @@ int ngtcp2_crypto_derive_and_install_initial_key(
   uint8_t tx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
   ngtcp2_crypto_ctx ctx;
   ngtcp2_crypto_aead retry_aead;
+  ngtcp2_crypto_aead_ctx rx_aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx rx_hp_ctx = {0};
+  ngtcp2_crypto_aead_ctx tx_aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx tx_hp_ctx = {0};
+  ngtcp2_crypto_aead_ctx retry_aead_ctx = {0};
   int rv;
+  int server = ngtcp2_conn_is_server(conn);
 
   ngtcp2_crypto_ctx_initial(&ctx);
 
@@ -366,8 +404,8 @@ int ngtcp2_crypto_derive_and_install_initial_key(
 
   if (ngtcp2_crypto_derive_initial_secrets(
           rx_secret, tx_secret, initial_secret, client_dcid,
-          ngtcp2_conn_is_server(conn) ? NGTCP2_CRYPTO_SIDE_SERVER
-                                      : NGTCP2_CRYPTO_SIDE_CLIENT) != 0) {
+          server ? NGTCP2_CRYPTO_SIDE_SERVER : NGTCP2_CRYPTO_SIDE_CLIENT) !=
+      0) {
     return -1;
   }
 
@@ -383,27 +421,69 @@ int ngtcp2_crypto_derive_and_install_initial_key(
     return -1;
   }
 
-  rv = ngtcp2_conn_install_initial_key(
-      conn, rx_key, rx_iv, rx_hp_key, tx_key, tx_iv, tx_hp_key,
-      NGTCP2_CRYPTO_INITIAL_KEYLEN, NGTCP2_CRYPTO_INITIAL_IVLEN);
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&rx_aead_ctx, &ctx.aead, rx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&rx_hp_ctx, &ctx.hp, rx_hp_key) !=
+      0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&tx_aead_ctx, &ctx.aead, tx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&tx_hp_ctx, &ctx.hp, tx_hp_key) !=
+      0) {
+    goto fail;
+  }
+
+  if (!server && !ngtcp2_conn_after_retry(conn)) {
+    ngtcp2_crypto_aead_retry(&retry_aead);
+
+    if (ngtcp2_crypto_aead_ctx_encrypt_init(
+            &retry_aead_ctx, &retry_aead, (const uint8_t *)NGTCP2_RETRY_KEY,
+            sizeof(NGTCP2_RETRY_NONCE) - 1) != 0) {
+      goto fail;
+    }
+  }
+
+  rv = ngtcp2_conn_install_initial_key(conn, &rx_aead_ctx, rx_iv, &rx_hp_ctx,
+                                       &tx_aead_ctx, tx_iv, &tx_hp_ctx,
+                                       NGTCP2_CRYPTO_INITIAL_IVLEN);
   if (rv != 0) {
-    return -1;
+    goto fail;
   }
 
-  ngtcp2_conn_set_retry_aead(conn, ngtcp2_crypto_aead_retry(&retry_aead));
+  if (retry_aead_ctx.native_handle) {
+    ngtcp2_conn_set_retry_aead(conn, &retry_aead, &retry_aead_ctx);
+  }
 
   return 0;
+
+fail:
+  ngtcp2_crypto_aead_ctx_free(&retry_aead_ctx);
+  ngtcp2_crypto_cipher_ctx_free(&tx_hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&tx_aead_ctx);
+  ngtcp2_crypto_cipher_ctx_free(&rx_hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&rx_aead_ctx);
+
+  return -1;
 }
 
-int ngtcp2_crypto_update_key(ngtcp2_conn *conn, uint8_t *rx_secret,
-                             uint8_t *tx_secret, uint8_t *rx_key,
-                             uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
-                             const uint8_t *current_rx_secret,
-                             const uint8_t *current_tx_secret,
-                             size_t secretlen) {
+int ngtcp2_crypto_update_key(
+    ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+    ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv,
+    ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv,
+    const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+    size_t secretlen) {
   const ngtcp2_crypto_ctx *ctx = ngtcp2_conn_get_crypto_ctx(conn);
   const ngtcp2_crypto_aead *aead = &ctx->aead;
   const ngtcp2_crypto_md *md = &ctx->md;
+  size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
 
   if (ngtcp2_crypto_update_traffic_secret(rx_secret, md, current_rx_secret,
                                           secretlen) != 0) {
@@ -425,51 +505,69 @@ int ngtcp2_crypto_update_key(ngtcp2_conn *conn, uint8_t *rx_secret,
     return -1;
   }
 
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(rx_aead_ctx, aead, rx_key, ivlen) !=
+      0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(tx_aead_ctx, aead, tx_key, ivlen) !=
+      0) {
+    ngtcp2_crypto_aead_ctx_free(rx_aead_ctx);
+    rx_aead_ctx->native_handle = NULL;
+    return -1;
+  }
+
   return 0;
 }
 
 int ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                             const ngtcp2_crypto_aead_ctx *aead_ctx,
                              const uint8_t *plaintext, size_t plaintextlen,
-                             const uint8_t *key, const uint8_t *nonce,
-                             size_t noncelen, const uint8_t *ad, size_t adlen) {
-  if (ngtcp2_crypto_encrypt(dest, aead, plaintext, plaintextlen, key, nonce,
-                            noncelen, ad, adlen) != 0) {
+                             const uint8_t *nonce, size_t noncelen,
+                             const uint8_t *ad, size_t adlen) {
+  if (ngtcp2_crypto_encrypt(dest, aead, aead_ctx, plaintext, plaintextlen,
+                            nonce, noncelen, ad, adlen) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
   return 0;
 }
 
 int ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                             const ngtcp2_crypto_aead_ctx *aead_ctx,
                              const uint8_t *ciphertext, size_t ciphertextlen,
-                             const uint8_t *key, const uint8_t *nonce,
-                             size_t noncelen, const uint8_t *ad, size_t adlen) {
-  if (ngtcp2_crypto_decrypt(dest, aead, ciphertext, ciphertextlen, key, nonce,
-                            noncelen, ad, adlen) != 0) {
+                             const uint8_t *nonce, size_t noncelen,
+                             const uint8_t *ad, size_t adlen) {
+  if (ngtcp2_crypto_decrypt(dest, aead, aead_ctx, ciphertext, ciphertextlen,
+                            nonce, noncelen, ad, adlen) != 0) {
     return NGTCP2_ERR_TLS_DECRYPT;
   }
   return 0;
 }
 
 int ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-                             const uint8_t *hp_key, const uint8_t *sample) {
-  if (ngtcp2_crypto_hp_mask(dest, hp, hp_key, sample) != 0) {
+                             const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                             const uint8_t *sample) {
+  if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
   return 0;
 }
 
-int ngtcp2_crypto_update_key_cb(ngtcp2_conn *conn, uint8_t *rx_secret,
-                                uint8_t *tx_secret, uint8_t *rx_key,
-                                uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
-                                const uint8_t *current_rx_secret,
-                                const uint8_t *current_tx_secret,
-                                size_t secretlen, void *user_data) {
+int ngtcp2_crypto_update_key_cb(
+    ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+    ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+    ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+    const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+    size_t secretlen, void *user_data) {
+  uint8_t rx_key[64];
+  uint8_t tx_key[64];
   (void)conn;
   (void)user_data;
 
-  if (ngtcp2_crypto_update_key(conn, rx_secret, tx_secret, rx_key, rx_iv,
-                               tx_key, tx_iv, current_rx_secret,
-                               current_tx_secret, secretlen) != 0) {
+  if (ngtcp2_crypto_update_key(conn, rx_secret, tx_secret, rx_aead_ctx, rx_key,
+                               rx_iv, tx_aead_ctx, tx_key, tx_iv,
+                               current_rx_secret, current_tx_secret,
+                               secretlen) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
   return 0;
@@ -509,6 +607,8 @@ ngtcp2_ssize ngtcp2_crypto_write_connection_close(uint8_t *dest, size_t destlen,
   uint8_t tx_hp_key[NGTCP2_CRYPTO_INITIAL_KEYLEN];
   ngtcp2_crypto_ctx ctx;
   ngtcp2_ssize spktlen;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   ngtcp2_crypto_ctx_initial(&ctx);
 
@@ -524,13 +624,28 @@ ngtcp2_ssize ngtcp2_crypto_write_connection_close(uint8_t *dest, size_t destlen,
     return -1;
   }
 
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &ctx.aead, tx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    spktlen = -1;
+    goto end;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, &ctx.hp, tx_hp_key) != 0) {
+    spktlen = -1;
+    goto end;
+  }
+
   spktlen = ngtcp2_pkt_write_connection_close(
       dest, destlen, dcid, scid, error_code, ngtcp2_crypto_encrypt_cb,
-      &ctx.aead, tx_key, tx_iv, ngtcp2_crypto_hp_mask_cb, &ctx.hp, tx_hp_key);
+      &ctx.aead, &aead_ctx, tx_iv, ngtcp2_crypto_hp_mask_cb, &ctx.hp, &hp_ctx);
   if (spktlen < 0) {
-    return -1;
+    spktlen = -1;
   }
 
+end:
+  ngtcp2_crypto_cipher_ctx_free(&hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
   return spktlen;
 }
 
@@ -541,15 +656,25 @@ ngtcp2_ssize ngtcp2_crypto_write_retry(uint8_t *dest, size_t destlen,
                                        const uint8_t *token, size_t tokenlen) {
   ngtcp2_crypto_aead aead;
   ngtcp2_ssize spktlen;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
 
   ngtcp2_crypto_aead_retry(&aead);
 
-  spktlen = ngtcp2_pkt_write_retry(dest, destlen, dcid, scid, odcid, token,
-                                   tokenlen, ngtcp2_crypto_encrypt_cb, &aead);
-  if (spktlen < 0) {
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead,
+                                          (const uint8_t *)NGTCP2_RETRY_KEY,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
     return -1;
   }
 
+  spktlen =
+      ngtcp2_pkt_write_retry(dest, destlen, dcid, scid, odcid, token, tokenlen,
+                             ngtcp2_crypto_encrypt_cb, &aead, &aead_ctx);
+  if (spktlen < 0) {
+    spktlen = -1;
+  }
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
   return spktlen;
 }
 
@@ -639,3 +764,20 @@ int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn,
 
   return 0;
 }
+
+void ngtcp2_crypto_delete_crypto_aead_ctx_cb(ngtcp2_conn *conn,
+                                             ngtcp2_crypto_aead_ctx *aead_ctx,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  ngtcp2_crypto_aead_ctx_free(aead_ctx);
+}
+
+void ngtcp2_crypto_delete_crypto_cipher_ctx_cb(
+    ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  ngtcp2_crypto_cipher_ctx_free(cipher_ctx);
+}
diff --git a/crypto/shared.h b/crypto/shared.h
index a904f699..b70513af 100644
--- a/crypto/shared.h
+++ b/crypto/shared.h
@@ -110,7 +110,6 @@ int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
  */
 int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls);
 
-
 /**
  * @function
  *
@@ -165,4 +164,26 @@ int ngtcp2_crypto_derive_and_install_initial_key(
     uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp,
     const ngtcp2_cid *client_dcid);
 
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_cipher_ctx_encrypt_init` initializes |cipher_ctx|
+ * with new cipher context object for encryption which is constructed
+ * to use |key| as encryption key.  |cipher| specifies cipher to use.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_cipher_ctx_free` frees up resources used by
+ * |cipher_ctx|.  This function does not free the memory pointed by
+ * |cipher_ctx| itself.
+ */
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx);
+
 #endif /* NGTCP2_SHARED_H */
diff --git a/examples/client.cc b/examples/client.cc
index d5b6cec2..0c10f4cb 100644
--- a/examples/client.cc
+++ b/examples/client.cc
@@ -657,8 +657,8 @@ int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
 
 namespace {
 int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-               const uint8_t *hp_key, const uint8_t *sample) {
-  if (ngtcp2_crypto_hp_mask(dest, hp, hp_key, sample) != 0) {
+               const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
+  if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
 
@@ -672,14 +672,16 @@ int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
 
 namespace {
 int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
-               uint8_t *rx_key, uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
+               ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+               ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
                const uint8_t *current_rx_secret,
                const uint8_t *current_tx_secret, size_t secretlen,
                void *user_data) {
   auto c = static_cast<Client *>(user_data);
 
-  if (c->update_key(rx_secret, tx_secret, rx_key, rx_iv, tx_key, tx_iv,
-                    current_rx_secret, current_tx_secret, secretlen) != 0) {
+  if (c->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
+                    tx_iv, current_rx_secret, current_tx_secret,
+                    secretlen) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
 
@@ -883,6 +885,8 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr,
       nullptr, // dcid_status
       nullptr, // handshake_confirmed
       ::recv_new_token,
+      ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+      ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
   };
 
   auto dis = std::uniform_int_distribution<uint8_t>(
@@ -1424,8 +1428,9 @@ void Client::start_key_update_timer() {
   ev_timer_start(loop_, &key_update_timer_);
 }
 
-int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret, uint8_t *rx_key,
-                       uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
+int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+                       ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+                       ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
                        const uint8_t *current_rx_secret,
                        const uint8_t *current_tx_secret, size_t secretlen) {
   if (!config.quiet) {
@@ -1439,17 +1444,22 @@ int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret, uint8_t *rx_key,
 
   ++nkey_update_;
 
-  if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_key, rx_iv,
-                               tx_key, tx_iv, current_rx_secret,
-                               current_tx_secret, secretlen) != 0) {
+  std::array<uint8_t, 64> rx_key, tx_key;
+
+  if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
+                               rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
+                               tx_iv, current_rx_secret, current_tx_secret,
+                               secretlen) != 0) {
     return -1;
   }
 
   if (!config.quiet && config.show_secret) {
     std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
-    debug::print_secrets(rx_secret, secretlen, rx_key, keylen, rx_iv, ivlen);
+    debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
+                         ivlen);
     std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
-    debug::print_secrets(tx_secret, secretlen, tx_key, keylen, tx_iv, ivlen);
+    debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
+                         ivlen);
   }
 
   return 0;
diff --git a/examples/client.h b/examples/client.h
index 77a1e9f2..db3b2660 100644
--- a/examples/client.h
+++ b/examples/client.h
@@ -236,8 +236,9 @@ public:
   int make_stream_early();
   int change_local_addr();
   void start_change_local_addr_timer();
-  int update_key(uint8_t *rx_secret, uint8_t *tx_secret, uint8_t *rx_key,
-                 uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
+  int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+                 ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+                 ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
                  const uint8_t *current_rx_secret,
                  const uint8_t *current_tx_secret, size_t secretlen);
   int initiate_key_update();
diff --git a/examples/server.cc b/examples/server.cc
index a3736325..0e7a8798 100644
--- a/examples/server.cc
+++ b/examples/server.cc
@@ -847,8 +847,8 @@ int Handler::handshake_completed() {
 
 namespace {
 int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-               const uint8_t *hp_key, const uint8_t *sample) {
-  if (ngtcp2_crypto_hp_mask(dest, hp, hp_key, sample) != 0) {
+               const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
+  if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
 
@@ -1090,13 +1090,15 @@ int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
 
 namespace {
 int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
-               uint8_t *rx_key, uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
+               ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+               ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
                const uint8_t *current_rx_secret,
                const uint8_t *current_tx_secret, size_t secretlen,
                void *user_data) {
   auto h = static_cast<Handler *>(user_data);
-  if (h->update_key(rx_secret, tx_secret, rx_key, rx_iv, tx_key, tx_iv,
-                    current_rx_secret, current_tx_secret, secretlen) != 0) {
+  if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
+                    tx_iv, current_rx_secret, current_tx_secret,
+                    secretlen) != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
   return 0;
@@ -1507,8 +1509,13 @@ int Handler::init(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
       nullptr, // select_preferred_addr
       ::stream_reset,
       ::extend_max_remote_streams_bidi,
-      nullptr, // extend_max_remote_streams_uni,
+      nullptr, // extend_max_remote_streams_uni
       ::extend_max_stream_data,
+      nullptr, // dcid_status
+      nullptr, // handshake_confirmed
+      nullptr, // recv_new_token
+      ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+      ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
   };
 
   auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
@@ -1996,8 +2003,9 @@ int Handler::recv_stream_data(uint32_t flags, int64_t stream_id,
   return 0;
 }
 
-int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret, uint8_t *rx_key,
-                        uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
+int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+                        ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+                        ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
                         const uint8_t *current_rx_secret,
                         const uint8_t *current_tx_secret, size_t secretlen) {
   auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
@@ -2007,17 +2015,22 @@ int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret, uint8_t *rx_key,
 
   ++nkey_update_;
 
-  if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_key, rx_iv,
-                               tx_key, tx_iv, current_rx_secret,
-                               current_tx_secret, secretlen) != 0) {
+  std::array<uint8_t, 64> rx_key, tx_key;
+
+  if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
+                               rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
+                               tx_iv, current_rx_secret, current_tx_secret,
+                               secretlen) != 0) {
     return -1;
   }
 
   if (!config.quiet && config.show_secret) {
     std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
-    debug::print_secrets(rx_secret, secretlen, rx_key, keylen, rx_iv, ivlen);
+    debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
+                         ivlen);
     std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
-    debug::print_secrets(tx_secret, secretlen, tx_key, keylen, tx_iv, ivlen);
+    debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
+                         ivlen);
   }
 
   return 0;
@@ -2731,9 +2744,20 @@ int Server::generate_retry_token(uint8_t *token, size_t &tokenlen,
       generate_retry_token_aad(aad.data(), aad.size(), sa, salen, retry_scid);
 
   token[0] = RETRY_TOKEN_MAGIC;
-  if (ngtcp2_crypto_encrypt(token + 1, &token_aead_, plaintext.data(),
-                            plaintextlen, key.data(), iv.data(), ivlen,
-                            aad.data(), aadlen) != 0) {
+
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &token_aead_, key.data(),
+                                          ivlen) != 0) {
+    return -1;
+  }
+
+  auto rv = ngtcp2_crypto_encrypt(token + 1, &token_aead_, &aead_ctx,
+                                  plaintext.data(), plaintextlen, iv.data(),
+                                  ivlen, aad.data(), aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
     return -1;
   }
 
@@ -2796,11 +2820,21 @@ int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
   auto aadlen =
       generate_retry_token_aad(aad.data(), aad.size(), sa, salen, &hd->dcid);
 
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &token_aead_, key.data(),
+                                          ivlen) != 0) {
+    return -1;
+  }
+
   std::array<uint8_t, MAX_RETRY_TOKENLEN> plaintext;
 
-  if (ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, ciphertext,
-                            ciphertextlen, key.data(), iv.data(), ivlen,
-                            aad.data(), aadlen) != 0) {
+  auto rv = ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, &aead_ctx,
+                                  ciphertext, ciphertextlen, iv.data(), ivlen,
+                                  aad.data(), aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
     if (!config.quiet) {
       std::cerr << "Could not decrypt token" << std::endl;
     }
@@ -2907,10 +2941,20 @@ int Server::generate_token(uint8_t *token, size_t &tokenlen,
 
   auto plaintextlen = std::distance(std::begin(plaintext), p);
 
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &token_aead_, key.data(),
+                                          ivlen) != 0) {
+    return -1;
+  }
+
   token[0] = TOKEN_MAGIC;
-  if (ngtcp2_crypto_encrypt(token + 1, &token_aead_, plaintext.data(),
-                            plaintextlen, key.data(), iv.data(), ivlen,
-                            aad.data(), aadlen) != 0) {
+  auto rv = ngtcp2_crypto_encrypt(token + 1, &token_aead_, &aead_ctx,
+                                  plaintext.data(), plaintextlen, iv.data(),
+                                  ivlen, aad.data(), aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
     return -1;
   }
 
@@ -2972,11 +3016,21 @@ int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
     return -1;
   }
 
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &token_aead_, key.data(),
+                                          ivlen) != 0) {
+    return -1;
+  }
+
   std::array<uint8_t, MAX_TOKENLEN> plaintext;
 
-  if (ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, ciphertext,
-                            ciphertextlen, key.data(), iv.data(), ivlen,
-                            aad.data(), aadlen) != 0) {
+  auto rv = ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, &aead_ctx,
+                                  ciphertext, ciphertextlen, iv.data(), ivlen,
+                                  aad.data(), aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
     if (!config.quiet) {
       std::cerr << "Could not decrypt token" << std::endl;
     }
diff --git a/examples/server.h b/examples/server.h
index 7aa9de3a..8b1dff95 100644
--- a/examples/server.h
+++ b/examples/server.h
@@ -268,8 +268,9 @@ public:
 
   void set_tls_alert(uint8_t alert);
 
-  int update_key(uint8_t *rx_secret, uint8_t *tx_secret, uint8_t *rx_key,
-                 uint8_t *rx_iv, uint8_t *tx_key, uint8_t *tx_iv,
+  int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+                 ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+                 ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
                  const uint8_t *current_rx_secret,
                  const uint8_t *current_tx_secret, size_t secretlen);
 
diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h
index f6a4dbac..8e0cbcf1 100644
--- a/lib/includes/ngtcp2/ngtcp2.h
+++ b/lib/includes/ngtcp2/ngtcp2.h
@@ -190,6 +190,15 @@ typedef struct ngtcp2_mem {
   "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97\x86\xf1\x9c\x61\x11\xe0\x43\x90"   \
   "\xa8\x99"
 
+/* NGTCP2_RETRY_KEY is an encryption key to create integrity tag of
+   Retry packet. */
+#define NGTCP2_RETRY_KEY                                                       \
+  "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"
+
+/* NGTCP2_RETRY_NONCE is nonce used when generating integrity tag of
+   Retry packet. */
+#define NGTCP2_RETRY_NONCE "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"
+
 /* NGTCP2_HP_MASKLEN is the length of header protection mask. */
 #define NGTCP2_HP_MASKLEN 5
 
@@ -805,6 +814,30 @@ typedef struct ngtcp2_crypto_cipher {
   void *native_handle;
 } ngtcp2_crypto_cipher;
 
+/**
+ * @struct
+ *
+ * `ngtcp2_crypto_aead_ctx` is a wrapper around native AEAD cipher
+ * context object.  It should be initialized with a specific key.
+ * ngtcp2 library reuses this context object to encrypt or decrypt
+ * multiple packets.
+ */
+typedef struct ngtcp2_crypto_aead_ctx {
+  void *native_handle;
+} ngtcp2_crypto_aead_ctx;
+
+/**
+ * @struct
+ *
+ * `ngtcp2_crypto_cipher_ctx` is a wrapper around native cipher
+ * context object.  It should be initialized with a specific key.
+ * ngtcp2 library reuses this context object to encrypt or decrypt
+ * multiple packet headers.
+ */
+typedef struct ngtcp2_crypto_cipher_ctx {
+  void *native_handle;
+} ngtcp2_crypto_cipher_ctx;
+
 /**
  * @function
  *
@@ -1206,9 +1239,10 @@ typedef int (*ngtcp2_recv_retry)(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
  * :type:`ngtcp2_encrypt` is invoked when the ngtcp2 library asks the
  * application to encrypt packet payload.  The packet payload to
  * encrypt is passed as |plaintext| of length |plaintextlen|.  The
- * encryption key is passed as |key|.  The nonce is passed as |nonce|
- * of length |noncelen|.  The ad, Additional Data to AEAD, is passed
- * as |ad| of length |adlen|.
+ * AEAD cipher is |aead|.  |aead_ctx| is the AEAD cipher context
+ * object which is initialized with encryption key.  The nonce is
+ * passed as |nonce| of length |noncelen|.  The ad, Additional Data to
+ * AEAD, is passed as |ad| of length |adlen|.
  *
  * The implementation of this callback must encrypt |plaintext| using
  * the negotiated cipher suite and write the ciphertext into the
@@ -1222,9 +1256,10 @@ typedef int (*ngtcp2_recv_retry)(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
  * return immediately.
  */
 typedef int (*ngtcp2_encrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                              const ngtcp2_crypto_aead_ctx *aead_ctx,
                               const uint8_t *plaintext, size_t plaintextlen,
-                              const uint8_t *key, const uint8_t *nonce,
-                              size_t noncelen, const uint8_t *ad, size_t adlen);
+                              const uint8_t *nonce, size_t noncelen,
+                              const uint8_t *ad, size_t adlen);
 
 /**
  * @functypedef
@@ -1232,7 +1267,8 @@ typedef int (*ngtcp2_encrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead,
  * :type:`ngtcp2_decrypt` is invoked when the ngtcp2 library asks the
  * application to decrypt packet payload.  The packet payload to
  * decrypt is passed as |ciphertext| of length |ciphertextlen|.  The
- * decryption key is passed as |key| of length |keylen|.  The nonce is
+ * AEAD cipher is |aead|.  |aead_ctx| is the AEAD cipher context
+ * object which is initialized with decryption key.  The nonce is
  * passed as |nonce| of length |noncelen|.  The ad, Additional Data to
  * AEAD, is passed as |ad| of length |adlen|.
  *
@@ -1249,16 +1285,19 @@ typedef int (*ngtcp2_encrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead,
  * makes the library call return immediately.
  */
 typedef int (*ngtcp2_decrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                              const ngtcp2_crypto_aead_ctx *aead_ctx,
                               const uint8_t *ciphertext, size_t ciphertextlen,
-                              const uint8_t *key, const uint8_t *nonce,
-                              size_t noncelen, const uint8_t *ad, size_t adlen);
+                              const uint8_t *nonce, size_t noncelen,
+                              const uint8_t *ad, size_t adlen);
 
 /**
  * @functypedef
  *
  * :type:`ngtcp2_hp_mask` is invoked when the ngtcp2 library asks the
  * application to produce mask to encrypt or decrypt packet header.
- * The key is passed as |hp_key|.  The sample is passed as |sample|.
+ * The encryption cipher is |hp|.  |hp_ctx| is the cipher context
+ * object which is initialized with header protection key.  The sample
+ * is passed as |sample|.
  *
  * The implementation of this callback must produce a mask using the
  * header protection cipher suite specified by QUIC specification and
@@ -1271,7 +1310,8 @@ typedef int (*ngtcp2_decrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead,
  *  return immediately.
  */
 typedef int (*ngtcp2_hp_mask)(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-                              const uint8_t *hp_key, const uint8_t *sample);
+                              const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                              const uint8_t *sample);
 
 /**
  * @enum
@@ -1506,28 +1546,31 @@ typedef int (*ngtcp2_remove_connection_id)(ngtcp2_conn *conn,
  *
  * :type:`ngtcp2_update_key` is a callback function which tells the
  * application that it must generate new packet protection keying
- * materials.  The current set of secrets are given as
- * |current_rx_secret| and |current_tx_secret| of length |secretlen|.
- * They are decryption and encryption secrets respectively.
+ * materials and AEAD cipher context objects with new keys.  The
+ * current set of secrets are given as |current_rx_secret| and
+ * |current_tx_secret| of length |secretlen|.  They are decryption and
+ * encryption secrets respectively.
  *
  * The application has to generate new secrets and keys for both
- * encryption and decryption, and write decryption secret, key and IV
- * to the buffer pointed by |rx_secret|, |rx_key| and |rx_iv|
- * respectively.  Similarly, write encryption secret, key and IV to
- * the buffer pointed by |tx_secret|, |tx_key| and |tx_iv|.  All given
+ * encryption and decryption, and write decryption secret and IV to
+ * the buffer pointed by |rx_secret| and |rx_iv| respectively.  It
+ * also has to create new AEAD cipher context object with new
+ * decryption key and initialize |rx_aead_ctx| with it.  Similarly,
+ * write encryption secret and IV to the buffer pointed by |tx_secret|
+ * and |tx_iv|.  Create new AEAD cipher context object with new
+ * encryption key and initialize |tx_aead_ctx| with it.  All given
  * buffers have the enough capacity to store secret, key and IV.
  *
  * The callback function must return 0 if it succeeds.  Returning
  * :enum:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
  * immediately.
  */
-typedef int (*ngtcp2_update_key)(ngtcp2_conn *conn, uint8_t *rx_secret,
-                                 uint8_t *tx_secret, uint8_t *rx_key,
-                                 uint8_t *rx_iv, uint8_t *tx_key,
-                                 uint8_t *tx_iv,
-                                 const uint8_t *current_rx_secret,
-                                 const uint8_t *current_tx_secret,
-                                 size_t secretlen, void *user_data);
+typedef int (*ngtcp2_update_key)(
+    ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+    ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+    ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+    const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+    size_t secretlen, void *user_data);
 
 /**
  * @functypedef
@@ -1614,6 +1657,26 @@ typedef int (*ngtcp2_connection_id_status)(ngtcp2_conn *conn, int type,
 typedef int (*ngtcp2_recv_new_token)(ngtcp2_conn *conn, const ngtcp2_vec *token,
                                      void *user_data);
 
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_delete_crypto_aead_ctx` is a callback function which
+ * must delete the native object pointed by |aead_ctx|->native_handle.
+ */
+typedef void (*ngtcp2_delete_crypto_aead_ctx)(ngtcp2_conn *conn,
+                                              ngtcp2_crypto_aead_ctx *aead_ctx,
+                                              void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` is a callback function
+ * which must delete the native object pointed by
+ * |cipher_ctx|->native_handle.
+ */
+typedef void (*ngtcp2_delete_crypto_cipher_ctx)(
+    ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data);
+
 typedef struct ngtcp2_conn_callbacks {
   /**
    * client_initial is a callback function which is invoked when
@@ -1800,6 +1863,16 @@ typedef struct ngtcp2_conn_callbacks {
    * token is received from server.  This field is ignored by server.
    */
   ngtcp2_recv_new_token recv_new_token;
+  /**
+   * delete_crypto_aead_ctx is a callback function which deletes a
+   * given AEAD cipher context object.
+   */
+  ngtcp2_delete_crypto_aead_ctx delete_crypto_aead_ctx;
+  /**
+   * delete_crypto_cipher_ctx is a callback function which deletes a
+   * given cipher context object.
+   */
+  ngtcp2_delete_crypto_cipher_ctx delete_crypto_cipher_ctx;
 } ngtcp2_conn_callbacks;
 
 /**
@@ -1826,9 +1899,9 @@ typedef struct ngtcp2_conn_callbacks {
 NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_connection_close(
     uint8_t *dest, size_t destlen, const ngtcp2_cid *dcid,
     const ngtcp2_cid *scid, uint64_t error_code, ngtcp2_encrypt encrypt,
-    const ngtcp2_crypto_aead *aead, const uint8_t *key, const uint8_t *iv,
-    ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp,
-    const uint8_t *hp_key);
+    const ngtcp2_crypto_aead *aead, const ngtcp2_crypto_aead_ctx *aead_ctx,
+    const uint8_t *iv, ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp,
+    const ngtcp2_crypto_cipher_ctx *hp_ctx);
 
 /**
  * @function
@@ -1837,6 +1910,8 @@ NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_connection_close(
  * by |dest| whose length is |destlen|.  |odcid| specifies Original
  * Destination Connection ID.  |token| specifies Retry Token, and
  * |tokenlen| specifies its length.  |aead| must be AEAD_AES_128_GCM.
+ * |aead_ctx| must be initialized with :macro:`NGTCP2_RETRY_KEY` as an
+ * encryption key.
  *
  * This function returns the number of bytes written to the buffer, or
  * one of the following negative error codes:
@@ -1849,7 +1924,8 @@ NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_connection_close(
 NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_retry(
     uint8_t *dest, size_t destlen, const ngtcp2_cid *dcid,
     const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token,
-    size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead);
+    size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead,
+    const ngtcp2_crypto_aead_ctx *aead_ctx);
 
 /**
  * @function
@@ -1989,12 +2065,21 @@ NGTCP2_EXTERN int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn);
  * @function
  *
  * `ngtcp2_conn_install_initial_key` installs packet protection keying
- * materials for Initial packets.  |rx_key| of length |keylen|, IV
- * |rx_iv| of length |rx_ivlen|, and packet header protection key
- * |rx_hp_key| of length |keylen| to decrypt incoming Initial packets.
- * Similarly, |tx_key|, |tx_iv| and |tx_hp_key| are for encrypt
- * outgoing packets and are the same length with the rx counterpart .
- * If they have already been set, they are overwritten.
+ * materials for Initial packets.  |rx_aead_ctx| is AEAD cipher
+ * context object and must be initialized with decryption key, IV
+ * |rx_iv| of length |rx_ivlen|, and packet header protection cipher
+ * context object |rx_hp_ctx| to decrypt incoming Initial packets.
+ * Similarly, |tx_aead_ctx|, |tx_iv| and |tx_hp_ctx| are for
+ * encrypting outgoing packets and are the same length with the
+ * decryption counterpart .  If they have already been set, they are
+ * overwritten.
+ *
+ * If this function succeeds, |conn| takes ownership of |rx_aead_ctx|,
+ * |rx_hp_ctx|, |tx_aead_ctx|, and |tx_hp_ctx|.
+ * :type:`ngtcp2_delete_crypto_aead_ctx` and
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete
+ * these objects when they are no longer used.  If this function
+ * fails, the caller is responsible to delete them.
  *
  * After receiving Retry packet, the DCID most likely changes.  In
  * that case, client application must generate these keying materials
@@ -2007,49 +2092,62 @@ NGTCP2_EXTERN int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn);
  *     Out of memory.
  */
 NGTCP2_EXTERN int ngtcp2_conn_install_initial_key(
-    ngtcp2_conn *conn, const uint8_t *rx_key, const uint8_t *rx_iv,
-    const uint8_t *rx_hp_key, const uint8_t *tx_key, const uint8_t *tx_iv,
-    const uint8_t *tx_hp_key, size_t keylen, size_t ivlen);
+    ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx,
+    const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx,
+    const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv,
+    const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen);
 
 /**
  * @function
  *
  * `ngtcp2_conn_install_rx_handshake_key` installs packet protection
- * keying materials for decrypting incoming Handshake packets.  |key|
- * of length |keylen|, IV |iv| of length |ivlen|, and packet header
- * protection key |hp_key| of length |keylen| to decrypt incoming
+ * keying materials for decrypting incoming Handshake packets.
+ * |aead_ctx| is AEAD cipher context object which must be initialized
+ * with decryption key, IV |iv| of length |ivlen|, and packet header
+ * protection cipher context object |hp_ctx| to decrypt incoming
  * Handshake packets.
  *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx|,
+ * and |hp_ctx|.  :type:`ngtcp2_delete_crypto_aead_ctx` and
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete
+ * these objects when they are no longer used.  If this function
+ * fails, the caller is responsible to delete them.
+ *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
  *
  * :enum:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
  */
-NGTCP2_EXTERN int
-ngtcp2_conn_install_rx_handshake_key(ngtcp2_conn *conn, const uint8_t *key,
-                                     const uint8_t *iv, const uint8_t *hp_key,
-                                     size_t keylen, size_t ivlen);
+NGTCP2_EXTERN int ngtcp2_conn_install_rx_handshake_key(
+    ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx,
+    const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx);
 
 /**
  * @function
  *
  * `ngtcp2_conn_install_tx_handshake_key` installs packet protection
- * keying materials for encrypting outgoing Handshake packets.  |key|
- * of length |keylen|, IV |iv| of length |ivlen|, and packet header
- * protection key |hp_key| of length |keylen| to encrypt outgoing
+ * keying materials for encrypting outgoing Handshake packets.
+ * |aead_ctx| is AEAD cipher context object which must be initialized
+ * with encryption key, IV |iv| of length |ivlen|, and packet header
+ * protection cipher context object |hp_ctx| to encrypt outgoing
  * Handshake packets.
  *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :type:`ngtcp2_delete_crypto_aead_ctx` and
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete
+ * these objects when they are no longer used.  If this function
+ * fails, the caller is responsible to delete them.
+ *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
  *
  * :enum:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
  */
-NGTCP2_EXTERN int
-ngtcp2_conn_install_tx_handshake_key(ngtcp2_conn *conn, const uint8_t *key,
-                                     const uint8_t *iv, const uint8_t *hp_key,
-                                     size_t keylen, size_t ivlen);
+NGTCP2_EXTERN int ngtcp2_conn_install_tx_handshake_key(
+    ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx,
+    const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx);
 
 /**
  * @function
@@ -2074,10 +2172,16 @@ NGTCP2_EXTERN size_t ngtcp2_conn_get_aead_overhead(ngtcp2_conn *conn);
 /**
  * @function
  *
- * `ngtcp2_conn_install_early_key` installs packet protection key
- * |key| of length |keylen|, IV |iv| of length |ivlen|, and packet
- * header protection key |hp_key| of length |keylen| to encrypt (for
- * client)or decrypt (for server) 0RTT packets.
+ * `ngtcp2_conn_install_early_key` installs packet protection AEAD
+ * cipher context object |aead_ctx|, IV |iv| of length |ivlen|, and
+ * packet header protection cipher context object |hp_ctx| to encrypt
+ * (for client) or decrypt (for server) 0RTT packets.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :type:`ngtcp2_delete_crypto_aead_ctx` and
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete
+ * these objects when they are no longer used.  If this function
+ * fails, the caller is responsible to delete them.
  *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
@@ -2085,11 +2189,9 @@ NGTCP2_EXTERN size_t ngtcp2_conn_get_aead_overhead(ngtcp2_conn *conn);
  * :enum:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
  */
-NGTCP2_EXTERN int ngtcp2_conn_install_early_key(ngtcp2_conn *conn,
-                                                const uint8_t *key,
-                                                const uint8_t *iv,
-                                                const uint8_t *hp_key,
-                                                size_t keylen, size_t ivlen);
+NGTCP2_EXTERN int ngtcp2_conn_install_early_key(
+    ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx,
+    const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx);
 
 /**
  * @function
@@ -2097,9 +2199,16 @@ NGTCP2_EXTERN int ngtcp2_conn_install_early_key(ngtcp2_conn *conn,
  * `ngtcp2_conn_install_rx_key` installs packet protection keying
  * materials for decrypting Short packets.  |secret| of length
  * |secretlen| is the decryption secret which is used to derive keying
- * materials passed to this function.  |key| of length |keylen|, IV
- * |iv| of length |ivlen|, and packet header protection key |hp_key|
- * of length |keylen| to decrypt incoming Short packets.
+ * materials passed to this function.  |aead_ctx| is AEAD cipher
+ * context object which must be initialized with decryption key, IV
+ * |iv| of length |ivlen|, and packet header protection cipher context
+ * object |hp_ctx| to decrypt incoming Short packets.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :type:`ngtcp2_delete_crypto_aead_ctx` and
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete
+ * these objects when they are no longer used.  If this function
+ * fails, the caller is responsible to delete them.
  *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
@@ -2107,11 +2216,10 @@ NGTCP2_EXTERN int ngtcp2_conn_install_early_key(ngtcp2_conn *conn,
  * :enum:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
  */
-NGTCP2_EXTERN int
-ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret,
-                           const uint8_t *key, const uint8_t *iv,
-                           const uint8_t *hp_key, size_t secretlen,
-                           size_t keylen, size_t ivlen);
+NGTCP2_EXTERN int ngtcp2_conn_install_rx_key(
+    ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen,
+    const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen,
+    const ngtcp2_crypto_cipher_ctx *hp_ctx);
 
 /**
  * @function
@@ -2119,9 +2227,16 @@ ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret,
  * `ngtcp2_conn_install_tx_key` installs packet protection keying
  * materials for encrypting Short packets.  |secret| of length
  * |secretlen| is the encryption secret which is used to derive keying
- * materials passed to this function.  |key| of length |keylen|, IV
- * |iv| of length |ivlen|, and packet header protection key |hp_key|
- * of length |keylen| to encrypt outgoing Short packets.
+ * materials passed to this function.  |aead_ctx| is AEAD cipher
+ * context object which must be initialized with encryption key, IV
+ * |iv| of length |ivlen|, and packet header protection cipher context
+ * object |hp_ctx| to encrypt outgoing Short packets.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :type:`ngtcp2_delete_crypto_aead_ctx` and
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete
+ * these objects when they are no longer used.  If this function
+ * fails, the caller is responsible to delete them.
  *
  * This function returns 0 if it succeeds, or one of the following
  * negative error codes:
@@ -2129,11 +2244,10 @@ ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret,
  * :enum:`NGTCP2_ERR_NOMEM`
  *     Out of memory.
  */
-NGTCP2_EXTERN int
-ngtcp2_conn_install_tx_key(ngtcp2_conn *conn, const uint8_t *secret,
-                           const uint8_t *key, const uint8_t *iv,
-                           const uint8_t *hp_key, size_t secretlen,
-                           size_t keylen, size_t ivlen);
+NGTCP2_EXTERN int ngtcp2_conn_install_tx_key(
+    ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen,
+    const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen,
+    const ngtcp2_crypto_cipher_ctx *hp_ctx);
 
 /**
  * @function
@@ -2980,13 +3094,21 @@ NGTCP2_EXTERN void ngtcp2_conn_set_tls_native_handle(ngtcp2_conn *conn,
 /**
  * @function
  *
- * `ngtcp2_conn_set_retry_aead` sets |aead| for Retry integrity tag
- * verification.  It must be AEAD_AES_128_GCM.  This function must be
- * called if |conn| is initialized as client.  Server does not verify
- * the tag and has no need to call this function.
+ * `ngtcp2_conn_set_retry_aead` sets |aead| and |aead_ctx| for Retry
+ * integrity tag verification.  |aead| must be AEAD_AES_128_GCM.
+ * |aead_ctx| must be initialized with :macro:`NGTCP2_RETRY_KEY` as
+ * encryption key.  This function must be called if |conn| is
+ * initialized as client.  Server does not verify the tag and has no
+ * need to call this function.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx|.
+ * :type:`ngtcp2_delete_crypto_aead_ctx` will be called to delete this
+ * object when it is no longer used.  If this function fails, the
+ * caller is responsible to delete it.
  */
-NGTCP2_EXTERN void ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn,
-                                              const ngtcp2_crypto_aead *aead);
+NGTCP2_EXTERN void
+ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn, const ngtcp2_crypto_aead *aead,
+                           const ngtcp2_crypto_aead_ctx *aead_ctx);
 
 /**
  * @function
@@ -3039,6 +3161,14 @@ NGTCP2_EXTERN int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn,
  */
 NGTCP2_EXTERN int ngtcp2_conn_is_server(ngtcp2_conn *conn);
 
+/**
+ * @function
+ *
+ * `ngtcp2_conn_after_retry` returns nonzero if |conn| as a client has
+ * received Retry packet from server and successfully validated it.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_after_retry(ngtcp2_conn *conn);
+
 /**
  * @function
  *
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index 3b189655..e8f56210 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -359,6 +359,29 @@ static int conn_call_deactivate_dcid(ngtcp2_conn *conn,
       conn, NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE, dcid);
 }
 
+static void conn_call_delete_crypto_aead_ctx(ngtcp2_conn *conn,
+                                             ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (!aead_ctx->native_handle) {
+    return;
+  }
+
+  assert(conn->callbacks.delete_crypto_aead_ctx);
+
+  conn->callbacks.delete_crypto_aead_ctx(conn, aead_ctx, conn->user_data);
+}
+
+static void
+conn_call_delete_crypto_cipher_ctx(ngtcp2_conn *conn,
+                                   ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (!cipher_ctx->native_handle) {
+    return;
+  }
+
+  assert(conn->callbacks.delete_crypto_cipher_ctx);
+
+  conn->callbacks.delete_crypto_cipher_ctx(conn, cipher_ctx, conn->user_data);
+}
+
 static int crypto_offset_less(const ngtcp2_ksl_key *lhs,
                               const ngtcp2_ksl_key *rhs) {
   return *(int64_t *)lhs < *(int64_t *)rhs;
@@ -458,9 +481,6 @@ static void pktns_free(ngtcp2_pktns *pktns, const ngtcp2_mem *mem) {
 
   ngtcp2_frame_chain_list_del(pktns->tx.frq, mem);
 
-  ngtcp2_vec_del(pktns->crypto.rx.hp_key, mem);
-  ngtcp2_vec_del(pktns->crypto.tx.hp_key, mem);
-
   ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, mem);
   ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, mem);
 
@@ -944,13 +964,71 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) {
 
   ngtcp2_qlog_end(&conn->qlog);
 
+  if (conn->early.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx);
+
+  if (conn->crypto.key_update.old_rx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+        conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx);
+  }
+  if (conn->crypto.key_update.new_rx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+        conn, &conn->crypto.key_update.new_rx_ckm->aead_ctx);
+  }
+  if (conn->crypto.key_update.new_tx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+        conn, &conn->crypto.key_update.new_tx_ckm->aead_ctx);
+  }
+
+  if (conn->pktns.crypto.rx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn,
+                                     &conn->pktns.crypto.rx.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.rx.hp_ctx);
+
+  if (conn->pktns.crypto.tx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn,
+                                     &conn->pktns.crypto.tx.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.tx.hp_ctx);
+
+  if (conn->hs_pktns) {
+    if (conn->hs_pktns->crypto.rx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+          conn, &conn->hs_pktns->crypto.rx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.rx.hp_ctx);
+
+    if (conn->hs_pktns->crypto.tx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+          conn, &conn->hs_pktns->crypto.tx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.tx.hp_ctx);
+  }
+  if (conn->in_pktns) {
+    if (conn->in_pktns->crypto.rx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+          conn, &conn->in_pktns->crypto.rx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.rx.hp_ctx);
+
+    if (conn->in_pktns->crypto.tx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+          conn, &conn->in_pktns->crypto.tx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.tx.hp_ctx);
+  }
+
+  conn_call_delete_crypto_aead_ctx(conn, &conn->crypto.retry_aead_ctx);
+
   ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_buf.base);
   ngtcp2_mem_free(conn->mem, conn->local.settings.token.base);
 
   ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem);
   ngtcp2_crypto_km_del(conn->crypto.key_update.new_rx_ckm, conn->mem);
   ngtcp2_crypto_km_del(conn->crypto.key_update.new_tx_ckm, conn->mem);
-  ngtcp2_vec_del(conn->early.hp_key, conn->mem);
   ngtcp2_crypto_km_del(conn->early.ckm, conn->mem);
 
   pktns_free(&conn->pktns, conn->mem);
@@ -1771,7 +1849,7 @@ static ngtcp2_ssize conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest,
   cc.aead = pktns->crypto.ctx.aead;
   cc.hp = pktns->crypto.ctx.hp;
   cc.ckm = pktns->crypto.tx.ckm;
-  cc.hp_key = pktns->crypto.tx.hp_key;
+  cc.hp_ctx = pktns->crypto.tx.hp_ctx;
   cc.encrypt = conn->callbacks.encrypt;
   cc.hp_mask = conn->callbacks.hp_mask;
 
@@ -2034,6 +2112,11 @@ static void conn_discard_pktns(ngtcp2_conn *conn, ngtcp2_pktns **ppktns,
   conn->cstat.last_tx_pkt_ts[pktns->rtb.pktns_id] = UINT64_MAX;
   conn->cstat.loss_time[pktns->rtb.pktns_id] = UINT64_MAX;
 
+  conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx);
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx);
+  conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx);
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx);
+
   pktns_del(pktns, conn->mem);
   *ppktns = NULL;
 
@@ -2523,7 +2606,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest,
               ? NGTCP2_PKT_FLAG_KEY_PHASE
               : NGTCP2_PKT_FLAG_NONE;
       cc->ckm = pktns->crypto.tx.ckm;
-      cc->hp_key = pktns->crypto.tx.hp_key;
+      cc->hp_ctx = pktns->crypto.tx.hp_ctx;
 
       /* transport parameter is only valid after handshake completion
          which means we don't know how many connection ID that remote
@@ -2544,7 +2627,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest,
       }
       hd_flags = NGTCP2_PKT_FLAG_LONG_FORM;
       cc->ckm = conn->early.ckm;
-      cc->hp_key = conn->early.hp_key;
+      cc->hp_ctx = conn->early.hp_ctx;
       break;
     default:
       /* Unreachable */
@@ -3139,7 +3222,7 @@ ngtcp2_conn_write_single_frame_pkt(ngtcp2_conn *conn, uint8_t *dest,
   cc.aead = pktns->crypto.ctx.aead;
   cc.hp = pktns->crypto.ctx.hp;
   cc.ckm = pktns->crypto.tx.ckm;
-  cc.hp_key = pktns->crypto.tx.hp_key;
+  cc.hp_ctx = pktns->crypto.tx.hp_ctx;
   cc.encrypt = conn->callbacks.encrypt;
   cc.hp_mask = conn->callbacks.hp_mask;
 
@@ -3671,7 +3754,8 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
   retry.odcid = conn->dcid.current.cid;
 
   rv = ngtcp2_pkt_verify_retry_tag(&retry, pkt, pktlen, conn->callbacks.encrypt,
-                                   &conn->crypto.retry_aead);
+                                   &conn->crypto.retry_aead,
+                                   &conn->crypto.retry_aead_ctx);
   if (rv != 0) {
     ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
                     "unable to verify Retry packet integrity");
@@ -4032,7 +4116,7 @@ static ngtcp2_ssize decrypt_pkt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
 
   ngtcp2_crypto_create_nonce(nonce, ckm->iv.base, ckm->iv.len, pkt_num);
 
-  rv = decrypt(dest, aead, payload, payloadlen, ckm->key.base, nonce,
+  rv = decrypt(dest, aead, &ckm->aead_ctx, payload, payloadlen, nonce,
                ckm->iv.len, ad, adlen);
 
   if (rv != 0) {
@@ -4066,7 +4150,7 @@ static ngtcp2_ssize decrypt_hp(ngtcp2_pkt_hd *hd, uint8_t *dest, size_t destlen,
                                const ngtcp2_crypto_cipher *hp,
                                const uint8_t *pkt, size_t pktlen,
                                size_t pkt_num_offset, ngtcp2_crypto_km *ckm,
-                               const ngtcp2_vec *hp_key,
+                               const ngtcp2_crypto_cipher_ctx *hp_ctx,
                                ngtcp2_hp_mask hp_mask) {
   size_t sample_offset;
   uint8_t *p = dest;
@@ -4086,7 +4170,7 @@ static ngtcp2_ssize decrypt_hp(ngtcp2_pkt_hd *hd, uint8_t *dest, size_t destlen,
 
   sample_offset = pkt_num_offset + 4;
 
-  rv = hp_mask(mask, hp, hp_key->base, pkt + sample_offset);
+  rv = hp_mask(mask, hp, hp_ctx, pkt + sample_offset);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
@@ -4355,7 +4439,7 @@ static ngtcp2_ssize conn_recv_handshake_pkt(ngtcp2_conn *conn,
   ngtcp2_crypto_aead *aead;
   ngtcp2_crypto_cipher *hp;
   ngtcp2_crypto_km *ckm;
-  const ngtcp2_vec *hp_key;
+  ngtcp2_crypto_cipher_ctx *hp_ctx;
   ngtcp2_hp_mask hp_mask;
   ngtcp2_decrypt decrypt;
   size_t aead_overhead;
@@ -4584,14 +4668,14 @@ static ngtcp2_ssize conn_recv_handshake_pkt(ngtcp2_conn *conn,
   aead = &pktns->crypto.ctx.aead;
   hp = &pktns->crypto.ctx.hp;
   ckm = pktns->crypto.rx.ckm;
-  hp_key = pktns->crypto.rx.hp_key;
+  hp_ctx = &pktns->crypto.rx.hp_ctx;
 
   assert(ckm);
   assert(hp_mask);
   assert(decrypt);
 
   nwrite = decrypt_hp(&hd, plain_hdpkt, sizeof(plain_hdpkt), hp, pkt, pktlen,
-                      (size_t)nread, ckm, hp_key, hp_mask);
+                      (size_t)nread, ckm, hp_ctx, hp_mask);
   if (nwrite < 0) {
     if (ngtcp2_err_is_fatal((int)nwrite)) {
       return nwrite;
@@ -6212,7 +6296,8 @@ static int conn_prepare_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   ngtcp2_crypto_km *rx_ckm = pktns->crypto.rx.ckm;
   ngtcp2_crypto_km *tx_ckm = pktns->crypto.tx.ckm;
   ngtcp2_crypto_km *new_rx_ckm, *new_tx_ckm;
-  size_t secretlen, keylen, ivlen;
+  ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}, tx_aead_ctx = {0};
+  size_t secretlen, ivlen;
 
   if ((conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) &&
       (tx_ckm->use_count >= pktns->crypto.ctx.max_encryption ||
@@ -6233,17 +6318,16 @@ static int conn_prepare_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   }
 
   secretlen = rx_ckm->secret.len;
-  keylen = rx_ckm->key.len;
   ivlen = rx_ckm->iv.len;
 
   rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_rx_ckm,
-                                   secretlen, keylen, ivlen, conn->mem);
+                                   secretlen, ivlen, conn->mem);
   if (rv != 0) {
     return rv;
   }
 
   rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_tx_ckm,
-                                   secretlen, keylen, ivlen, conn->mem);
+                                   secretlen, ivlen, conn->mem);
   if (rv != 0) {
     return rv;
   }
@@ -6254,21 +6338,27 @@ static int conn_prepare_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
   assert(conn->callbacks.update_key);
 
   rv = conn->callbacks.update_key(
-      conn, new_rx_ckm->secret.base, new_tx_ckm->secret.base,
-      new_rx_ckm->key.base, new_rx_ckm->iv.base, new_tx_ckm->key.base,
-      new_tx_ckm->iv.base, rx_ckm->secret.base, tx_ckm->secret.base, secretlen,
-      conn->user_data);
+      conn, new_rx_ckm->secret.base, new_tx_ckm->secret.base, &rx_aead_ctx,
+      new_rx_ckm->iv.base, &tx_aead_ctx, new_tx_ckm->iv.base,
+      rx_ckm->secret.base, tx_ckm->secret.base, secretlen, conn->user_data);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
 
+  new_rx_ckm->aead_ctx = rx_aead_ctx;
+  new_tx_ckm->aead_ctx = tx_aead_ctx;
+
   if (!(rx_ckm->flags & NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE)) {
     new_rx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE;
     new_tx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE;
   }
 
-  ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem);
-  conn->crypto.key_update.old_rx_ckm = NULL;
+  if (conn->crypto.key_update.old_rx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+        conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx);
+    ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem);
+    conn->crypto.key_update.old_rx_ckm = NULL;
+  }
 
   return 0;
 }
@@ -6291,7 +6381,11 @@ static void conn_rotate_keys(ngtcp2_conn *conn, int64_t pkt_num) {
   conn->crypto.key_update.new_rx_ckm = NULL;
   pktns->crypto.rx.ckm->pkt_num = pkt_num;
 
+  assert(pktns->crypto.tx.ckm);
+
+  conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx);
   ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem);
+
   pktns->crypto.tx.ckm = conn->crypto.key_update.new_tx_ckm;
   conn->crypto.key_update.new_tx_ckm = NULL;
   pktns->crypto.tx.ckm->pkt_num = pktns->tx.last_pkt_num + 1;
@@ -6586,7 +6680,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
   ngtcp2_crypto_aead *aead;
   ngtcp2_crypto_cipher *hp;
   ngtcp2_crypto_km *ckm;
-  const ngtcp2_vec *hp_key;
+  ngtcp2_crypto_cipher_ctx *hp_ctx;
   uint8_t plain_hdpkt[1500];
   ngtcp2_hp_mask hp_mask;
   ngtcp2_decrypt decrypt;
@@ -6635,7 +6729,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
 
       pktns = conn->hs_pktns;
       ckm = pktns->crypto.rx.ckm;
-      hp_key = pktns->crypto.rx.hp_key;
+      hp_ctx = &pktns->crypto.rx.hp_ctx;
       hp_mask = conn->callbacks.hp_mask;
       decrypt = conn->callbacks.decrypt;
       aead_overhead = conn->crypto.aead_overhead;
@@ -6647,7 +6741,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
 
       pktns = &conn->pktns;
       ckm = conn->early.ckm;
-      hp_key = conn->early.hp_key;
+      hp_ctx = &conn->early.hp_ctx;
       hp_mask = conn->callbacks.hp_mask;
       decrypt = conn->callbacks.decrypt;
       aead_overhead = conn->crypto.aead_overhead;
@@ -6668,7 +6762,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
 
     pktns = &conn->pktns;
     ckm = pktns->crypto.rx.ckm;
-    hp_key = pktns->crypto.rx.hp_key;
+    hp_ctx = &pktns->crypto.rx.hp_ctx;
     hp_mask = conn->callbacks.hp_mask;
     decrypt = conn->callbacks.decrypt;
     aead_overhead = conn->crypto.aead_overhead;
@@ -6678,7 +6772,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
   hp = &pktns->crypto.ctx.hp;
 
   nwrite = decrypt_hp(&hd, plain_hdpkt, sizeof(plain_hdpkt), hp, pkt, pktlen,
-                      (size_t)nread, ckm, hp_key, hp_mask);
+                      (size_t)nread, ckm, hp_ctx, hp_mask);
   if (nwrite < 0) {
     if (ngtcp2_err_is_fatal((int)nwrite)) {
       return nwrite;
@@ -7990,155 +8084,158 @@ size_t ngtcp2_conn_get_aead_overhead(ngtcp2_conn *conn) {
   return conn->crypto.aead_overhead;
 }
 
-int ngtcp2_conn_install_initial_key(ngtcp2_conn *conn, const uint8_t *rx_key,
-                                    const uint8_t *rx_iv,
-                                    const uint8_t *rx_hp_key,
-                                    const uint8_t *tx_key, const uint8_t *tx_iv,
-                                    const uint8_t *tx_hp_key, size_t keylen,
-                                    size_t ivlen) {
+int ngtcp2_conn_install_initial_key(
+    ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx,
+    const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx,
+    const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv,
+    const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen) {
   ngtcp2_pktns *pktns = conn->in_pktns;
   int rv;
 
   assert(pktns);
 
-  if (pktns->crypto.rx.hp_key) {
-    ngtcp2_vec_del(pktns->crypto.rx.hp_key, conn->mem);
-    pktns->crypto.rx.hp_key = NULL;
-  }
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx);
+  pktns->crypto.rx.hp_ctx.native_handle = NULL;
+
   if (pktns->crypto.rx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx);
     ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem);
     pktns->crypto.rx.ckm = NULL;
   }
-  if (pktns->crypto.tx.hp_key) {
-    ngtcp2_vec_del(pktns->crypto.tx.hp_key, conn->mem);
-    pktns->crypto.tx.hp_key = NULL;
-  }
+
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx);
+  pktns->crypto.tx.hp_ctx.native_handle = NULL;
+
   if (pktns->crypto.tx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx);
     ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem);
     pktns->crypto.tx.ckm = NULL;
   }
 
-  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, rx_key, keylen,
-                            rx_iv, ivlen, conn->mem);
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, NULL, rx_iv, ivlen,
+                            conn->mem);
   if (rv != 0) {
     return rv;
   }
 
-  rv = ngtcp2_vec_new(&pktns->crypto.rx.hp_key, rx_hp_key, keylen, conn->mem);
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, NULL, tx_iv, ivlen,
+                            conn->mem);
   if (rv != 0) {
     return rv;
   }
 
-  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, tx_key, keylen,
-                            tx_iv, ivlen, conn->mem);
-  if (rv != 0) {
-    return rv;
-  }
+  /* Take owner ship after we are sure that no failure occurs, so that
+     caller can delete these contexts on failure. */
+  pktns->crypto.rx.ckm->aead_ctx = *rx_aead_ctx;
+  pktns->crypto.rx.hp_ctx = *rx_hp_ctx;
+  pktns->crypto.tx.ckm->aead_ctx = *tx_aead_ctx;
+  pktns->crypto.tx.hp_ctx = *tx_hp_ctx;
 
-  return ngtcp2_vec_new(&pktns->crypto.tx.hp_key, tx_hp_key, keylen, conn->mem);
+  return 0;
 }
 
-int ngtcp2_conn_install_rx_handshake_key(ngtcp2_conn *conn, const uint8_t *key,
-                                         const uint8_t *iv,
-                                         const uint8_t *hp_key, size_t keylen,
-                                         size_t ivlen) {
+int ngtcp2_conn_install_rx_handshake_key(
+    ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx,
+    const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) {
   ngtcp2_pktns *pktns = conn->hs_pktns;
   int rv;
 
   assert(pktns);
-  assert(!pktns->crypto.rx.hp_key);
+  assert(!pktns->crypto.rx.hp_ctx.native_handle);
   assert(!pktns->crypto.rx.ckm);
 
-  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, key, keylen, iv,
-                            ivlen, conn->mem);
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, aead_ctx, iv, ivlen,
+                            conn->mem);
   if (rv != 0) {
     return rv;
   }
 
-  return ngtcp2_vec_new(&pktns->crypto.rx.hp_key, hp_key, keylen, conn->mem);
+  pktns->crypto.rx.hp_ctx = *hp_ctx;
+
+  return 0;
 }
 
-int ngtcp2_conn_install_tx_handshake_key(ngtcp2_conn *conn, const uint8_t *key,
-                                         const uint8_t *iv,
-                                         const uint8_t *hp_key, size_t keylen,
-                                         size_t ivlen) {
+int ngtcp2_conn_install_tx_handshake_key(
+    ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx,
+    const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) {
   ngtcp2_pktns *pktns = conn->hs_pktns;
   int rv;
 
   assert(pktns);
-  assert(!pktns->crypto.tx.hp_key);
+  assert(!pktns->crypto.tx.hp_ctx.native_handle);
   assert(!pktns->crypto.tx.ckm);
 
-  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, key, keylen, iv,
-                            ivlen, conn->mem);
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, aead_ctx, iv, ivlen,
+                            conn->mem);
   if (rv != 0) {
     return rv;
   }
 
-  return ngtcp2_vec_new(&pktns->crypto.tx.hp_key, hp_key, keylen, conn->mem);
+  pktns->crypto.tx.hp_ctx = *hp_ctx;
+
+  return 0;
 }
 
-int ngtcp2_conn_install_early_key(ngtcp2_conn *conn, const uint8_t *key,
-                                  const uint8_t *iv, const uint8_t *hp_key,
-                                  size_t keylen, size_t ivlen) {
+int ngtcp2_conn_install_early_key(ngtcp2_conn *conn,
+                                  const ngtcp2_crypto_aead_ctx *aead_ctx,
+                                  const uint8_t *iv, size_t ivlen,
+                                  const ngtcp2_crypto_cipher_ctx *hp_ctx) {
   int rv;
 
-  assert(!conn->early.hp_key);
+  assert(!conn->early.hp_ctx.native_handle);
   assert(!conn->early.ckm);
 
-  rv = ngtcp2_crypto_km_new(&conn->early.ckm, NULL, 0, key, keylen, iv, ivlen,
+  rv = ngtcp2_crypto_km_new(&conn->early.ckm, NULL, 0, aead_ctx, iv, ivlen,
                             conn->mem);
   if (rv != 0) {
     return rv;
   }
 
-  return ngtcp2_vec_new(&conn->early.hp_key, hp_key, keylen, conn->mem);
+  conn->early.hp_ctx = *hp_ctx;
+
+  return 0;
 }
 
 int ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret,
-                               const uint8_t *key, const uint8_t *iv,
-                               const uint8_t *hp_key, size_t secretlen,
-                               size_t keylen, size_t ivlen) {
+                               size_t secretlen,
+                               const ngtcp2_crypto_aead_ctx *aead_ctx,
+                               const uint8_t *iv, size_t ivlen,
+                               const ngtcp2_crypto_cipher_ctx *hp_ctx) {
   ngtcp2_pktns *pktns = &conn->pktns;
   int rv;
 
-  assert(!pktns->crypto.rx.hp_key);
+  assert(!pktns->crypto.rx.hp_ctx.native_handle);
   assert(!pktns->crypto.rx.ckm);
 
-  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, secret, secretlen, key,
-                            keylen, iv, ivlen, conn->mem);
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, secret, secretlen, aead_ctx,
+                            iv, ivlen, conn->mem);
   if (rv != 0) {
     return rv;
   }
 
-  rv = ngtcp2_vec_new(&pktns->crypto.rx.hp_key, hp_key, keylen, conn->mem);
-  if (rv != 0) {
-    return rv;
-  }
+  pktns->crypto.rx.hp_ctx = *hp_ctx;
 
   return 0;
 }
 
 int ngtcp2_conn_install_tx_key(ngtcp2_conn *conn, const uint8_t *secret,
-                               const uint8_t *key, const uint8_t *iv,
-                               const uint8_t *hp_key, size_t secretlen,
-                               size_t keylen, size_t ivlen) {
+                               size_t secretlen,
+                               const ngtcp2_crypto_aead_ctx *aead_ctx,
+                               const uint8_t *iv, size_t ivlen,
+                               const ngtcp2_crypto_cipher_ctx *hp_ctx) {
   ngtcp2_pktns *pktns = &conn->pktns;
   int rv;
 
-  assert(!pktns->crypto.tx.hp_key);
+  assert(!pktns->crypto.tx.hp_ctx.native_handle);
   assert(!pktns->crypto.tx.ckm);
 
-  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, secret, secretlen, key,
-                            keylen, iv, ivlen, conn->mem);
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, secret, secretlen, aead_ctx,
+                            iv, ivlen, conn->mem);
   if (rv != 0) {
     return rv;
   }
 
-  rv = ngtcp2_vec_new(&pktns->crypto.tx.hp_key, hp_key, keylen, conn->mem);
-  if (rv != 0) {
-    return rv;
-  }
+  pktns->crypto.tx.hp_ctx = *hp_ctx;
 
   conn->remote.transport_params = conn->remote.pending_transport_params;
   conn_sync_stream_id_limit(conn);
@@ -9642,8 +9739,12 @@ const ngtcp2_crypto_ctx *ngtcp2_conn_get_initial_crypto_ctx(ngtcp2_conn *conn) {
 }
 
 void ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn,
-                                const ngtcp2_crypto_aead *aead) {
+                                const ngtcp2_crypto_aead *aead,
+                                const ngtcp2_crypto_aead_ctx *aead_ctx) {
+  assert(!conn->crypto.retry_aead_ctx.native_handle);
+
   conn->crypto.retry_aead = *aead;
+  conn->crypto.retry_aead_ctx = *aead_ctx;
 }
 
 void ngtcp2_conn_set_crypto_ctx(ngtcp2_conn *conn,
@@ -9685,6 +9786,10 @@ int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn, int64_t stream_id) {
 
 int ngtcp2_conn_is_server(ngtcp2_conn *conn) { return conn->server; }
 
+int ngtcp2_conn_after_retry(ngtcp2_conn *conn) {
+  return (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) != 0;
+}
+
 void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent,
                                       const uint8_t *data) {
   memcpy(pcent->data, data, sizeof(pcent->data));
@@ -9710,13 +9815,12 @@ void ngtcp2_settings_default(ngtcp2_settings *settings) {
 ngtcp2_ssize ngtcp2_pkt_write_connection_close(
     uint8_t *dest, size_t destlen, const ngtcp2_cid *dcid,
     const ngtcp2_cid *scid, uint64_t error_code, ngtcp2_encrypt encrypt,
-    const ngtcp2_crypto_aead *aead, const uint8_t *key, const uint8_t *iv,
-    ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp,
-    const uint8_t *hp_key) {
+    const ngtcp2_crypto_aead *aead, const ngtcp2_crypto_aead_ctx *aead_ctx,
+    const uint8_t *iv, ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp,
+    const ngtcp2_crypto_cipher_ctx *hp_ctx) {
   ngtcp2_pkt_hd hd;
   ngtcp2_crypto_km ckm;
   ngtcp2_crypto_cc cc;
-  ngtcp2_vec hp_key_vec;
   ngtcp2_ppe ppe;
   ngtcp2_frame fr = {0};
   int rv;
@@ -9726,9 +9830,8 @@ ngtcp2_ssize ngtcp2_pkt_write_connection_close(
                      NGTCP2_PROTO_VER, /* len = */ 0);
 
   ngtcp2_vec_init(&ckm.secret, NULL, 0);
-  ngtcp2_vec_init(&ckm.key, key, 16);
   ngtcp2_vec_init(&ckm.iv, iv, 12);
-  ngtcp2_vec_init(&hp_key_vec, hp_key, 16);
+  ckm.aead_ctx = *aead_ctx;
   ckm.pkt_num = 0;
   ckm.flags = NGTCP2_CRYPTO_KM_FLAG_NONE;
 
@@ -9736,7 +9839,7 @@ ngtcp2_ssize ngtcp2_pkt_write_connection_close(
   cc.aead = *aead;
   cc.hp = *hp;
   cc.ckm = &ckm;
-  cc.hp_key = &hp_key_vec;
+  cc.hp_ctx = *hp_ctx;
   cc.encrypt = encrypt;
   cc.hp_mask = hp_mask;
 
diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h
index 4b32e19b..0271e230 100644
--- a/lib/ngtcp2_conn.h
+++ b/lib/ngtcp2_conn.h
@@ -236,16 +236,16 @@ typedef struct {
       /* ckm is a cryptographic key, and iv to encrypt outgoing
          packets. */
       ngtcp2_crypto_km *ckm;
-      /* hp_key is header protection key. */
-      ngtcp2_vec *hp_key;
+      /* hp_ctx is cipher context for packet header protection. */
+      ngtcp2_crypto_cipher_ctx hp_ctx;
     } tx;
 
     struct {
       /* ckm is a cryptographic key, and iv to decrypt incoming
          packets. */
       ngtcp2_crypto_km *ckm;
-      /* hp_key is header protection key. */
-      ngtcp2_vec *hp_key;
+      /* hp_ctx is cipher context for packet header protection. */
+      ngtcp2_crypto_cipher_ctx hp_ctx;
     } rx;
 
     ngtcp2_strm strm;
@@ -355,7 +355,7 @@ struct ngtcp2_conn {
 
   struct {
     ngtcp2_crypto_km *ckm;
-    ngtcp2_vec *hp_key;
+    ngtcp2_crypto_cipher_ctx hp_ctx;
   } early;
 
   struct {
@@ -435,8 +435,12 @@ struct ngtcp2_conn {
     size_t aead_overhead;
     /* decrypt_buf is a buffer which is used to write decrypted data. */
     ngtcp2_vec decrypt_buf;
-    /* retry_aead is AEAD to verify Retry packet integrity. */
+    /* retry_aead is AEAD to verify Retry packet integrity.  It is
+       used by client only. */
     ngtcp2_crypto_aead retry_aead;
+    /* retry_aead_ctx is AEAD cipher context to verify Retry packet
+       integrity.  It is used by client only. */
+    ngtcp2_crypto_aead_ctx retry_aead_ctx;
     /* tls_error is TLS related error. */
     int tls_error;
   } crypto;
diff --git a/lib/ngtcp2_crypto.c b/lib/ngtcp2_crypto.c
index 26eacb87..5cfe145e 100644
--- a/lib/ngtcp2_crypto.c
+++ b/lib/ngtcp2_crypto.c
@@ -32,10 +32,11 @@
 #include "ngtcp2_conn.h"
 
 int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret,
-                         size_t secretlen, const uint8_t *key, size_t keylen,
+                         size_t secretlen,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
                          const uint8_t *iv, size_t ivlen,
                          const ngtcp2_mem *mem) {
-  int rv = ngtcp2_crypto_km_nocopy_new(pckm, secretlen, keylen, ivlen, mem);
+  int rv = ngtcp2_crypto_km_nocopy_new(pckm, secretlen, ivlen, mem);
   if (rv != 0) {
     return rv;
   }
@@ -43,19 +44,20 @@ int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret,
   if (secretlen) {
     memcpy((*pckm)->secret.base, secret, secretlen);
   }
-  memcpy((*pckm)->key.base, key, keylen);
+  if (aead_ctx) {
+    (*pckm)->aead_ctx = *aead_ctx;
+  }
   memcpy((*pckm)->iv.base, iv, ivlen);
 
   return 0;
 }
 
 int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen,
-                                size_t keylen, size_t ivlen,
-                                const ngtcp2_mem *mem) {
+                                size_t ivlen, const ngtcp2_mem *mem) {
   size_t len;
   uint8_t *p;
 
-  len = sizeof(ngtcp2_crypto_km) + secretlen + keylen + ivlen;
+  len = sizeof(ngtcp2_crypto_km) + secretlen + ivlen;
 
   *pckm = ngtcp2_mem_malloc(mem, len);
   if (*pckm == NULL) {
@@ -66,11 +68,9 @@ int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen,
   (*pckm)->secret.base = p;
   (*pckm)->secret.len = secretlen;
   p += secretlen;
-  (*pckm)->key.base = p;
-  (*pckm)->key.len = keylen;
-  p += keylen;
   (*pckm)->iv.base = p;
   (*pckm)->iv.len = ivlen;
+  (*pckm)->aead_ctx.native_handle = NULL;
   (*pckm)->pkt_num = -1;
   (*pckm)->use_count = 0;
   (*pckm)->flags = NGTCP2_CRYPTO_KM_FLAG_NONE;
diff --git a/lib/ngtcp2_crypto.h b/lib/ngtcp2_crypto.h
index 1cc144e8..b1baf30a 100644
--- a/lib/ngtcp2_crypto.h
+++ b/lib/ngtcp2_crypto.h
@@ -50,7 +50,7 @@ typedef enum {
 
 typedef struct {
   ngtcp2_vec secret;
-  ngtcp2_vec key;
+  ngtcp2_crypto_aead_ctx aead_ctx;
   ngtcp2_vec iv;
   /* pkt_num is a packet number of a packet which uses this keying
      material.  For encryption key, it is the lowest packet number of
@@ -75,7 +75,8 @@ typedef struct {
  * store secret is update keys.  Only 1RTT key can be updated.
  */
 int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret,
-                         size_t secretlen, const uint8_t *key, size_t keylen,
+                         size_t secretlen,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
                          const uint8_t *iv, size_t ivlen,
                          const ngtcp2_mem *mem);
 
@@ -84,8 +85,7 @@ int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret,
  * it does not copy secret, key and IV.
  */
 int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen,
-                                size_t keylen, size_t ivlen,
-                                const ngtcp2_mem *mem);
+                                size_t ivlen, const ngtcp2_mem *mem);
 
 void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, const ngtcp2_mem *mem);
 
@@ -93,7 +93,7 @@ typedef struct {
   ngtcp2_crypto_aead aead;
   ngtcp2_crypto_cipher hp;
   ngtcp2_crypto_km *ckm;
-  const ngtcp2_vec *hp_key;
+  ngtcp2_crypto_cipher_ctx hp_ctx;
   size_t aead_overhead;
   ngtcp2_encrypt encrypt;
   ngtcp2_decrypt decrypt;
diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c
index fb9ce8fb..e2d4371e 100644
--- a/lib/ngtcp2_pkt.c
+++ b/lib/ngtcp2_pkt.c
@@ -2066,16 +2066,12 @@ ngtcp2_pkt_write_stateless_reset(uint8_t *dest, size_t destlen,
   return p - dest;
 }
 
-static const uint8_t retry_key[] =
-    "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1";
-static const uint8_t retry_nonce[] =
-    "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c";
-
 ngtcp2_ssize
 ngtcp2_pkt_write_retry(uint8_t *dest, size_t destlen, const ngtcp2_cid *dcid,
                        const ngtcp2_cid *scid, const ngtcp2_cid *odcid,
                        const uint8_t *token, size_t tokenlen,
-                       ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead) {
+                       ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead,
+                       const ngtcp2_crypto_aead_ctx *aead_ctx) {
   ngtcp2_pkt_hd hd;
   uint8_t pseudo_retry[1500];
   ngtcp2_ssize pseudo_retrylen;
@@ -2106,8 +2102,10 @@ ngtcp2_pkt_write_retry(uint8_t *dest, size_t destlen, const ngtcp2_cid *dcid,
   }
 
   /* OpenSSL does not like NULL plaintext. */
-  rv = encrypt(tag, aead, (const uint8_t *)"", 0, retry_key, retry_nonce,
-               sizeof(retry_nonce) - 1, pseudo_retry, (size_t)pseudo_retrylen);
+  rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0,
+               (const uint8_t *)NGTCP2_RETRY_NONCE,
+               sizeof(NGTCP2_RETRY_NONCE) - 1, pseudo_retry,
+               (size_t)pseudo_retrylen);
   if (rv != 0) {
     return rv;
   }
@@ -2160,7 +2158,8 @@ ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry(
 int ngtcp2_pkt_verify_retry_tag(const ngtcp2_pkt_retry *retry,
                                 const uint8_t *pkt, size_t pktlen,
                                 ngtcp2_encrypt encrypt,
-                                const ngtcp2_crypto_aead *aead) {
+                                const ngtcp2_crypto_aead *aead,
+                                const ngtcp2_crypto_aead_ctx *aead_ctx) {
   uint8_t pseudo_retry[1500];
   size_t pseudo_retrylen;
   uint8_t *p = pseudo_retry;
@@ -2181,8 +2180,9 @@ int ngtcp2_pkt_verify_retry_tag(const ngtcp2_pkt_retry *retry,
   pseudo_retrylen = (size_t)(p - pseudo_retry);
 
   /* OpenSSL does not like NULL plaintext. */
-  rv = encrypt(tag, aead, (const uint8_t *)"", 0, retry_key, retry_nonce,
-               sizeof(retry_nonce) - 1, pseudo_retry, pseudo_retrylen);
+  rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0,
+               (const uint8_t *)NGTCP2_RETRY_NONCE,
+               sizeof(NGTCP2_RETRY_NONCE) - 1, pseudo_retry, pseudo_retrylen);
   if (rv != 0) {
     return rv;
   }
diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h
index caf4152c..7348cb9a 100644
--- a/lib/ngtcp2_pkt.h
+++ b/lib/ngtcp2_pkt.h
@@ -1120,6 +1120,7 @@ ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry(
 int ngtcp2_pkt_verify_retry_tag(const ngtcp2_pkt_retry *retry,
                                 const uint8_t *pkt, size_t pktlen,
                                 ngtcp2_encrypt encrypt,
-                                const ngtcp2_crypto_aead *aead);
+                                const ngtcp2_crypto_aead *aead,
+                                const ngtcp2_crypto_aead_ctx *aead_ctx);
 
 #endif /* NGTCP2_PKT_H */
diff --git a/lib/ngtcp2_ppe.c b/lib/ngtcp2_ppe.c
index c1856d0f..e2c47fd0 100644
--- a/lib/ngtcp2_ppe.c
+++ b/lib/ngtcp2_ppe.c
@@ -123,7 +123,7 @@ ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) {
   ngtcp2_crypto_create_nonce(ppe->nonce, cc->ckm->iv.base, cc->ckm->iv.len,
                              ppe->pkt_num);
 
-  rv = cc->encrypt(payload, &cc->aead, payload, payloadlen, cc->ckm->key.base,
+  rv = cc->encrypt(payload, &cc->aead, &cc->ckm->aead_ctx, payload, payloadlen,
                    ppe->nonce, cc->ckm->iv.len, buf->begin, ppe->hdlen);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
@@ -134,8 +134,7 @@ ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) {
   /* TODO Check that we have enough space to get sample */
   assert(ppe->sample_offset + NGTCP2_HP_SAMPLELEN <= ngtcp2_buf_len(buf));
 
-  rv = cc->hp_mask(mask, &cc->hp, cc->hp_key->base,
-                   buf->begin + ppe->sample_offset);
+  rv = cc->hp_mask(mask, &cc->hp, &cc->hp_ctx, buf->begin + ppe->sample_offset);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c
index 47559d28..55bb60d4 100644
--- a/tests/ngtcp2_conn_test.c
+++ b/tests/ngtcp2_conn_test.c
@@ -38,14 +38,15 @@
 #include "ngtcp2_rcvry.h"
 
 static int null_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                        const ngtcp2_crypto_aead_ctx *aead_ctx,
                         const uint8_t *plaintext, size_t plaintextlen,
-                        const uint8_t *key, const uint8_t *nonce,
-                        size_t noncelen, const uint8_t *ad, size_t adlen) {
+                        const uint8_t *nonce, size_t noncelen,
+                        const uint8_t *ad, size_t adlen) {
   (void)dest;
   (void)aead;
+  (void)aead_ctx;
   (void)plaintext;
   (void)plaintextlen;
-  (void)key;
   (void)nonce;
   (void)noncelen;
   (void)ad;
@@ -60,13 +61,14 @@ static int null_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
 }
 
 static int null_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                        const ngtcp2_crypto_aead_ctx *aead_ctx,
                         const uint8_t *ciphertext, size_t ciphertextlen,
-                        const uint8_t *key, const uint8_t *nonce,
-                        size_t noncelen, const uint8_t *ad, size_t adlen) {
+                        const uint8_t *nonce, size_t noncelen,
+                        const uint8_t *ad, size_t adlen) {
   (void)dest;
   (void)aead;
+  (void)aead_ctx;
   (void)ciphertext;
-  (void)key;
   (void)nonce;
   (void)noncelen;
   (void)ad;
@@ -77,14 +79,15 @@ static int null_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
 }
 
 static int fail_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                        const ngtcp2_crypto_aead_ctx *aead_ctx,
                         const uint8_t *ciphertext, size_t ciphertextlen,
-                        const uint8_t *key, const uint8_t *nonce,
-                        size_t noncelen, const uint8_t *ad, size_t adlen) {
+                        const uint8_t *nonce, size_t noncelen,
+                        const uint8_t *ad, size_t adlen) {
   (void)dest;
   (void)aead;
+  (void)aead_ctx;
   (void)ciphertext;
   (void)ciphertextlen;
-  (void)key;
   (void)nonce;
   (void)noncelen;
   (void)ad;
@@ -93,9 +96,10 @@ static int fail_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
 }
 
 static int null_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-                        const uint8_t *hp_key, const uint8_t *sample) {
+                        const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                        const uint8_t *sample) {
   (void)hp;
-  (void)hp_key;
+  (void)hp_ctx;
   (void)sample;
   memcpy(dest, NGTCP2_FAKE_HP_MASK, sizeof(NGTCP2_FAKE_HP_MASK) - 1);
   return 0;
@@ -113,9 +117,7 @@ static int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid,
 }
 
 static uint8_t null_secret[32];
-static uint8_t null_key[16];
 static uint8_t null_iv[16];
-static uint8_t null_hp_key[16];
 static uint8_t null_data[4096];
 static ngtcp2_path null_path = {{1, (uint8_t *)"0", NULL},
                                 {1, (uint8_t *)"0", NULL}};
@@ -149,25 +151,31 @@ static int client_initial(ngtcp2_conn *conn, void *user_data) {
 }
 
 static int client_initial_early_data(ngtcp2_conn *conn, void *user_data) {
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
+
   (void)user_data;
 
   ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data,
                                  217);
 
-  ngtcp2_conn_install_early_key(conn, null_key, null_iv, null_hp_key,
-                                sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_early_key(conn, &aead_ctx, null_iv, sizeof(null_iv),
+                                &hp_ctx);
 
   return 0;
 }
 
 static int recv_client_initial(ngtcp2_conn *conn, const ngtcp2_cid *dcid,
                                void *user_data) {
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
+
   (void)conn;
   (void)dcid;
   (void)user_data;
 
-  ngtcp2_conn_install_early_key(conn, null_key, null_iv, null_hp_key,
-                                sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_early_key(conn, &aead_ctx, null_iv, sizeof(null_iv),
+                                &hp_ctx);
 
   return 0;
 }
@@ -189,6 +197,9 @@ static int recv_crypto_data_server_early_data(ngtcp2_conn *conn,
                                               uint64_t offset,
                                               const uint8_t *data,
                                               size_t datalen, void *user_data) {
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
+
   (void)offset;
   (void)crypto_level;
   (void)data;
@@ -200,14 +211,13 @@ static int recv_crypto_data_server_early_data(ngtcp2_conn *conn,
   ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data,
                                  179);
 
-  ngtcp2_conn_install_rx_handshake_key(conn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_tx_handshake_key(conn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
 
-  ngtcp2_conn_install_tx_key(conn, null_secret, null_key, null_iv, null_hp_key,
-                             sizeof(null_secret), sizeof(null_key),
-                             sizeof(null_iv));
+  ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx,
+                             null_iv, sizeof(null_iv), &hp_ctx);
 
   conn->callbacks.recv_crypto_data = recv_crypto_data;
 
@@ -215,8 +225,9 @@ static int recv_crypto_data_server_early_data(ngtcp2_conn *conn,
 }
 
 static int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
-                      uint8_t *rx_key, uint8_t *rx_iv, uint8_t *tx_key,
-                      uint8_t *tx_iv, const uint8_t *current_rx_secret,
+                      ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+                      ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+                      const uint8_t *current_rx_secret,
                       const uint8_t *current_tx_secret, size_t secretlen,
                       void *user_data) {
   (void)conn;
@@ -228,9 +239,9 @@ static int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
 
   memset(rx_secret, 0xff, sizeof(null_secret));
   memset(tx_secret, 0xff, sizeof(null_secret));
-  memset(rx_key, 0xff, sizeof(null_key));
+  rx_aead_ctx->native_handle = NULL;
   memset(rx_iv, 0xff, sizeof(null_iv));
-  memset(tx_key, 0xff, sizeof(null_key));
+  tx_aead_ctx->native_handle = NULL;
   memset(tx_iv, 0xff, sizeof(null_iv));
 
   return 0;
@@ -384,6 +395,8 @@ static void setup_default_server(ngtcp2_conn **pconn) {
   ngtcp2_settings settings;
   ngtcp2_cid dcid, scid;
   ngtcp2_transport_params *params;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   dcid_init(&dcid);
   scid_init(&scid);
@@ -400,16 +413,14 @@ static void setup_default_server(ngtcp2_conn **pconn) {
 
   ngtcp2_conn_server_new(pconn, &dcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
                          &cb, &settings, /* mem = */ NULL, NULL);
-  ngtcp2_conn_install_rx_handshake_key(*pconn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_tx_handshake_key(*pconn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_rx_key(*pconn, null_secret, null_key, null_iv,
-                             null_hp_key, sizeof(null_secret), sizeof(null_key),
-                             sizeof(null_iv));
-  ngtcp2_conn_install_tx_key(*pconn, null_secret, null_key, null_iv,
-                             null_hp_key, sizeof(null_secret), sizeof(null_key),
-                             sizeof(null_iv));
+  ngtcp2_conn_install_rx_handshake_key(*pconn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_handshake_key(*pconn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_rx_key(*pconn, null_secret, sizeof(null_secret),
+                             &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_key(*pconn, null_secret, sizeof(null_secret),
+                             &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx);
   ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
   (*pconn)->state = NGTCP2_CS_POST_HANDSHAKE;
   (*pconn)->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED |
@@ -435,6 +446,8 @@ static void setup_default_client(ngtcp2_conn **pconn) {
   ngtcp2_settings settings;
   ngtcp2_cid dcid, scid;
   ngtcp2_transport_params *params;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   dcid_init(&dcid);
   scid_init(&scid);
@@ -450,16 +463,14 @@ static void setup_default_client(ngtcp2_conn **pconn) {
 
   ngtcp2_conn_client_new(pconn, &dcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
                          &cb, &settings, /* mem = */ NULL, NULL);
-  ngtcp2_conn_install_rx_handshake_key(*pconn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_tx_handshake_key(*pconn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_rx_key(*pconn, null_secret, null_key, null_iv,
-                             null_hp_key, sizeof(null_secret), sizeof(null_key),
-                             sizeof(null_iv));
-  ngtcp2_conn_install_tx_key(*pconn, null_secret, null_key, null_iv,
-                             null_hp_key, sizeof(null_secret), sizeof(null_key),
-                             sizeof(null_iv));
+  ngtcp2_conn_install_rx_handshake_key(*pconn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_handshake_key(*pconn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_rx_key(*pconn, null_secret, sizeof(null_secret),
+                             &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_key(*pconn, null_secret, sizeof(null_secret),
+                             &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx);
   ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
   (*pconn)->state = NGTCP2_CS_POST_HANDSHAKE;
   (*pconn)->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED |
@@ -485,6 +496,8 @@ static void setup_handshake_server(ngtcp2_conn **pconn) {
   ngtcp2_conn_callbacks cb;
   ngtcp2_settings settings;
   ngtcp2_cid dcid, scid;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   dcid_init(&dcid);
   scid_init(&scid);
@@ -501,13 +514,12 @@ static void setup_handshake_server(ngtcp2_conn **pconn) {
 
   ngtcp2_conn_server_new(pconn, &dcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
                          &cb, &settings, /* mem = */ NULL, NULL);
-  ngtcp2_conn_install_initial_key(*pconn, null_key, null_iv, null_hp_key,
-                                  null_key, null_iv, null_hp_key,
-                                  sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_rx_handshake_key(*pconn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_tx_handshake_key(*pconn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_initial_key(*pconn, &aead_ctx, null_iv, &hp_ctx,
+                                  &aead_ctx, null_iv, &hp_ctx, sizeof(null_iv));
+  ngtcp2_conn_install_rx_handshake_key(*pconn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_handshake_key(*pconn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
   ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
 }
 
@@ -516,6 +528,8 @@ static void setup_handshake_client(ngtcp2_conn **pconn) {
   ngtcp2_settings settings;
   ngtcp2_cid rcid, scid;
   ngtcp2_crypto_aead retry_aead = {0};
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   rcid_init(&rcid);
   scid_init(&scid);
@@ -531,10 +545,9 @@ static void setup_handshake_client(ngtcp2_conn **pconn) {
 
   ngtcp2_conn_client_new(pconn, &rcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
                          &cb, &settings, /* mem = */ NULL, NULL);
-  ngtcp2_conn_install_initial_key(*pconn, null_key, null_iv, null_hp_key,
-                                  null_key, null_iv, null_hp_key,
-                                  sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_set_retry_aead(*pconn, &retry_aead);
+  ngtcp2_conn_install_initial_key(*pconn, &aead_ctx, null_iv, &hp_ctx,
+                                  &aead_ctx, null_iv, &hp_ctx, sizeof(null_iv));
+  ngtcp2_conn_set_retry_aead(*pconn, &retry_aead, &aead_ctx);
 }
 
 static void setup_early_server(ngtcp2_conn **pconn) {
@@ -542,6 +555,8 @@ static void setup_early_server(ngtcp2_conn **pconn) {
   ngtcp2_settings settings;
   ngtcp2_transport_params *params;
   ngtcp2_cid dcid, scid;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   dcid_init(&dcid);
   scid_init(&scid);
@@ -558,9 +573,8 @@ static void setup_early_server(ngtcp2_conn **pconn) {
 
   ngtcp2_conn_server_new(pconn, &dcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
                          &cb, &settings, /* mem = */ NULL, NULL);
-  ngtcp2_conn_install_initial_key(*pconn, null_key, null_iv, null_hp_key,
-                                  null_key, null_iv, null_hp_key,
-                                  sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_initial_key(*pconn, &aead_ctx, null_iv, &hp_ctx,
+                                  &aead_ctx, null_iv, &hp_ctx, sizeof(null_iv));
   ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
   params = &(*pconn)->remote.transport_params;
   params->initial_max_stream_data_bidi_local = 64 * 1024;
@@ -579,6 +593,8 @@ static void setup_early_client(ngtcp2_conn **pconn) {
   ngtcp2_settings settings;
   ngtcp2_transport_params params;
   ngtcp2_cid rcid, scid;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   rcid_init(&rcid);
   scid_init(&scid);
@@ -594,9 +610,8 @@ static void setup_early_client(ngtcp2_conn **pconn) {
 
   ngtcp2_conn_client_new(pconn, &rcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
                          &cb, &settings, /* mem = */ NULL, NULL);
-  ngtcp2_conn_install_initial_key(*pconn, null_key, null_iv, null_hp_key,
-                                  null_key, null_iv, null_hp_key,
-                                  sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_initial_key(*pconn, &aead_ctx, null_iv, &hp_ctx,
+                                  &aead_ctx, null_iv, &hp_ctx, sizeof(null_iv));
   ngtcp2_conn_set_aead_overhead(*pconn, NGTCP2_FAKE_AEAD_OVERHEAD);
 
   memset(&params, 0, sizeof(params));
@@ -2254,6 +2269,7 @@ void test_ngtcp2_conn_recv_retry(void) {
   ngtcp2_vec datav;
   ngtcp2_strm *strm;
   ngtcp2_crypto_aead aead = {0};
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
 
   dcid_init(&dcid);
   setup_handshake_client(&conn);
@@ -2263,9 +2279,9 @@ void test_ngtcp2_conn_recv_retry(void) {
 
   CU_ASSERT(spktlen > 0);
 
-  spktlen = ngtcp2_pkt_write_retry(buf, sizeof(buf), &conn->oscid, &dcid,
-                                   ngtcp2_conn_get_dcid(conn), token,
-                                   strsize(token), null_encrypt, &aead);
+  spktlen = ngtcp2_pkt_write_retry(
+      buf, sizeof(buf), &conn->oscid, &dcid, ngtcp2_conn_get_dcid(conn), token,
+      strsize(token), null_encrypt, &aead, &aead_ctx);
 
   CU_ASSERT(spktlen > 0);
 
@@ -2297,9 +2313,9 @@ void test_ngtcp2_conn_recv_retry(void) {
 
   CU_ASSERT(spktlen > 0);
 
-  spktlen = ngtcp2_pkt_write_retry(buf, sizeof(buf), &conn->oscid, &dcid,
-                                   ngtcp2_conn_get_dcid(conn), token,
-                                   strsize(token), null_encrypt, &aead);
+  spktlen = ngtcp2_pkt_write_retry(
+      buf, sizeof(buf), &conn->oscid, &dcid, ngtcp2_conn_get_dcid(conn), token,
+      strsize(token), null_encrypt, &aead, &aead_ctx);
 
   CU_ASSERT(spktlen > 0);
 
@@ -2338,9 +2354,9 @@ void test_ngtcp2_conn_recv_retry(void) {
   CU_ASSERT(spktlen > 0);
   CU_ASSERT(119 == datalen);
 
-  spktlen = ngtcp2_pkt_write_retry(buf, sizeof(buf), &conn->oscid, &dcid,
-                                   ngtcp2_conn_get_dcid(conn), token,
-                                   strsize(token), null_encrypt, &aead);
+  spktlen = ngtcp2_pkt_write_retry(
+      buf, sizeof(buf), &conn->oscid, &dcid, ngtcp2_conn_get_dcid(conn), token,
+      strsize(token), null_encrypt, &aead, &aead_ctx);
 
   CU_ASSERT(spktlen > 0);
 
@@ -3549,8 +3565,7 @@ void test_ngtcp2_conn_recv_early_data(void) {
 
   pktlen = write_single_frame_0rtt_pkt(
       conn, buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num,
-      conn->version, &fr, null_key, null_iv, null_hp_key, sizeof(null_key),
-      sizeof(null_iv));
+      conn->version, &fr, null_iv, sizeof(null_iv));
 
   memset(&ud, 0, sizeof(ud));
   rv = ngtcp2_conn_read_pkt(conn, &null_path, buf, pktlen, ++t);
@@ -3583,8 +3598,7 @@ void test_ngtcp2_conn_recv_early_data(void) {
 
   pktlen = write_single_frame_0rtt_pkt(
       conn, buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num,
-      conn->version, &fr, null_key, null_iv, null_hp_key, sizeof(null_key),
-      sizeof(null_iv));
+      conn->version, &fr, null_iv, sizeof(null_iv));
 
   rv = ngtcp2_conn_read_pkt(conn, &null_path, buf, pktlen, ++t);
 
@@ -3613,10 +3627,10 @@ void test_ngtcp2_conn_recv_early_data(void) {
   fr.stream.data[0].len = 999;
   fr.stream.data[0].base = null_data;
 
-  pktlen += write_single_frame_0rtt_pkt(
-      conn, buf + pktlen, sizeof(buf) - pktlen, &rcid,
-      ngtcp2_conn_get_dcid(conn), ++pkt_num, conn->version, &fr, null_key,
-      null_iv, null_hp_key, sizeof(null_key), sizeof(null_iv));
+  pktlen +=
+      write_single_frame_0rtt_pkt(conn, buf + pktlen, sizeof(buf) - pktlen,
+                                  &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num,
+                                  conn->version, &fr, null_iv, sizeof(null_iv));
 
   rv = ngtcp2_conn_read_pkt(conn, &null_path, buf, pktlen, ++t);
 
@@ -4753,6 +4767,8 @@ void test_ngtcp2_conn_handshake_probe(void) {
   ngtcp2_rtb_entry *ent;
   ngtcp2_ksl_it it;
   int rv;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   /* Retransmit first Initial on PTO timer */
   setup_handshake_client(&conn);
@@ -4808,10 +4824,10 @@ void test_ngtcp2_conn_handshake_probe(void) {
   CU_ASSERT(ent->flags & NGTCP2_RTB_FLAG_PROBE);
   CU_ASSERT(sizeof(buf) == ent->pktlen);
 
-  ngtcp2_conn_install_rx_handshake_key(conn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_tx_handshake_key(conn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
   ngtcp2_conn_set_aead_overhead(conn, NGTCP2_FAKE_AEAD_OVERHEAD);
 
   rv = ngtcp2_conn_on_loss_detection_timer(conn, ++t);
@@ -5273,6 +5289,8 @@ void test_ngtcp2_conn_send_initial_token(void) {
   ngtcp2_ssize spktlen, shdlen;
   ngtcp2_tstamp t = 0;
   ngtcp2_pkt_hd hd;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   rcid_init(&rcid);
   scid_init(&scid);
@@ -5291,10 +5309,9 @@ void test_ngtcp2_conn_send_initial_token(void) {
 
   ngtcp2_conn_client_new(&conn, &rcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX,
                          &cb, &settings, /* mem = */ NULL, NULL);
-  ngtcp2_conn_install_initial_key(conn, null_key, null_iv, null_hp_key,
-                                  null_key, null_iv, null_hp_key,
-                                  sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_set_retry_aead(conn, &retry_aead);
+  ngtcp2_conn_install_initial_key(conn, &aead_ctx, null_iv, &hp_ctx, &aead_ctx,
+                                  null_iv, &hp_ctx, sizeof(null_iv));
+  ngtcp2_conn_set_retry_aead(conn, &retry_aead, &aead_ctx);
 
   spktlen = ngtcp2_conn_write_pkt(conn, NULL, buf, sizeof(buf), ++t);
 
@@ -5415,6 +5432,8 @@ void test_ngtcp2_conn_write_connection_close(void) {
   ngtcp2_ssize spktlen, shdlen;
   ngtcp2_pkt_hd hd;
   const uint8_t *p;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   /* Client only Initial key */
   setup_handshake_client(&conn);
@@ -5443,8 +5462,8 @@ void test_ngtcp2_conn_write_connection_close(void) {
 
   CU_ASSERT(spktlen > 0);
 
-  ngtcp2_conn_install_tx_handshake_key(conn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
+  ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
   ngtcp2_conn_set_aead_overhead(conn, NGTCP2_FAKE_AEAD_OVERHEAD);
 
   spktlen = ngtcp2_conn_write_connection_close(conn, NULL, buf, sizeof(buf),
@@ -5463,11 +5482,10 @@ void test_ngtcp2_conn_write_connection_close(void) {
   /* Client has all keys and has not confirmed handshake */
   setup_handshake_client(&conn);
 
-  ngtcp2_conn_install_tx_handshake_key(conn, null_key, null_iv, null_hp_key,
-                                       sizeof(null_key), sizeof(null_iv));
-  ngtcp2_conn_install_tx_key(conn, null_secret, null_key, null_iv, null_hp_key,
-                             sizeof(null_secret), sizeof(null_key),
-                             sizeof(null_iv));
+  ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv,
+                                       sizeof(null_iv), &hp_ctx);
+  ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx,
+                             null_iv, sizeof(null_iv), &hp_ctx);
   ngtcp2_conn_set_aead_overhead(conn, NGTCP2_FAKE_AEAD_OVERHEAD);
 
   conn->state = NGTCP2_CS_POST_HANDSHAKE;
@@ -5539,9 +5557,8 @@ void test_ngtcp2_conn_write_connection_close(void) {
   /* Server has all keys and has not confirmed handshake */
   setup_handshake_server(&conn);
 
-  ngtcp2_conn_install_tx_key(conn, null_secret, null_key, null_iv, null_hp_key,
-                             sizeof(null_secret), sizeof(null_key),
-                             sizeof(null_iv));
+  ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx,
+                             null_iv, sizeof(null_iv), &hp_ctx);
 
   conn->state = NGTCP2_CS_POST_HANDSHAKE;
 
@@ -5599,19 +5616,21 @@ void test_ngtcp2_pkt_write_connection_close(void) {
   ngtcp2_cid dcid, scid;
   ngtcp2_crypto_aead aead = {0};
   ngtcp2_crypto_cipher hp_mask = {0};
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
 
   dcid_init(&dcid);
   scid_init(&scid);
 
   spktlen = ngtcp2_pkt_write_connection_close(
       buf, sizeof(buf), &dcid, &scid, NGTCP2_INVALID_TOKEN, null_encrypt, &aead,
-      null_key, null_iv, null_hp_mask, &hp_mask, null_hp_key);
+      &aead_ctx, null_iv, null_hp_mask, &hp_mask, &hp_ctx);
 
   CU_ASSERT(spktlen > 0);
 
   spktlen = ngtcp2_pkt_write_connection_close(
       buf, 16, &dcid, &scid, NGTCP2_INVALID_TOKEN, null_encrypt, &aead,
-      null_key, null_iv, null_hp_mask, &hp_mask, null_hp_key);
+      &aead_ctx, null_iv, null_hp_mask, &hp_mask, &hp_ctx);
 
   CU_ASSERT(NGTCP2_ERR_NOBUF == spktlen);
 }
diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c
index 670fb187..c1c304df 100644
--- a/tests/ngtcp2_pkt_test.c
+++ b/tests/ngtcp2_pkt_test.c
@@ -34,15 +34,15 @@
 #include "ngtcp2_vec.h"
 
 static int null_retry_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                              const ngtcp2_crypto_aead_ctx *aead_ctx,
                               const uint8_t *plaintext, size_t plaintextlen,
-                              const uint8_t *key, const uint8_t *nonce,
-                              size_t noncelen, const uint8_t *ad,
-                              size_t adlen) {
+                              const uint8_t *nonce, size_t noncelen,
+                              const uint8_t *ad, size_t adlen) {
   (void)dest;
   (void)aead;
+  (void)aead_ctx;
   (void)plaintext;
   (void)plaintextlen;
-  (void)key;
   (void)nonce;
   (void)noncelen;
   (void)ad;
@@ -1259,6 +1259,7 @@ void test_ngtcp2_pkt_write_retry(void) {
   ngtcp2_ssize nread;
   int rv;
   ngtcp2_crypto_aead aead = {0};
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
   uint8_t tag[NGTCP2_RETRY_TAGLEN] = {0};
 
   scid_init(&scid);
@@ -1269,9 +1270,9 @@ void test_ngtcp2_pkt_write_retry(void) {
     token[i] = (uint8_t)i;
   }
 
-  spktlen =
-      ngtcp2_pkt_write_retry(buf, sizeof(buf), &dcid, &scid, &odcid, token,
-                             sizeof(token), null_retry_encrypt, &aead);
+  spktlen = ngtcp2_pkt_write_retry(buf, sizeof(buf), &dcid, &scid, &odcid,
+                                   token, sizeof(token), null_retry_encrypt,
+                                   &aead, &aead_ctx);
 
   CU_ASSERT(spktlen > 0);
 
diff --git a/tests/ngtcp2_test_helper.c b/tests/ngtcp2_test_helper.c
index 231e37c3..87f14c39 100644
--- a/tests/ngtcp2_test_helper.c
+++ b/tests/ngtcp2_test_helper.c
@@ -83,14 +83,15 @@ size_t ngtcp2_t_encode_ack_frame(uint8_t *out, uint64_t largest_ack,
 }
 
 static int null_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                        const ngtcp2_crypto_aead_ctx *aead_ctx,
                         const uint8_t *plaintext, size_t plaintextlen,
-                        const uint8_t *key, const uint8_t *nonce,
-                        size_t noncelen, const uint8_t *ad, size_t adlen) {
+                        const uint8_t *nonce, size_t noncelen,
+                        const uint8_t *ad, size_t adlen) {
   (void)dest;
   (void)aead;
+  (void)aead_ctx;
   (void)plaintext;
   (void)plaintextlen;
-  (void)key;
   (void)nonce;
   (void)noncelen;
   (void)ad;
@@ -100,9 +101,10 @@ static int null_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
 }
 
 static int null_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
-                        const uint8_t *hp_key, const uint8_t *sample) {
+                        const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                        const uint8_t *sample) {
   (void)hp;
-  (void)hp_key;
+  (void)hp_ctx;
   (void)sample;
   memcpy(dest, NGTCP2_FAKE_HP_MASK, sizeof(NGTCP2_FAKE_HP_MASK) - 1);
   return 0;
@@ -129,7 +131,6 @@ size_t write_single_frame_pkt_flags(ngtcp2_conn *conn, uint8_t *out,
   cc.encrypt = null_encrypt;
   cc.hp_mask = null_hp_mask;
   cc.ckm = conn->pktns.crypto.rx.ckm;
-  cc.hp_key = conn->pktns.crypto.rx.hp_key;
   cc.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
 
   ngtcp2_pkt_hd_init(&hd, flags, NGTCP2_PKT_SHORT, dcid, NULL, pkt_num, 4,
@@ -167,7 +168,6 @@ size_t write_pkt_flags(ngtcp2_conn *conn, uint8_t *out, size_t outlen,
   cc.encrypt = null_encrypt;
   cc.hp_mask = null_hp_mask;
   cc.ckm = conn->pktns.crypto.rx.ckm;
-  cc.hp_key = conn->pktns.crypto.rx.hp_key;
   cc.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
 
   ngtcp2_pkt_hd_init(&hd, flags, NGTCP2_PKT_SHORT, dcid, NULL, pkt_num, 4,
@@ -201,7 +201,6 @@ size_t write_single_frame_pkt_without_conn_id(ngtcp2_conn *conn, uint8_t *out,
   cc.encrypt = null_encrypt;
   cc.hp_mask = null_hp_mask;
   cc.ckm = conn->pktns.crypto.rx.ckm;
-  cc.hp_key = conn->pktns.crypto.rx.hp_key;
   cc.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_SHORT, NULL, NULL,
@@ -234,12 +233,10 @@ size_t write_single_frame_handshake_pkt(ngtcp2_conn *conn, uint8_t *out,
   switch (pkt_type) {
   case NGTCP2_PKT_INITIAL:
     cc.ckm = conn->in_pktns->crypto.rx.ckm;
-    cc.hp_key = conn->in_pktns->crypto.rx.hp_key;
     cc.aead_overhead = NGTCP2_INITIAL_AEAD_OVERHEAD;
     break;
   case NGTCP2_PKT_HANDSHAKE:
     cc.ckm = conn->hs_pktns->crypto.rx.ckm;
-    cc.hp_key = conn->hs_pktns->crypto.rx.hp_key;
     cc.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
     break;
   default:
@@ -274,7 +271,6 @@ size_t write_single_frame_initial_pkt(ngtcp2_conn *conn, uint8_t *out,
   cc.encrypt = null_encrypt;
   cc.hp_mask = null_hp_mask;
   cc.ckm = conn->in_pktns->crypto.rx.ckm;
-  cc.hp_key = conn->in_pktns->crypto.rx.hp_key;
   cc.aead_overhead = NGTCP2_INITIAL_AEAD_OVERHEAD;
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_INITIAL, dcid,
@@ -296,18 +292,15 @@ size_t write_single_frame_0rtt_pkt(ngtcp2_conn *conn, uint8_t *out,
                                    size_t outlen, const ngtcp2_cid *dcid,
                                    const ngtcp2_cid *scid, int64_t pkt_num,
                                    uint32_t version, ngtcp2_frame *fr,
-                                   const uint8_t *key, const uint8_t *iv,
-                                   const uint8_t *hp_key, size_t keylen,
-                                   size_t ivlen) {
+                                   const uint8_t *iv, size_t ivlen) {
   ngtcp2_crypto_km *ckm;
-  ngtcp2_vec hp_keyv;
   ngtcp2_crypto_cc cc;
   ngtcp2_ppe ppe;
   ngtcp2_pkt_hd hd;
   int rv;
   ngtcp2_ssize n;
 
-  rv = ngtcp2_crypto_km_new(&ckm, NULL, 0, key, keylen, iv, ivlen, conn->mem);
+  rv = ngtcp2_crypto_km_new(&ckm, NULL, 0, NULL, iv, ivlen, conn->mem);
 
   assert(rv == 0);
 
@@ -315,7 +308,6 @@ size_t write_single_frame_0rtt_pkt(ngtcp2_conn *conn, uint8_t *out,
   cc.encrypt = null_encrypt;
   cc.hp_mask = null_hp_mask;
   cc.ckm = ckm;
-  cc.hp_key = ngtcp2_vec_init(&hp_keyv, hp_key, keylen);
   cc.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
 
   ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_0RTT, dcid,
@@ -352,17 +344,14 @@ size_t write_handshake_pkt(ngtcp2_conn *conn, uint8_t *out, size_t outlen,
   switch (pkt_type) {
   case NGTCP2_PKT_INITIAL:
     cc.ckm = conn->in_pktns->crypto.rx.ckm;
-    cc.hp_key = conn->in_pktns->crypto.rx.hp_key;
     cc.aead_overhead = NGTCP2_INITIAL_AEAD_OVERHEAD;
     break;
   case NGTCP2_PKT_HANDSHAKE:
     cc.ckm = conn->hs_pktns->crypto.rx.ckm;
-    cc.hp_key = conn->hs_pktns->crypto.rx.hp_key;
     cc.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
     break;
   case NGTCP2_PKT_0RTT:
     cc.ckm = conn->early.ckm;
-    cc.hp_key = conn->early.hp_key;
     cc.aead_overhead = NGTCP2_FAKE_AEAD_OVERHEAD;
     break;
   default:
diff --git a/tests/ngtcp2_test_helper.h b/tests/ngtcp2_test_helper.h
index 2318e56c..f283cbe2 100644
--- a/tests/ngtcp2_test_helper.h
+++ b/tests/ngtcp2_test_helper.h
@@ -163,9 +163,7 @@ size_t write_single_frame_0rtt_pkt(ngtcp2_conn *conn, uint8_t *out,
                                    size_t outlen, const ngtcp2_cid *dcid,
                                    const ngtcp2_cid *scid, int64_t pkt_num,
                                    uint32_t version, ngtcp2_frame *fr,
-                                   const uint8_t *key, const uint8_t *iv,
-                                   const uint8_t *hp_key, size_t keylen,
-                                   size_t ivlen);
+                                   const uint8_t *iv, size_t ivlen);
 
 /*
  * write_handshake_pkt writes an unprotected QUIC handshake packet
-- 
GitLab