Repository: randombit/botan
Pull request #3295 (2023-02-16 15:02:43)

Add SymmetricAlgorithm::has_keying_material
This formalizes what had been an ad-hoc check


Patch:
diff --git a/src/lib/base/sym_algo.h b/src/lib/base/sym_algo.h
index 41d999292..7d8b2b1ba 100644
--- a/src/lib/base/sym_algo.h
+++ b/src/lib/base/sym_algo.h
@@ -105,7 +105,8 @@ class BOTAN_PUBLIC_API(2,0) SymmetricAlgorithm
       virtual ~SymmetricAlgorithm() = default;
 
       /**
-      * Reset the state.
+      * Reset the internal state. This includes not just the key, but
+      * any partial message that may have been in process.
       */
       virtual void clear() = 0;
 
@@ -167,10 +168,20 @@ class BOTAN_PUBLIC_API(2,0) SymmetricAlgorithm
       */
       virtual std::string name() const = 0;
 
+      /**
+      * @return true if a key has been set on this object
+      */
+      virtual bool has_keying_material() const = 0;
+
    protected:
-      void verify_key_set(bool cond) const
+      void assert_key_material_set() const
+         {
+         assert_key_material_set(has_keying_material());
+         }
+
+      void assert_key_material_set(bool predicate) const
          {
-         if(cond == false)
+         if(!predicate)
             throw_key_not_set_error();
          }
 
diff --git a/src/lib/block/aes/aes.cpp b/src/lib/block/aes/aes.cpp
index f1067d0b9..cd62dc4b2 100644
--- a/src/lib/block/aes/aes.cpp
+++ b/src/lib/block/aes/aes.cpp
@@ -800,9 +800,13 @@ size_t AES_128::parallelism() const { return aes_parallelism(); }
 size_t AES_192::parallelism() const { return aes_parallelism(); }
 size_t AES_256::parallelism() const { return aes_parallelism(); }
 
+bool AES_128::has_keying_material() const { return !m_EK.empty(); }
+bool AES_192::has_keying_material() const { return !m_EK.empty(); }
+bool AES_256::has_keying_material() const { return !m_EK.empty(); }
+
 void AES_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_EK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_HW_AES_SUPPORT)
    if(CPUID::has_hw_aes())
@@ -823,7 +827,7 @@ void AES_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 
 void AES_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_DK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_HW_AES_SUPPORT)
    if(CPUID::has_hw_aes())
@@ -876,7 +880,7 @@ void AES_128::clear()
 
 void AES_192::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_EK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_HW_AES_SUPPORT)
    if(CPUID::has_hw_aes())
@@ -897,7 +901,7 @@ void AES_192::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 
 void AES_192::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_DK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_HW_AES_SUPPORT)
    if(CPUID::has_hw_aes())
@@ -950,7 +954,7 @@ void AES_192::clear()
 
 void AES_256::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_EK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_HW_AES_SUPPORT)
    if(CPUID::has_hw_aes())
@@ -971,7 +975,7 @@ void AES_256::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 
 void AES_256::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_DK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_HW_AES_SUPPORT)
    if(CPUID::has_hw_aes())
diff --git a/src/lib/block/aes/aes.h b/src/lib/block/aes/aes.h
index 908a9a7ae..cd708a441 100644
--- a/src/lib/block/aes/aes.h
+++ b/src/lib/block/aes/aes.h
@@ -28,6 +28,7 @@ class AES_128 final : public Block_Cipher_Fixed_Params<16, 16>
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<AES_128>(); }
       size_t parallelism() const override;
 
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
@@ -64,6 +65,7 @@ class AES_192 final : public Block_Cipher_Fixed_Params<16, 24>
       std::string name() const override { return "AES-192"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<AES_192>(); }
       size_t parallelism() const override;
+      bool has_keying_material() const override;
 
    private:
 #if defined(BOTAN_HAS_AES_VPERM)
@@ -102,6 +104,7 @@ class AES_256 final : public Block_Cipher_Fixed_Params<16, 32>
       std::string name() const override { return "AES-256"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<AES_256>(); }
       size_t parallelism() const override;
+      bool has_keying_material() const override;
 
    private:
 #if defined(BOTAN_HAS_AES_VPERM)
diff --git a/src/lib/block/aria/aria.cpp b/src/lib/block/aria/aria.cpp
index 6ba443257..8c94f3681 100644
--- a/src/lib/block/aria/aria.cpp
+++ b/src/lib/block/aria/aria.cpp
@@ -422,40 +422,44 @@ void key_schedule(secure_vector<uint32_t>& ERK,
 
 void ARIA_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(!m_ERK.empty());
+   assert_key_material_set();
    ARIA_F::transform(in, out, blocks, m_ERK);
    }
 
 void ARIA_192::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(!m_ERK.empty());
+   assert_key_material_set();
    ARIA_F::transform(in, out, blocks, m_ERK);
    }
 
 void ARIA_256::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(!m_ERK.empty());
+   assert_key_material_set();
    ARIA_F::transform(in, out, blocks, m_ERK);
    }
 
 void ARIA_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(!m_DRK.empty());
+   assert_key_material_set();
    ARIA_F::transform(in, out, blocks, m_DRK);
    }
 
 void ARIA_192::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(!m_DRK.empty());
+   assert_key_material_set();
    ARIA_F::transform(in, out, blocks, m_DRK);
    }
 
 void ARIA_256::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(!m_DRK.empty());
+   assert_key_material_set();
    ARIA_F::transform(in, out, blocks, m_DRK);
    }
 
+bool ARIA_128::has_keying_material() const { return !m_ERK.empty(); }
+bool ARIA_192::has_keying_material() const { return !m_ERK.empty(); }
+bool ARIA_256::has_keying_material() const { return !m_ERK.empty(); }
+
 void ARIA_128::key_schedule(const uint8_t key[], size_t length)
    {
    ARIA_F::key_schedule(m_ERK, m_DRK, key, length);
diff --git a/src/lib/block/aria/aria.h b/src/lib/block/aria/aria.h
index f3bf69de3..221c2d821 100644
--- a/src/lib/block/aria/aria.h
+++ b/src/lib/block/aria/aria.h
@@ -32,6 +32,8 @@ class ARIA_128 final : public Block_Cipher_Fixed_Params<16, 16>
       void clear() override;
       std::string name() const override { return "ARIA-128"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<ARIA_128>(); }
+
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
@@ -51,6 +53,8 @@ class ARIA_192 final : public Block_Cipher_Fixed_Params<16, 24>
       void clear() override;
       std::string name() const override { return "ARIA-192"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<ARIA_192>(); }
+
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
@@ -70,6 +74,8 @@ class ARIA_256 final : public Block_Cipher_Fixed_Params<16, 32>
       void clear() override;
       std::string name() const override { return "ARIA-256"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<ARIA_256>(); }
+
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
diff --git a/src/lib/block/blowfish/blowfish.cpp b/src/lib/block/blowfish/blowfish.cpp
index 70334f1a1..d2707e0dd 100644
--- a/src/lib/block/blowfish/blowfish.cpp
+++ b/src/lib/block/blowfish/blowfish.cpp
@@ -207,7 +207,7 @@ inline uint32_t BFF(uint32_t X, const secure_vector<uint32_t>& S)
 */
 void Blowfish::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_S.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 4)
       {
@@ -276,7 +276,7 @@ void Blowfish::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void Blowfish::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_S.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 4)
       {
@@ -341,6 +341,11 @@ void Blowfish::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool Blowfish::has_keying_material() const
+   {
+   return !m_P.empty();
+   }
+
 /*
 * Blowfish Key Schedule
 */
diff --git a/src/lib/block/blowfish/blowfish.h b/src/lib/block/blowfish/blowfish.h
index 71f545531..ce0e566a6 100644
--- a/src/lib/block/blowfish/blowfish.h
+++ b/src/lib/block/blowfish/blowfish.h
@@ -31,6 +31,8 @@ class BOTAN_TEST_API Blowfish final : public Block_Cipher_Fixed_Params<8, 1, 56>
       void clear() override;
       std::string name() const override { return "Blowfish"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Blowfish>(); }
+
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
diff --git a/src/lib/block/camellia/camellia.cpp b/src/lib/block/camellia/camellia.cpp
index 047ad0603..efddec67a 100644
--- a/src/lib/block/camellia/camellia.cpp
+++ b/src/lib/block/camellia/camellia.cpp
@@ -394,40 +394,44 @@ void key_schedule(secure_vector<uint64_t>& SK, const uint8_t key[], size_t lengt
 
 void Camellia_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SK.empty() == false);
+   assert_key_material_set();
    Camellia_F::encrypt(in, out, blocks, m_SK, 9);
    }
 
 void Camellia_192::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SK.empty() == false);
+   assert_key_material_set();
    Camellia_F::encrypt(in, out, blocks, m_SK, 12);
    }
 
 void Camellia_256::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SK.empty() == false);
+   assert_key_material_set();
    Camellia_F::encrypt(in, out, blocks, m_SK, 12);
    }
 
 void Camellia_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SK.empty() == false);
+   assert_key_material_set();
    Camellia_F::decrypt(in, out, blocks, m_SK, 9);
    }
 
 void Camellia_192::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SK.empty() == false);
+   assert_key_material_set();
    Camellia_F::decrypt(in, out, blocks, m_SK, 12);
    }
 
 void Camellia_256::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SK.empty() == false);
+   assert_key_material_set();
    Camellia_F::decrypt(in, out, blocks, m_SK, 12);
    }
 
+bool Camellia_128::has_keying_material() const { return !m_SK.empty(); }
+bool Camellia_192::has_keying_material() const { return !m_SK.empty(); }
+bool Camellia_256::has_keying_material() const { return !m_SK.empty(); }
+
 void Camellia_128::key_schedule(const uint8_t key[], size_t length)
    {
    Camellia_F::key_schedule(m_SK, key, length);
diff --git a/src/lib/block/camellia/camellia.h b/src/lib/block/camellia/camellia.h
index f8ac9d2b7..b0c3ffcf5 100644
--- a/src/lib/block/camellia/camellia.h
+++ b/src/lib/block/camellia/camellia.h
@@ -24,6 +24,7 @@ class Camellia_128 final : public Block_Cipher_Fixed_Params<16, 16>
       void clear() override;
       std::string name() const override { return "Camellia-128"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Camellia_128>(); }
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
@@ -42,6 +43,7 @@ class Camellia_192 final : public Block_Cipher_Fixed_Params<16, 24>
       void clear() override;
       std::string name() const override { return "Camellia-192"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Camellia_192>(); }
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
@@ -60,6 +62,7 @@ class Camellia_256 final : public Block_Cipher_Fixed_Params<16, 32>
       void clear() override;
       std::string name() const override { return "Camellia-256"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Camellia_256>(); }
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
 
diff --git a/src/lib/block/cascade/cascade.cpp b/src/lib/block/cascade/cascade.cpp
index a0ceaffdc..3b5feba2f 100644
--- a/src/lib/block/cascade/cascade.cpp
+++ b/src/lib/block/cascade/cascade.cpp
@@ -48,6 +48,12 @@ std::string Cascade_Cipher::name() const
    return "Cascade(" + m_cipher1->name() + "," + m_cipher2->name() + ")";
    }
 
+bool Cascade_Cipher::has_keying_material() const
+   {
+   return m_cipher1->has_keying_material() &&
+      m_cipher2->has_keying_material();
+   }
+
 std::unique_ptr<BlockCipher> Cascade_Cipher::new_object() const
    {
    return std::make_unique<Cascade_Cipher>(m_cipher1->new_object(),
diff --git a/src/lib/block/cascade/cascade.h b/src/lib/block/cascade/cascade.h
index 6021bfc73..84acffeaa 100644
--- a/src/lib/block/cascade/cascade.h
+++ b/src/lib/block/cascade/cascade.h
@@ -33,6 +33,8 @@ class Cascade_Cipher final : public BlockCipher
       std::string name() const override;
       std::unique_ptr<BlockCipher> new_object() const override;
 
+      bool has_keying_material() const override;
+
       /**
       * Create a cascade of two block ciphers
       * @param cipher1 the first cipher
diff --git a/src/lib/block/cast128/cast128.cpp b/src/lib/block/cast128/cast128.cpp
index 7c778b9d3..3afb8ae73 100644
--- a/src/lib/block/cast128/cast128.cpp
+++ b/src/lib/block/cast128/cast128.cpp
@@ -230,7 +230,7 @@ inline uint32_t F3(uint32_t R, uint32_t MK, uint8_t RK)
 */
 void CAST_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_RK.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -308,7 +308,7 @@ void CAST_128::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void CAST_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_RK.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -381,6 +381,11 @@ void CAST_128::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool CAST_128::has_keying_material() const
+   {
+   return !m_RK.empty();
+   }
+
 /*
 * CAST-128 Key Schedule
 */
diff --git a/src/lib/block/cast128/cast128.h b/src/lib/block/cast128/cast128.h
index c0b139e02..2f28b6125 100644
--- a/src/lib/block/cast128/cast128.h
+++ b/src/lib/block/cast128/cast128.h
@@ -24,7 +24,7 @@ class CAST_128 final : public Block_Cipher_Fixed_Params<8, 11, 16>
       void clear() override;
       std::string name() const override { return "CAST-128"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<CAST_128>(); }
-
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t[], size_t) override;
 
diff --git a/src/lib/block/des/des.cpp b/src/lib/block/des/des.cpp
index 07f7cd962..eedc4c2d0 100644
--- a/src/lib/block/des/des.cpp
+++ b/src/lib/block/des/des.cpp
@@ -290,7 +290,7 @@ inline void des_FP(uint32_t& L, uint32_t& R)
 */
 void DES::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_round_key.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -334,7 +334,7 @@ void DES::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void DES::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_round_key.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -373,6 +373,11 @@ void DES::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool DES::has_keying_material() const
+   {
+   return !m_round_key.empty();
+   }
+
 /*
 * DES Key Schedule
 */
@@ -392,7 +397,7 @@ void DES::clear()
 */
 void TripleDES::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_round_key.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -442,7 +447,7 @@ void TripleDES::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) cons
 */
 void TripleDES::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_round_key.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -487,6 +492,11 @@ void TripleDES::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) cons
       }
    }
 
+bool TripleDES::has_keying_material() const
+   {
+   return !m_round_key.empty();
+   }
+
 /*
 * TripleDES Key Schedule
 */
diff --git a/src/lib/block/des/des.h b/src/lib/block/des/des.h
index 1fd65cff5..4e8839e96 100644
--- a/src/lib/block/des/des.h
+++ b/src/lib/block/des/des.h
@@ -24,6 +24,7 @@ class DES final : public Block_Cipher_Fixed_Params<8, 8>
       void clear() override;
       std::string name() const override { return "DES"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<DES>(); }
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t[], size_t) override;
 
@@ -42,6 +43,7 @@ class TripleDES final : public Block_Cipher_Fixed_Params<8, 16, 24, 8>
       void clear() override;
       std::string name() const override { return "TripleDES"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<TripleDES>(); }
+      bool has_keying_material() const override;
    private:
 
       void key_schedule(const uint8_t[], size_t) override;
diff --git a/src/lib/block/gost_28147/gost_28147.cpp b/src/lib/block/gost_28147/gost_28147.cpp
index 86e59d256..f4303a31a 100644
--- a/src/lib/block/gost_28147/gost_28147.cpp
+++ b/src/lib/block/gost_28147/gost_28147.cpp
@@ -108,7 +108,7 @@ void GOST_ROUND2(uint32_t& N1, uint32_t& N2,
 */
 void GOST_28147_89::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_EK.empty() == false);
+   assert_key_material_set();
 
    for(size_t i = 0; i != blocks; ++i)
       {
@@ -140,7 +140,7 @@ void GOST_28147_89::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks)
 */
 void GOST_28147_89::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_EK.empty() == false);
+   assert_key_material_set();
 
    for(size_t i = 0; i != blocks; ++i)
       {
@@ -166,6 +166,11 @@ void GOST_28147_89::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks)
       }
    }
 
+bool GOST_28147_89::has_keying_material() const
+   {
+   return !m_EK.empty();
+   }
+
 /*
 * GOST Key Schedule
 */
diff --git a/src/lib/block/gost_28147/gost_28147.h b/src/lib/block/gost_28147/gost_28147.h
index fcb4a890f..28a02e42f 100644
--- a/src/lib/block/gost_28147/gost_28147.h
+++ b/src/lib/block/gost_28147/gost_28147.h
@@ -69,6 +69,8 @@ class GOST_28147_89 final : public Block_Cipher_Fixed_Params<8, 32>
          return std::make_unique<GOST_28147_89>(m_SBOX, m_name);
          }
 
+      bool has_keying_material() const override;
+
       /**
       * @param params the sbox parameters to use
       */
diff --git a/src/lib/block/idea/idea.cpp b/src/lib/block/idea/idea.cpp
index 66afc6a40..647f836ec 100644
--- a/src/lib/block/idea/idea.cpp
+++ b/src/lib/block/idea/idea.cpp
@@ -136,7 +136,7 @@ std::string IDEA::provider() const
 */
 void IDEA::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_EK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_IDEA_SSE2)
    if(CPUID::has_sse2())
@@ -159,7 +159,7 @@ void IDEA::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void IDEA::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_DK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_IDEA_SSE2)
    if(CPUID::has_sse2())
@@ -177,6 +177,11 @@ void IDEA::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    idea_op(in, out, blocks, m_DK.data());
    }
 
+bool IDEA::has_keying_material() const
+   {
+   return !m_EK.empty();
+   }
+
 /*
 * IDEA Key Schedule
 */
diff --git a/src/lib/block/idea/idea.h b/src/lib/block/idea/idea.h
index 45f88337c..9d7b6afed 100644
--- a/src/lib/block/idea/idea.h
+++ b/src/lib/block/idea/idea.h
@@ -27,6 +27,7 @@ class IDEA final : public Block_Cipher_Fixed_Params<8, 16>
       std::string name() const override { return "IDEA"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<IDEA>(); }
       size_t parallelism() const override;
+      bool has_keying_material() const override;
 
    private:
 #if defined(BOTAN_HAS_IDEA_SSE2)
diff --git a/src/lib/block/lion/lion.cpp b/src/lib/block/lion/lion.cpp
index ff5918391..b9a7a9998 100644
--- a/src/lib/block/lion/lion.cpp
+++ b/src/lib/block/lion/lion.cpp
@@ -15,7 +15,7 @@ namespace Botan {
 */
 void Lion::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_key1.empty() == false);
+   assert_key_material_set();
 
    const size_t LEFT_SIZE = left_size();
    const size_t RIGHT_SIZE = right_size();
@@ -47,7 +47,7 @@ void Lion::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void Lion::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_key1.empty() == false);
+   assert_key_material_set();
 
    const size_t LEFT_SIZE = left_size();
    const size_t RIGHT_SIZE = right_size();
@@ -74,6 +74,11 @@ void Lion::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool Lion::has_keying_material() const
+   {
+   return m_key1.size() > 0 && m_key2.size() > 0;
+   }
+
 /*
 * Lion Key Schedule
 */
diff --git a/src/lib/block/lion/lion.h b/src/lib/block/lion/lion.h
index 375d40232..1a3bf55ea 100644
--- a/src/lib/block/lion/lion.h
+++ b/src/lib/block/lion/lion.h
@@ -38,6 +38,7 @@ class Lion final : public BlockCipher
       void clear() override;
       std::string name() const override;
       std::unique_ptr<BlockCipher> new_object() const override;
+      bool has_keying_material() const override;
 
       /**
       * @param hash the hash to use internally
diff --git a/src/lib/block/noekeon/noekeon.cpp b/src/lib/block/noekeon/noekeon.cpp
index e6aac1e34..0d72a2a30 100644
--- a/src/lib/block/noekeon/noekeon.cpp
+++ b/src/lib/block/noekeon/noekeon.cpp
@@ -111,7 +111,7 @@ const uint8_t Noekeon::RC[] = {
 */
 void Noekeon::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_EK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_NOEKEON_SIMD)
    if(CPUID::has_simd_32())
@@ -164,7 +164,7 @@ void Noekeon::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void Noekeon::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_DK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_NOEKEON_SIMD)
    if(CPUID::has_simd_32())
@@ -212,6 +212,11 @@ void Noekeon::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool Noekeon::has_keying_material() const
+   {
+   return !m_EK.empty();
+   }
+
 /*
 * Noekeon Key Schedule
 */
diff --git a/src/lib/block/noekeon/noekeon.h b/src/lib/block/noekeon/noekeon.h
index 05244b523..2a5834c13 100644
--- a/src/lib/block/noekeon/noekeon.h
+++ b/src/lib/block/noekeon/noekeon.h
@@ -26,6 +26,7 @@ class Noekeon final : public Block_Cipher_Fixed_Params<16, 16>
       std::string name() const override { return "Noekeon"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Noekeon>(); }
       size_t parallelism() const override;
+      bool has_keying_material() const override;
 
    private:
 #if defined(BOTAN_HAS_NOEKEON_SIMD)
diff --git a/src/lib/block/seed/seed.cpp b/src/lib/block/seed/seed.cpp
index 2177f765a..e5242ec97 100644
--- a/src/lib/block/seed/seed.cpp
+++ b/src/lib/block/seed/seed.cpp
@@ -76,7 +76,7 @@ inline uint32_t SEED_G(uint32_t X)
 */
 void SEED::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_K.empty() == false);
+   assert_key_material_set();
 
    for(size_t i = 0; i != blocks; ++i)
       {
@@ -116,7 +116,7 @@ void SEED::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void SEED::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_K.empty() == false);
+   assert_key_material_set();
 
    for(size_t i = 0; i != blocks; ++i)
       {
@@ -151,6 +151,11 @@ void SEED::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool SEED::has_keying_material() const
+   {
+   return !m_K.empty();
+   }
+
 /*
 * SEED Key Schedule
 */
diff --git a/src/lib/block/seed/seed.h b/src/lib/block/seed/seed.h
index b054202bb..90436bf17 100644
--- a/src/lib/block/seed/seed.h
+++ b/src/lib/block/seed/seed.h
@@ -24,6 +24,8 @@ class SEED final : public Block_Cipher_Fixed_Params<16, 16>
       void clear() override;
       std::string name() const override { return "SEED"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<SEED>(); }
+      bool has_keying_material() const override;
+
    private:
       void key_schedule(const uint8_t[], size_t) override;
 
diff --git a/src/lib/block/serpent/serpent.cpp b/src/lib/block/serpent/serpent.cpp
index bfdfea7c7..757e35dcc 100644
--- a/src/lib/block/serpent/serpent.cpp
+++ b/src/lib/block/serpent/serpent.cpp
@@ -23,7 +23,7 @@ void Serpent::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
    using namespace Botan::Serpent_F;
 
-   verify_key_set(m_round_key.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_SERPENT_AVX2)
    if(CPUID::has_avx2())
@@ -102,7 +102,7 @@ void Serpent::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
    using namespace Botan::Serpent_F;
 
-   verify_key_set(m_round_key.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_SERPENT_AVX2)
    if(CPUID::has_avx2())
@@ -174,6 +174,11 @@ void Serpent::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool Serpent::has_keying_material() const
+   {
+   return !m_round_key.empty();
+   }
+
 /*
 * Serpent Key Schedule
 */
diff --git a/src/lib/block/serpent/serpent.h b/src/lib/block/serpent/serpent.h
index df23d6996..66518bf3a 100644
--- a/src/lib/block/serpent/serpent.h
+++ b/src/lib/block/serpent/serpent.h
@@ -28,6 +28,7 @@ class Serpent final : public Block_Cipher_Fixed_Params<16, 16, 32, 8>
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Serpent>(); }
 
       size_t parallelism() const override { return 4; }
+      bool has_keying_material() const override;
 
    private:
 
diff --git a/src/lib/block/shacal2/shacal2.cpp b/src/lib/block/shacal2/shacal2.cpp
index 28fb4d6e8..a45efd616 100644
--- a/src/lib/block/shacal2/shacal2.cpp
+++ b/src/lib/block/shacal2/shacal2.cpp
@@ -46,7 +46,7 @@ inline void SHACAL2_Rev(uint32_t A, uint32_t B, uint32_t C, uint32_t& D,
 */
 void SHACAL2::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_RK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_SHACAL2_X86)
    if(CPUID::has_intel_sha())
@@ -123,7 +123,7 @@ void SHACAL2::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void SHACAL2::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_RK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_SHACAL2_AVX2)
    if(CPUID::has_avx2())
@@ -181,6 +181,11 @@ void SHACAL2::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool SHACAL2::has_keying_material() const
+   {
+   return !m_RK.empty();
+   }
+
 /*
 * SHACAL2 Key Schedule
 */
diff --git a/src/lib/block/shacal2/shacal2.h b/src/lib/block/shacal2/shacal2.h
index 3e8b6a056..1b2d4f4e3 100644
--- a/src/lib/block/shacal2/shacal2.h
+++ b/src/lib/block/shacal2/shacal2.h
@@ -26,6 +26,7 @@ class SHACAL2 final : public Block_Cipher_Fixed_Params<32, 16, 64, 4>
       std::string name() const override { return "SHACAL2"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<SHACAL2>(); }
       size_t parallelism() const override;
+      bool has_keying_material() const override;
 
    private:
       void key_schedule(const uint8_t[], size_t) override;
diff --git a/src/lib/block/sm4/sm4.cpp b/src/lib/block/sm4/sm4.cpp
index f1023c617..0bcaeb7cd 100644
--- a/src/lib/block/sm4/sm4.cpp
+++ b/src/lib/block/sm4/sm4.cpp
@@ -179,7 +179,7 @@ void SM4_D(uint32_t& B0, uint32_t& B1, uint32_t& B2, uint32_t& B3,
 */
 void SM4::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_RK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_SM4_ARMV8)
    if(CPUID::has_arm_sm4())
@@ -242,7 +242,7 @@ void SM4::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void SM4::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_RK.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_SM4_ARMV8)
    if(CPUID::has_arm_sm4())
@@ -300,6 +300,11 @@ void SM4::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool SM4::has_keying_material() const
+   {
+   return !m_RK.empty();
+   }
+
 /*
 * SM4 Key Schedule
 */
diff --git a/src/lib/block/sm4/sm4.h b/src/lib/block/sm4/sm4.h
index 4948b09b7..033e5f64d 100644
--- a/src/lib/block/sm4/sm4.h
+++ b/src/lib/block/sm4/sm4.h
@@ -27,6 +27,7 @@ class SM4 final : public Block_Cipher_Fixed_Params<16, 16>
 
       std::string provider() const override;
       size_t parallelism() const override;
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t[], size_t) override;
 
diff --git a/src/lib/block/threefish_512/threefish_512.cpp b/src/lib/block/threefish_512/threefish_512.cpp
index 0a37744d7..dfb3e3eb7 100644
--- a/src/lib/block/threefish_512/threefish_512.cpp
+++ b/src/lib/block/threefish_512/threefish_512.cpp
@@ -209,7 +209,7 @@ void Threefish_512::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks)
    {
    using namespace Threefish_F;
 
-   verify_key_set(m_K.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_THREEFISH_512_AVX2)
    if(CPUID::has_avx2())
@@ -247,7 +247,7 @@ void Threefish_512::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks)
    {
    using namespace Threefish_F;
 
-   verify_key_set(m_K.empty() == false);
+   assert_key_material_set();
 
 #if defined(BOTAN_HAS_THREEFISH_512_AVX2)
    if(CPUID::has_avx2())
@@ -290,6 +290,11 @@ void Threefish_512::set_tweak(const uint8_t tweak[], size_t len)
    m_T[2] = m_T[0] ^ m_T[1];
    }
 
+bool Threefish_512::has_keying_material() const
+   {
+   return !m_K.empty();
+   }
+
 void Threefish_512::key_schedule(const uint8_t key[], size_t /*length*/)
    {
    // todo: define key schedule for smaller keys
diff --git a/src/lib/block/threefish_512/threefish_512.h b/src/lib/block/threefish_512/threefish_512.h
index 76c7837a8..72d3d8645 100644
--- a/src/lib/block/threefish_512/threefish_512.h
+++ b/src/lib/block/threefish_512/threefish_512.h
@@ -29,6 +29,7 @@ class Threefish_512 final :
       std::string name() const override { return "Threefish-512"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Threefish_512>(); }
       size_t parallelism() const override;
+      bool has_keying_material() const override;
 
    private:
 
diff --git a/src/lib/block/twofish/twofish.cpp b/src/lib/block/twofish/twofish.cpp
index dfb807c84..3257b079e 100644
--- a/src/lib/block/twofish/twofish.cpp
+++ b/src/lib/block/twofish/twofish.cpp
@@ -61,7 +61,7 @@ inline void TF_D(uint32_t A, uint32_t B, uint32_t& C, uint32_t& D,
 */
 void Twofish::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SB.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -133,7 +133,7 @@ void Twofish::encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
 */
 void Twofish::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
    {
-   verify_key_set(m_SB.empty() == false);
+   assert_key_material_set();
 
    while(blocks >= 2)
       {
@@ -200,6 +200,11 @@ void Twofish::decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const
       }
    }
 
+bool Twofish::has_keying_material() const
+   {
+   return !m_SB.empty();
+   }
+
 /*
 * Twofish Key Schedule
 */
diff --git a/src/lib/block/twofish/twofish.h b/src/lib/block/twofish/twofish.h
index 5e5b86ab4..98f1a537b 100644
--- a/src/lib/block/twofish/twofish.h
+++ b/src/lib/block/twofish/twofish.h
@@ -24,6 +24,8 @@ class Twofish final : public Block_Cipher_Fixed_Params<16, 16, 32, 8>
       void clear() override;
       std::string name() const override { return "Twofish"; }
       std::unique_ptr<BlockCipher> new_object() const override { return std::make_unique<Twofish>(); }
+
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t[], size_t) override;
 
diff --git a/src/lib/hash/blake2/blake2b.cpp b/src/lib/hash/blake2/blake2b.cpp
index 939238196..f602c3788 100644
--- a/src/lib/hash/blake2/blake2b.cpp
+++ b/src/lib/hash/blake2/blake2b.cpp
@@ -196,7 +196,7 @@ void BLAKE2b::final_result(uint8_t output[])
 
 Key_Length_Specification BLAKE2b::key_spec() const
    {
-   return Key_Length_Specification(0, 64);
+   return Key_Length_Specification(1, 64);
    }
 
 std::string BLAKE2b::name() const
@@ -214,6 +214,11 @@ std::unique_ptr<HashFunction> BLAKE2b::copy_state() const
    return std::make_unique<BLAKE2b>(*this);
    }
 
+bool BLAKE2b::has_keying_material() const
+   {
+   return m_key_size > 0;
+   }
+
 void BLAKE2b::key_schedule(const uint8_t key[], size_t length)
    {
    BOTAN_ASSERT_NOMSG(length <= m_buffer.size());
diff --git a/src/lib/hash/blake2/blake2b.h b/src/lib/hash/blake2/blake2b.h
index ec3a24e23..fb4caa4b4 100644
--- a/src/lib/hash/blake2/blake2b.h
+++ b/src/lib/hash/blake2/blake2b.h
@@ -37,6 +37,7 @@ class BLAKE2b final : public HashFunction, public SymmetricAlgorithm
       std::unique_ptr<HashFunction> new_object() const override;
       std::string name() const override;
       void clear() override;
+      bool has_keying_material() const override;
 
       std::unique_ptr<HashFunction> copy_state() const override;
 
diff --git a/src/lib/mac/blake2mac/blake2bmac.h b/src/lib/mac/blake2mac/blake2bmac.h
index 179f95ef4..8067b6d14 100644
--- a/src/lib/mac/blake2mac/blake2bmac.h
+++ b/src/lib/mac/blake2mac/blake2bmac.h
@@ -31,6 +31,8 @@ class BLAKE2bMAC final : public MessageAuthenticationCode
 
       void clear() override;
 
+      bool has_keying_material() const override { return m_blake.has_keying_material(); }
+
       Key_Length_Specification key_spec() const override
          {
          return m_blake.key_spec();
@@ -44,13 +46,13 @@ class BLAKE2bMAC final : public MessageAuthenticationCode
 
       void add_data(const uint8_t input[], size_t length) override
          {
-         verify_key_set(m_blake.key_size() > 0);
+         assert_key_material_set();
          m_blake.update(input, length);
          }
 
       void final_result(uint8_t out[]) override
          {
-         verify_key_set(m_blake.key_size() > 0);
+         assert_key_material_set();
          m_blake.final(out);
          }
 
diff --git a/src/lib/mac/cmac/cmac.cpp b/src/lib/mac/cmac/cmac.cpp
index 204c5a3ac..7418be620 100644
--- a/src/lib/mac/cmac/cmac.cpp
+++ b/src/lib/mac/cmac/cmac.cpp
@@ -64,6 +64,11 @@ void CMAC::final_result(uint8_t mac[])
    m_position = 0;
    }
 
+bool CMAC::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 /*
 * CMAC Key Schedule
 */
diff --git a/src/lib/mac/cmac/cmac.h b/src/lib/mac/cmac/cmac.h
index 629bf0955..553d02467 100644
--- a/src/lib/mac/cmac/cmac.h
+++ b/src/lib/mac/cmac/cmac.h
@@ -24,6 +24,7 @@ class CMAC final : public MessageAuthenticationCode
       std::unique_ptr<MessageAuthenticationCode> new_object() const override;
 
       void clear() override;
+      bool has_keying_material() const override;
 
       Key_Length_Specification key_spec() const override
          {
diff --git a/src/lib/mac/gmac/gmac.cpp b/src/lib/mac/gmac/gmac.cpp
index d1ac027bc..ab519b1fd 100644
--- a/src/lib/mac/gmac/gmac.cpp
+++ b/src/lib/mac/gmac/gmac.cpp
@@ -79,6 +79,11 @@ void GMAC::add_data(const uint8_t input[], size_t size)
       }
    }
 
+bool GMAC::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 void GMAC::key_schedule(const uint8_t key[], size_t size)
    {
    clear();
diff --git a/src/lib/mac/gmac/gmac.h b/src/lib/mac/gmac/gmac.h
index 6f46f435a..6a05f4dd3 100644
--- a/src/lib/mac/gmac/gmac.h
+++ b/src/lib/mac/gmac/gmac.h
@@ -32,6 +32,8 @@ class GMAC final : public MessageAuthenticationCode
 
       Key_Length_Specification key_spec() const override;
 
+      bool has_keying_material() const override;
+
       /**
       * Creates a new GMAC instance.
       *
diff --git a/src/lib/mac/hmac/hmac.cpp b/src/lib/mac/hmac/hmac.cpp
index d37dfab0f..74cff3e70 100644
--- a/src/lib/mac/hmac/hmac.cpp
+++ b/src/lib/mac/hmac/hmac.cpp
@@ -16,7 +16,7 @@ namespace Botan {
 */
 void HMAC::add_data(const uint8_t input[], size_t length)
    {
-   verify_key_set(m_ikey.empty() == false);
+   assert_key_material_set();
    m_hash->update(input, length);
    }
 
@@ -25,7 +25,7 @@ void HMAC::add_data(const uint8_t input[], size_t length)
 */
 void HMAC::final_result(uint8_t mac[])
    {
-   verify_key_set(m_okey.empty() == false);
+   assert_key_material_set();
    m_hash->final(mac);
    m_hash->update(m_okey);
    m_hash->update(mac, m_hash_output_length);
@@ -44,6 +44,11 @@ size_t HMAC::output_length() const
    return m_hash_output_length;
    }
 
+bool HMAC::has_keying_material() const
+   {
+   return !m_okey.empty();
+   }
+
 /*
 * HMAC Key Schedule
 */
diff --git a/src/lib/mac/hmac/hmac.h b/src/lib/mac/hmac/hmac.h
index aa709b6db..a9271ea35 100644
--- a/src/lib/mac/hmac/hmac.h
+++ b/src/lib/mac/hmac/hmac.h
@@ -27,6 +27,8 @@ class HMAC final : public MessageAuthenticationCode
 
       Key_Length_Specification key_spec() const override;
 
+      bool has_keying_material() const override;
+
       /**
       * @param hash the hash to use for HMACing
       */
diff --git a/src/lib/mac/poly1305/poly1305.cpp b/src/lib/mac/poly1305/poly1305.cpp
index bcc2fef33..50f93978a 100644
--- a/src/lib/mac/poly1305/poly1305.cpp
+++ b/src/lib/mac/poly1305/poly1305.cpp
@@ -151,6 +151,11 @@ void Poly1305::clear()
    m_buf_pos = 0;
    }
 
+bool Poly1305::has_keying_material() const
+   {
+   return m_poly.size() == 8;
+   }
+
 void Poly1305::key_schedule(const uint8_t key[], size_t /*length*/)
    {
    m_buf_pos = 0;
@@ -162,7 +167,7 @@ void Poly1305::key_schedule(const uint8_t key[], size_t /*length*/)
 
 void Poly1305::add_data(const uint8_t input[], size_t length)
    {
-   verify_key_set(m_poly.size() == 8);
+   assert_key_material_set();
 
    if(m_buf_pos)
       {
@@ -189,7 +194,7 @@ void Poly1305::add_data(const uint8_t input[], size_t length)
 
 void Poly1305::final_result(uint8_t out[])
    {
-   verify_key_set(m_poly.size() == 8);
+   assert_key_material_set();
 
    if(m_buf_pos != 0)
       {
diff --git a/src/lib/mac/poly1305/poly1305.h b/src/lib/mac/poly1305/poly1305.h
index 750538aeb..e16a968fe 100644
--- a/src/lib/mac/poly1305/poly1305.h
+++ b/src/lib/mac/poly1305/poly1305.h
@@ -34,6 +34,8 @@ class Poly1305 final : public MessageAuthenticationCode
          }
 
       bool fresh_key_required_per_message() const override { return true; }
+
+      bool has_keying_material() const override;
    private:
       void add_data(const uint8_t[], size_t) override;
       void final_result(uint8_t[]) override;
diff --git a/src/lib/mac/siphash/siphash.cpp b/src/lib/mac/siphash/siphash.cpp
index b7e775490..bc83c9a4b 100644
--- a/src/lib/mac/siphash/siphash.cpp
+++ b/src/lib/mac/siphash/siphash.cpp
@@ -41,7 +41,7 @@ void SipRounds(uint64_t M, secure_vector<uint64_t>& V, size_t r)
 
 void SipHash::add_data(const uint8_t input[], size_t length)
    {
-   verify_key_set(m_V.empty() == false);
+   assert_key_material_set();
 
    // SipHash counts the message length mod 256
    m_words += static_cast<uint8_t>(length);
@@ -80,7 +80,7 @@ void SipHash::add_data(const uint8_t input[], size_t length)
 
 void SipHash::final_result(uint8_t mac[])
    {
-   verify_key_set(m_V.empty() == false);
+   assert_key_material_set();
 
    if(m_mbuf_pos == 0)
       {
@@ -109,6 +109,11 @@ void SipHash::final_result(uint8_t mac[])
    m_words = 0;
    }
 
+bool SipHash::has_keying_material() const
+   {
+   return !m_V.empty();
+   }
+
 void SipHash::key_schedule(const uint8_t key[], size_t /*length*/)
    {
    const uint64_t K0 = load_le<uint64_t>(key, 0);
diff --git a/src/lib/mac/siphash/siphash.h b/src/lib/mac/siphash/siphash.h
index 47429ce5a..faa6b5bf0 100644
--- a/src/lib/mac/siphash/siphash.h
+++ b/src/lib/mac/siphash/siphash.h
@@ -24,6 +24,8 @@ class SipHash final : public MessageAuthenticationCode
 
       size_t output_length() const override { return 8; }
 
+      bool has_keying_material() const override;
+
       Key_Length_Specification key_spec() const override
          {
          return Key_Length_Specification(16);
diff --git a/src/lib/mac/x919_mac/x919_mac.cpp b/src/lib/mac/x919_mac/x919_mac.cpp
index 794d521e4..1000b782a 100644
--- a/src/lib/mac/x919_mac/x919_mac.cpp
+++ b/src/lib/mac/x919_mac/x919_mac.cpp
@@ -14,7 +14,7 @@ namespace Botan {
 */
 void ANSI_X919_MAC::add_data(const uint8_t input[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    size_t xored = std::min(8 - m_position, length);
    xor_buf(&m_state[m_position], input, xored);
@@ -50,6 +50,12 @@ void ANSI_X919_MAC::final_result(uint8_t mac[])
    m_position = 0;
    }
 
+bool ANSI_X919_MAC::has_keying_material() const
+   {
+   return m_des1->has_keying_material() &&
+      m_des2->has_keying_material();
+   }
+
 /*
 * ANSI X9.19 MAC Key Schedule
 */
diff --git a/src/lib/mac/x919_mac/x919_mac.h b/src/lib/mac/x919_mac/x919_mac.h
index b7a758f54..7855fcf29 100644
--- a/src/lib/mac/x919_mac/x919_mac.h
+++ b/src/lib/mac/x919_mac/x919_mac.h
@@ -30,6 +30,8 @@ class ANSI_X919_MAC final : public MessageAuthenticationCode
          return Key_Length_Specification(8, 16, 8);
          }
 
+      bool has_keying_material() const override;
+
       ANSI_X919_MAC();
 
       ANSI_X919_MAC(const ANSI_X919_MAC&) = delete;
diff --git a/src/lib/misc/fpe_fe1/fpe_fe1.cpp b/src/lib/misc/fpe_fe1/fpe_fe1.cpp
index 263b0fbdc..eed064d28 100644
--- a/src/lib/misc/fpe_fe1/fpe_fe1.cpp
+++ b/src/lib/misc/fpe_fe1/fpe_fe1.cpp
@@ -105,6 +105,11 @@ Key_Length_Specification FPE_FE1::key_spec() const
    return m_mac->key_spec();
    }
 
+bool FPE_FE1::has_keying_material() const
+   {
+   return m_mac->has_keying_material();
+   }
+
 void FPE_FE1::key_schedule(const uint8_t key[], size_t length)
    {
    m_mac->set_key(key, length);
diff --git a/src/lib/misc/fpe_fe1/fpe_fe1.h b/src/lib/misc/fpe_fe1/fpe_fe1.h
index d9f760f0d..1d463bee3 100644
--- a/src/lib/misc/fpe_fe1/fpe_fe1.h
+++ b/src/lib/misc/fpe_fe1/fpe_fe1.h
@@ -42,6 +42,8 @@ class BOTAN_PUBLIC_API(2,5) FPE_FE1 final : public SymmetricAlgorithm
 
       Key_Length_Specification key_spec() const override;
 
+      bool has_keying_material() const override;
+
       std::string name() const override;
 
       void clear() override;
diff --git a/src/lib/modes/aead/ccm/ccm.cpp b/src/lib/modes/aead/ccm/ccm.cpp
index 8ebd31c32..e99c47af5 100644
--- a/src/lib/modes/aead/ccm/ccm.cpp
+++ b/src/lib/modes/aead/ccm/ccm.cpp
@@ -81,6 +81,11 @@ Key_Length_Specification CCM_Mode::key_spec() const
    return m_cipher->key_spec();
    }
 
+bool CCM_Mode::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 void CCM_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    m_cipher->set_key(key, length);
diff --git a/src/lib/modes/aead/ccm/ccm.h b/src/lib/modes/aead/ccm/ccm.h
index 9bbc1f35e..ba3f6cfe5 100644
--- a/src/lib/modes/aead/ccm/ccm.h
+++ b/src/lib/modes/aead/ccm/ccm.h
@@ -21,32 +21,33 @@ namespace Botan {
 class CCM_Mode : public AEAD_Mode
    {
    public:
-      size_t process(uint8_t buf[], size_t sz) override;
+      size_t process(uint8_t buf[], size_t sz) override final;
 
-      void set_associated_data(const uint8_t ad[], size_t ad_len) override;
+      void set_associated_data(const uint8_t ad[], size_t ad_len) override final;
 
-      bool associated_data_requires_key() const override { return false; }
+      bool associated_data_requires_key() const override final { return false; }
 
-      std::string name() const override;
+      std::string name() const override final;
 
-      size_t update_granularity() const override;
+      size_t update_granularity() const override final;
 
-      size_t ideal_granularity() const override;
+      size_t ideal_granularity() const override final;
 
-      bool requires_entire_message() const override;
+      bool requires_entire_message() const override final;
 
-      Key_Length_Specification key_spec() const override;
+      Key_Length_Specification key_spec() const override final;
 
-      bool valid_nonce_length(size_t) const override;
+      bool valid_nonce_length(size_t) const override final;
 
-      size_t default_nonce_length() const override;
+      size_t default_nonce_length() const override final;
 
-      void clear() override;
+      void clear() override final;
 
-      void reset() override;
+      void reset() override final;
 
-      size_t tag_size() const override { return m_tag_size; }
+      size_t tag_size() const override final { return m_tag_size; }
 
+      bool has_keying_material() const override final;
    protected:
       CCM_Mode(std::unique_ptr<BlockCipher> cipher, size_t tag_size, size_t L);
 
@@ -65,9 +66,9 @@ class CCM_Mode : public AEAD_Mode
       secure_vector<uint8_t> format_b0(size_t msg_size);
       secure_vector<uint8_t> format_c0();
    private:
-      void start_msg(const uint8_t nonce[], size_t nonce_len) override;
+      void start_msg(const uint8_t nonce[], size_t nonce_len) override final;
 
-      void key_schedule(const uint8_t key[], size_t length) override;
+      void key_schedule(const uint8_t key[], size_t length) override final;
 
       const size_t m_tag_size;
       const size_t m_L;
diff --git a/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp b/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp
index 0063d2e2f..d5fa9f612 100644
--- a/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp
+++ b/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.cpp
@@ -48,6 +48,11 @@ void ChaCha20Poly1305_Mode::reset()
    m_nonce_len = 0;
    }
 
+bool ChaCha20Poly1305_Mode::has_keying_material() const
+   {
+   return m_chacha->has_keying_material();
+   }
+
 void ChaCha20Poly1305_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    m_chacha->set_key(key, length);
diff --git a/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h b/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h
index d99a32147..d28686dba 100644
--- a/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h
+++ b/src/lib/modes/aead/chacha20poly1305/chacha20poly1305.h
@@ -46,6 +46,8 @@ class ChaCha20Poly1305_Mode : public AEAD_Mode
 
       void reset() override;
 
+      bool has_keying_material() const override final;
+
    protected:
       std::unique_ptr<StreamCipher> m_chacha;
       std::unique_ptr<MessageAuthenticationCode> m_poly1305;
diff --git a/src/lib/modes/aead/eax/eax.cpp b/src/lib/modes/aead/eax/eax.cpp
index 9e042c25e..3a23a531f 100644
--- a/src/lib/modes/aead/eax/eax.cpp
+++ b/src/lib/modes/aead/eax/eax.cpp
@@ -82,7 +82,13 @@ size_t EAX_Mode::ideal_granularity() const
 
 Key_Length_Specification EAX_Mode::key_spec() const
    {
-   return m_cipher->key_spec();
+   return m_ctr->key_spec();
+   }
+
+bool EAX_Mode::has_keying_material() const
+   {
+   return m_ctr->has_keying_material() &&
+      m_cmac->has_keying_material();
    }
 
 /*
diff --git a/src/lib/modes/aead/eax/eax.h b/src/lib/modes/aead/eax/eax.h
index a1e335b3e..42a030bb2 100644
--- a/src/lib/modes/aead/eax/eax.h
+++ b/src/lib/modes/aead/eax/eax.h
@@ -22,24 +22,26 @@ namespace Botan {
 class EAX_Mode : public AEAD_Mode
    {
    public:
-      void set_associated_data(const uint8_t ad[], size_t ad_len) override;
+      void set_associated_data(const uint8_t ad[], size_t ad_len) override final;
 
       std::string name() const override final;
 
-      size_t update_granularity() const override;
+      size_t update_granularity() const override final;
 
-      size_t ideal_granularity() const override;
+      size_t ideal_granularity() const override final;
 
-      Key_Length_Specification key_spec() const override;
+      Key_Length_Specification key_spec() const override final;
 
       // EAX supports arbitrary nonce lengths
-      bool valid_nonce_length(size_t) const override { return true; }
+      bool valid_nonce_length(size_t) const override final { return true; }
 
-      size_t tag_size() const override { return m_tag_size; }
+      size_t tag_size() const override final { return m_tag_size; }
 
-      void clear() override;
+      void clear() override final;
 
-      void reset() override;
+      void reset() override final;
+
+      bool has_keying_material() const override final;
 
    protected:
       /**
@@ -60,9 +62,9 @@ class EAX_Mode : public AEAD_Mode
 
       secure_vector<uint8_t> m_nonce_mac;
    private:
-      void start_msg(const uint8_t nonce[], size_t nonce_len) override;
+      void start_msg(const uint8_t nonce[], size_t nonce_len) override final;
 
-      void key_schedule(const uint8_t key[], size_t length) override;
+      void key_schedule(const uint8_t key[], size_t length) override final;
    };
 
 /**
diff --git a/src/lib/modes/aead/gcm/gcm.cpp b/src/lib/modes/aead/gcm/gcm.cpp
index c0fe5c356..ee714177d 100644
--- a/src/lib/modes/aead/gcm/gcm.cpp
+++ b/src/lib/modes/aead/gcm/gcm.cpp
@@ -77,6 +77,11 @@ Key_Length_Specification GCM_Mode::key_spec() const
    return m_ctr->key_spec();
    }
 
+bool GCM_Mode::has_keying_material() const
+   {
+   return m_ctr->has_keying_material();
+   }
+
 void GCM_Mode::key_schedule(const uint8_t key[], size_t keylen)
    {
    m_ctr->set_key(key, keylen);
diff --git a/src/lib/modes/aead/gcm/gcm.h b/src/lib/modes/aead/gcm/gcm.h
index 0b2edd418..4f7c597d2 100644
--- a/src/lib/modes/aead/gcm/gcm.h
+++ b/src/lib/modes/aead/gcm/gcm.h
@@ -24,25 +24,27 @@ class GHASH;
 class GCM_Mode : public AEAD_Mode
    {
    public:
-      void set_associated_data(const uint8_t ad[], size_t ad_len) override;
+      void set_associated_data(const uint8_t ad[], size_t ad_len) override final;
 
       std::string name() const override final;
 
-      size_t update_granularity() const override;
+      size_t update_granularity() const override final;
 
-      size_t ideal_granularity() const override;
+      size_t ideal_granularity() const override final;
 
-      Key_Length_Specification key_spec() const override;
+      Key_Length_Specification key_spec() const override final;
 
-      bool valid_nonce_length(size_t len) const override;
+      bool valid_nonce_length(size_t len) const override final;
 
       size_t tag_size() const override final { return m_tag_size; }
 
-      void clear() override;
+      void clear() override final;
 
-      void reset() override;
+      void reset() override final;
 
-      std::string provider() const override;
+      std::string provider() const override final;
+
+      bool has_keying_material() const override final;
    protected:
       GCM_Mode(std::unique_ptr<BlockCipher> cipher, size_t tag_size);
 
diff --git a/src/lib/modes/aead/ocb/ocb.cpp b/src/lib/modes/aead/ocb/ocb.cpp
index 5bbb31872..ec4a5ecb4 100644
--- a/src/lib/modes/aead/ocb/ocb.cpp
+++ b/src/lib/modes/aead/ocb/ocb.cpp
@@ -230,6 +230,11 @@ Key_Length_Specification OCB_Mode::key_spec() const
    return m_cipher->key_spec();
    }
 
+bool OCB_Mode::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 void OCB_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    m_cipher->set_key(key, length);
@@ -238,7 +243,7 @@ void OCB_Mode::key_schedule(const uint8_t key[], size_t length)
 
 void OCB_Mode::set_associated_data(const uint8_t ad[], size_t ad_len)
    {
-   verify_key_set(m_L != nullptr);
+   assert_key_material_set();
    m_ad_hash = ocb_hash(*m_L, *m_cipher, ad, ad_len);
    }
 
@@ -339,7 +344,7 @@ void OCB_Mode::start_msg(const uint8_t nonce[], size_t nonce_len)
    if(!valid_nonce_length(nonce_len))
       throw Invalid_IV_Length(name(), nonce_len);
 
-   verify_key_set(m_L != nullptr);
+   assert_key_material_set();
 
    m_L->init(update_nonce(nonce, nonce_len));
    zeroise(m_checksum);
@@ -348,7 +353,7 @@ void OCB_Mode::start_msg(const uint8_t nonce[], size_t nonce_len)
 
 void OCB_Encryption::encrypt(uint8_t buffer[], size_t blocks)
    {
-   verify_key_set(m_L != nullptr);
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_L->initialized());
 
    const size_t BS = block_size();
@@ -379,7 +384,7 @@ size_t OCB_Encryption::process(uint8_t buf[], size_t sz)
 
 void OCB_Encryption::finish(secure_vector<uint8_t>& buffer, size_t offset)
    {
-   verify_key_set(m_L != nullptr);
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_L->initialized());
 
    const size_t BS = block_size();
@@ -439,7 +444,7 @@ void OCB_Encryption::finish(secure_vector<uint8_t>& buffer, size_t offset)
 
 void OCB_Decryption::decrypt(uint8_t buffer[], size_t blocks)
    {
-   verify_key_set(m_L != nullptr);
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_L->initialized());
 
    const size_t BS = block_size();
@@ -470,7 +475,7 @@ size_t OCB_Decryption::process(uint8_t buf[], size_t sz)
 
 void OCB_Decryption::finish(secure_vector<uint8_t>& buffer, size_t offset)
    {
-   verify_key_set(m_L != nullptr);
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_L->initialized());
 
    const size_t BS = block_size();
diff --git a/src/lib/modes/aead/ocb/ocb.h b/src/lib/modes/aead/ocb/ocb.h
index b7d960c96..0345c4226 100644
--- a/src/lib/modes/aead/ocb/ocb.h
+++ b/src/lib/modes/aead/ocb/ocb.h
@@ -31,23 +31,25 @@ class L_computer;
 class BOTAN_TEST_API OCB_Mode : public AEAD_Mode
    {
    public:
-      void set_associated_data(const uint8_t ad[], size_t ad_len) override;
+      void set_associated_data(const uint8_t ad[], size_t ad_len) override final;
 
-      std::string name() const override;
+      std::string name() const override final;
 
-      size_t update_granularity() const override;
+      size_t update_granularity() const override final;
 
-      size_t ideal_granularity() const override;
+      size_t ideal_granularity() const override final;
 
-      Key_Length_Specification key_spec() const override;
+      Key_Length_Specification key_spec() const override final;
 
-      bool valid_nonce_length(size_t) const override;
+      bool valid_nonce_length(size_t) const override final;
 
-      size_t tag_size() const override { return m_tag_size; }
+      size_t tag_size() const override final { return m_tag_size; }
 
-      void clear() override;
+      void clear() override final;
 
-      void reset() override;
+      void reset() override final;
+
+      bool has_keying_material() const override final;
 
       ~OCB_Mode();
    protected:
@@ -70,9 +72,9 @@ class BOTAN_TEST_API OCB_Mode : public AEAD_Mode
       secure_vector<uint8_t> m_checksum;
       secure_vector<uint8_t> m_ad_hash;
    private:
-      void start_msg(const uint8_t nonce[], size_t nonce_len) override;
+      void start_msg(const uint8_t nonce[], size_t nonce_len) override final;
 
-      void key_schedule(const uint8_t key[], size_t length) override;
+      void key_schedule(const uint8_t key[], size_t length) override final;
 
       const secure_vector<uint8_t>& update_nonce(const uint8_t nonce[], size_t nonce_len);
 
diff --git a/src/lib/modes/aead/siv/siv.cpp b/src/lib/modes/aead/siv/siv.cpp
index fafd0b115..35f8d54fe 100644
--- a/src/lib/modes/aead/siv/siv.cpp
+++ b/src/lib/modes/aead/siv/siv.cpp
@@ -72,6 +72,12 @@ Key_Length_Specification SIV_Mode::key_spec() const
    return m_mac->key_spec().multiple(2);
    }
 
+bool SIV_Mode::has_keying_material() const
+   {
+   return m_ctr->has_keying_material() &&
+      m_mac->has_keying_material();
+   }
+
 void SIV_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    const size_t keylen = length / 2;
diff --git a/src/lib/modes/aead/siv/siv.h b/src/lib/modes/aead/siv/siv.h
index 2d5f23d7f..d2e0d8b84 100644
--- a/src/lib/modes/aead/siv/siv.h
+++ b/src/lib/modes/aead/siv/siv.h
@@ -23,7 +23,7 @@ class MessageAuthenticationCode;
 class BOTAN_TEST_API SIV_Mode : public AEAD_Mode
    {
    public:
-      size_t process(uint8_t buf[], size_t size) override;
+      size_t process(uint8_t buf[], size_t size) override final;
 
       /**
       * Sets the nth element of the vector of associated data
@@ -31,32 +31,34 @@ class BOTAN_TEST_API SIV_Mode : public AEAD_Mode
       * @param ad associated data
       * @param ad_len length of associated data in bytes
       */
-      void set_associated_data_n(size_t n, const uint8_t ad[], size_t ad_len) override;
+      void set_associated_data_n(size_t n, const uint8_t ad[], size_t ad_len) override final;
 
-      size_t maximum_associated_data_inputs() const override;
+      size_t maximum_associated_data_inputs() const override final;
 
-      void set_associated_data(const uint8_t ad[], size_t ad_len) override
+      void set_associated_data(const uint8_t ad[], size_t ad_len) override final
          {
          set_associated_data_n(0, ad, ad_len);
          }
 
-      std::string name() const override;
+      std::string name() const override final;
 
-      size_t update_granularity() const override;
+      size_t update_granularity() const override final;
 
-      size_t ideal_granularity() const override;
+      size_t ideal_granularity() const override final;
 
-      Key_Length_Specification key_spec() const override;
+      Key_Length_Specification key_spec() const override final;
 
-      bool valid_nonce_length(size_t) const override;
+      bool valid_nonce_length(size_t) const override final;
 
-      bool requires_entire_message() const override;
+      bool requires_entire_message() const override final;
 
-      void clear() override;
+      void clear() override final;
 
-      void reset() override;
+      void reset() override final;
 
-      size_t tag_size() const override { return 16; }
+      size_t tag_size() const override final { return 16; }
+
+      bool has_keying_material() const override final;
 
       ~SIV_Mode();
 
@@ -73,9 +75,9 @@ class BOTAN_TEST_API SIV_Mode : public AEAD_Mode
 
       secure_vector<uint8_t> S2V(const uint8_t text[], size_t text_len);
    private:
-      void start_msg(const uint8_t nonce[], size_t nonce_len) override;
+      void start_msg(const uint8_t nonce[], size_t nonce_len) override final;
 
-      void key_schedule(const uint8_t key[], size_t length) override;
+      void key_schedule(const uint8_t key[], size_t length) override final;
 
       const std::string m_name;
       const size_t m_bs;
diff --git a/src/lib/modes/cbc/cbc.cpp b/src/lib/modes/cbc/cbc.cpp
index 0c954a59d..6bc40e286 100644
--- a/src/lib/modes/cbc/cbc.cpp
+++ b/src/lib/modes/cbc/cbc.cpp
@@ -69,6 +69,11 @@ bool CBC_Mode::valid_nonce_length(size_t n) const
    return (n == 0 || n == block_size());
    }
 
+bool CBC_Mode::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 void CBC_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    m_cipher->set_key(key, length);
diff --git a/src/lib/modes/cbc/cbc.h b/src/lib/modes/cbc/cbc.h
index 2ce86e6e9..b2567ab3f 100644
--- a/src/lib/modes/cbc/cbc.h
+++ b/src/lib/modes/cbc/cbc.h
@@ -21,22 +21,23 @@ namespace Botan {
 class CBC_Mode : public Cipher_Mode
    {
    public:
-      std::string name() const override;
+      std::string name() const override final;
 
-      size_t update_granularity() const override;
+      size_t update_granularity() const override final;
 
-      size_t ideal_granularity() const override;
+      size_t ideal_granularity() const override final;
 
-      Key_Length_Specification key_spec() const override;
+      Key_Length_Specification key_spec() const override final;
 
-      size_t default_nonce_length() const override;
+      size_t default_nonce_length() const override final;
 
       bool valid_nonce_length(size_t n) const override;
 
-      void clear() override;
+      void clear() override final;
 
       void reset() override;
 
+      bool has_keying_material() const override final;
    protected:
       CBC_Mode(std::unique_ptr<BlockCipher> cipher,
                std::unique_ptr<BlockCipherModePaddingMethod> padding);
diff --git a/src/lib/modes/cfb/cfb.cpp b/src/lib/modes/cfb/cfb.cpp
index 12727dd8c..11a95f066 100644
--- a/src/lib/modes/cfb/cfb.cpp
+++ b/src/lib/modes/cfb/cfb.cpp
@@ -77,6 +77,11 @@ bool CFB_Mode::valid_nonce_length(size_t n) const
    return (n == 0 || n == block_size());
    }
 
+bool CFB_Mode::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 void CFB_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    m_cipher->set_key(key, length);
@@ -88,7 +93,7 @@ void CFB_Mode::start_msg(const uint8_t nonce[], size_t nonce_len)
    if(!valid_nonce_length(nonce_len))
       throw Invalid_IV_Length(name(), nonce_len);
 
-   verify_key_set(!m_keystream.empty());
+   assert_key_material_set();
 
    if(nonce_len == 0)
       {
@@ -122,7 +127,7 @@ void CFB_Mode::shift_register()
 
 size_t CFB_Encryption::process(uint8_t buf[], size_t sz)
    {
-   verify_key_set(!m_keystream.empty());
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_state.empty() == false);
 
    const size_t shift = feedback();
@@ -187,7 +192,7 @@ inline void xor_copy(uint8_t buf[], uint8_t key_buf[], size_t len)
 
 size_t CFB_Decryption::process(uint8_t buf[], size_t sz)
    {
-   verify_key_set(!m_keystream.empty());
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_state.empty() == false);
 
    const size_t shift = feedback();
diff --git a/src/lib/modes/cfb/cfb.h b/src/lib/modes/cfb/cfb.h
index 587780cba..153e932d2 100644
--- a/src/lib/modes/cfb/cfb.h
+++ b/src/lib/modes/cfb/cfb.h
@@ -39,6 +39,8 @@ class CFB_Mode : public Cipher_Mode
       void clear() override final;
 
       void reset() override final;
+
+      bool has_keying_material() const override final;
    protected:
       CFB_Mode(std::unique_ptr<BlockCipher> cipher, size_t feedback_bits);
 
diff --git a/src/lib/modes/stream_mode.h b/src/lib/modes/stream_mode.h
index 38ad8122a..8738eb044 100644
--- a/src/lib/modes/stream_mode.h
+++ b/src/lib/modes/stream_mode.h
@@ -63,6 +63,8 @@ class Stream_Cipher_Mode final : public Cipher_Mode
 
       void reset() override { /* no msg state */ }
 
+      bool has_keying_material() const override { return m_cipher->has_keying_material(); }
+
    private:
       void start_msg(const uint8_t nonce[], size_t nonce_len) override
          {
diff --git a/src/lib/modes/xts/xts.cpp b/src/lib/modes/xts/xts.cpp
index f40c1fc74..89173adfd 100644
--- a/src/lib/modes/xts/xts.cpp
+++ b/src/lib/modes/xts/xts.cpp
@@ -72,6 +72,12 @@ bool XTS_Mode::valid_nonce_length(size_t n) const
    return cipher_block_size() == n;
    }
 
+bool XTS_Mode::has_keying_material() const
+   {
+   return m_cipher->has_keying_material() &&
+      m_tweak_cipher->has_keying_material();
+   }
+
 void XTS_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    const size_t key_half = length / 2;
diff --git a/src/lib/modes/xts/xts.h b/src/lib/modes/xts/xts.h
index 7e3db2281..52cddb612 100644
--- a/src/lib/modes/xts/xts.h
+++ b/src/lib/modes/xts/xts.h
@@ -20,24 +20,25 @@ namespace Botan {
 class XTS_Mode : public Cipher_Mode
    {
    public:
-      std::string name() const override;
+      std::string name() const override final;
 
-      size_t update_granularity() const override;
+      size_t update_granularity() const override final;
 
-      size_t ideal_granularity() const override;
+      size_t ideal_granularity() const override final;
 
-      size_t minimum_final_size() const override;
+      size_t minimum_final_size() const override final;
 
-      Key_Length_Specification key_spec() const override;
+      Key_Length_Specification key_spec() const override final;
 
-      size_t default_nonce_length() const override;
+      size_t default_nonce_length() const override final;
 
-      bool valid_nonce_length(size_t n) const override;
+      bool valid_nonce_length(size_t n) const override final;
 
-      void clear() override;
+      void clear() override final;
 
-      void reset() override;
+      void reset() override final;
 
+      bool has_keying_material() const override final;
    protected:
       explicit XTS_Mode(std::unique_ptr<BlockCipher> cipher);
 
diff --git a/src/lib/prov/commoncrypto/commoncrypto_block.cpp b/src/lib/prov/commoncrypto/commoncrypto_block.cpp
index 55ddd276b..d4a640791 100644
--- a/src/lib/prov/commoncrypto/commoncrypto_block.cpp
+++ b/src/lib/prov/commoncrypto/commoncrypto_block.cpp
@@ -32,9 +32,11 @@ class CommonCrypto_BlockCipher final : public BlockCipher
 
       Key_Length_Specification key_spec() const override { return m_opts.key_spec; }
 
+      bool has_keying_material() const override { return m_key_set; }
+
       void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override
          {
-         verify_key_set(m_key_set);
+         assert_key_material_set();
          size_t total_len = blocks * m_opts.block_size;
          size_t out_len = 0;
 
@@ -48,7 +50,7 @@ class CommonCrypto_BlockCipher final : public BlockCipher
 
       void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override
          {
-         verify_key_set(m_key_set);
+         assert_key_material_set();
          size_t total_len = blocks * m_opts.block_size;
          size_t out_len = 0;
 
diff --git a/src/lib/prov/commoncrypto/commoncrypto_mode.cpp b/src/lib/prov/commoncrypto/commoncrypto_mode.cpp
index a0a60bb40..b1b00fd39 100644
--- a/src/lib/prov/commoncrypto/commoncrypto_mode.cpp
+++ b/src/lib/prov/commoncrypto/commoncrypto_mode.cpp
@@ -40,6 +40,7 @@ class CommonCrypto_Cipher_Mode final : public Cipher_Mode
       void clear() override;
       void reset() override;
       Key_Length_Specification key_spec() const override;
+      bool has_keying_material() const override { return m_key_set; }
 
    private:
       void key_schedule(const uint8_t key[], size_t length) override;
@@ -72,7 +73,7 @@ CommonCrypto_Cipher_Mode::~CommonCrypto_Cipher_Mode()
 
 void CommonCrypto_Cipher_Mode::start_msg(const uint8_t nonce[], size_t nonce_len)
    {
-   verify_key_set(m_key_set);
+   assert_key_material_set();
 
    if(!valid_nonce_length(nonce_len))
       { throw Invalid_IV_Length(name(), nonce_len); }
@@ -89,7 +90,7 @@ void CommonCrypto_Cipher_Mode::start_msg(const uint8_t nonce[], size_t nonce_len
 
 size_t CommonCrypto_Cipher_Mode::process(uint8_t msg[], size_t msg_len)
    {
-   verify_key_set(m_key_set);
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_nonce_set);
 
    if(msg_len == 0)
@@ -119,7 +120,7 @@ size_t CommonCrypto_Cipher_Mode::process(uint8_t msg[], size_t msg_len)
 void CommonCrypto_Cipher_Mode::finish(secure_vector<uint8_t>& buffer,
                                       size_t offset)
    {
-   verify_key_set(m_key_set);
+   assert_key_material_set();
    BOTAN_STATE_CHECK(m_nonce_set);
 
    BOTAN_ASSERT(buffer.size() >= offset, "Offset ok");
diff --git a/src/lib/stream/chacha/chacha.cpp b/src/lib/stream/chacha/chacha.cpp
index 03e8d0c61..2443d6b49 100644
--- a/src/lib/stream/chacha/chacha.cpp
+++ b/src/lib/stream/chacha/chacha.cpp
@@ -193,7 +193,7 @@ void ChaCha::chacha(uint8_t output[],
 */
 void ChaCha::cipher(const uint8_t in[], uint8_t out[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    while(length >= m_buffer.size() - m_position)
       {
@@ -216,7 +216,7 @@ void ChaCha::cipher(const uint8_t in[], uint8_t out[], size_t length)
 
 void ChaCha::write_keystream(uint8_t out[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    while(length >= m_buffer.size() - m_position)
       {
@@ -284,6 +284,11 @@ void ChaCha::initialize_state()
    m_position = 0;
    }
 
+bool ChaCha::has_keying_material() const
+   {
+   return m_state.size() > 0;
+   }
+
 /*
 * ChaCha Key Schedule
 */
@@ -322,7 +327,7 @@ bool ChaCha::valid_iv_length(size_t iv_len) const
 
 void ChaCha::set_iv(const uint8_t iv[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    if(!valid_iv_length(length))
       throw Invalid_IV_Length(name(), length);
@@ -390,7 +395,7 @@ std::string ChaCha::name() const
 
 void ChaCha::seek(uint64_t offset)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    // Find the block offset
    const uint64_t counter = offset / 64;
diff --git a/src/lib/stream/chacha/chacha.h b/src/lib/stream/chacha/chacha.h
index 351d4226e..019ecc0ba 100644
--- a/src/lib/stream/chacha/chacha.h
+++ b/src/lib/stream/chacha/chacha.h
@@ -53,6 +53,7 @@ class ChaCha final : public StreamCipher
 
       void seek(uint64_t offset) override;
 
+      bool has_keying_material() const override;
    private:
       void key_schedule(const uint8_t key[], size_t key_len) override;
 
diff --git a/src/lib/stream/ctr/ctr.cpp b/src/lib/stream/ctr/ctr.cpp
index bd79b12b5..71f76debd 100644
--- a/src/lib/stream/ctr/ctr.cpp
+++ b/src/lib/stream/ctr/ctr.cpp
@@ -65,6 +65,11 @@ std::unique_ptr<StreamCipher> CTR_BE::new_object() const
    return std::make_unique<CTR_BE>(m_cipher->new_object(), m_ctr_size);
    }
 
+bool CTR_BE::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 void CTR_BE::key_schedule(const uint8_t key[], size_t key_len)
    {
    m_cipher->set_key(key, key_len);
@@ -84,7 +89,7 @@ std::string CTR_BE::name() const
 
 void CTR_BE::cipher(const uint8_t in[], uint8_t out[], size_t length)
    {
-   verify_key_set(m_iv.empty() == false);
+   assert_key_material_set();
 
    const uint8_t* pad_bits = &m_pad[0];
    const size_t pad_size = m_pad.size();
@@ -124,7 +129,7 @@ void CTR_BE::cipher(const uint8_t in[], uint8_t out[], size_t length)
 
 void CTR_BE::write_keystream(uint8_t out[], size_t length)
    {
-   verify_key_set(m_iv.empty() == false);
+   assert_key_material_set();
 
    const size_t avail = m_pad.size() - m_pad_pos;
    const size_t take = std::min(length, avail);
@@ -228,7 +233,7 @@ void CTR_BE::add_counter(const uint64_t counter)
 
 void CTR_BE::seek(uint64_t offset)
    {
-   verify_key_set(m_iv.empty() == false);
+   assert_key_material_set();
 
    const uint64_t base_counter = m_ctr_blocks * (offset / m_counter.size());
 
diff --git a/src/lib/stream/ctr/ctr.h b/src/lib/stream/ctr/ctr.h
index 9024023d2..1428e4520 100644
--- a/src/lib/stream/ctr/ctr.h
+++ b/src/lib/stream/ctr/ctr.h
@@ -37,6 +37,8 @@ class CTR_BE final : public StreamCipher
 
       void clear() override;
 
+      bool has_keying_material() const override;
+
       /**
       * @param cipher the block cipher to use
       */
diff --git a/src/lib/stream/ofb/ofb.cpp b/src/lib/stream/ofb/ofb.cpp
index 8924b60ee..24b578c81 100644
--- a/src/lib/stream/ofb/ofb.cpp
+++ b/src/lib/stream/ofb/ofb.cpp
@@ -24,6 +24,11 @@ void OFB::clear()
    m_buf_pos = 0;
    }
 
+bool OFB::has_keying_material() const
+   {
+   return m_cipher->has_keying_material();
+   }
+
 void OFB::key_schedule(const uint8_t key[], size_t key_len)
    {
    m_cipher->set_key(key, key_len);
diff --git a/src/lib/stream/ofb/ofb.h b/src/lib/stream/ofb/ofb.h
index dcf42a06a..01f4941e6 100644
--- a/src/lib/stream/ofb/ofb.h
+++ b/src/lib/stream/ofb/ofb.h
@@ -35,6 +35,8 @@ class OFB final : public StreamCipher
 
       void clear() override;
 
+      bool has_keying_material() const override;
+
       /**
       * @param cipher the block cipher to use
       */
diff --git a/src/lib/stream/rc4/rc4.cpp b/src/lib/stream/rc4/rc4.cpp
index 063531193..60fa4bfb3 100644
--- a/src/lib/stream/rc4/rc4.cpp
+++ b/src/lib/stream/rc4/rc4.cpp
@@ -15,7 +15,7 @@ namespace Botan {
 */
 void RC4::cipher(const uint8_t in[], uint8_t out[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    while(length >= m_buffer.size() - m_position)
       {
@@ -73,6 +73,11 @@ void RC4::generate()
    m_position = 0;
    }
 
+bool RC4::has_keying_material() const
+   {
+   return m_state.size() > 0;
+   }
+
 /*
 * RC4 Key Schedule
 */
diff --git a/src/lib/stream/rc4/rc4.h b/src/lib/stream/rc4/rc4.h
index 49d78c710..f16b8f456 100644
--- a/src/lib/stream/rc4/rc4.h
+++ b/src/lib/stream/rc4/rc4.h
@@ -30,6 +30,10 @@ class RC4 final : public StreamCipher
 
       Key_Length_Specification key_spec() const override;
 
+      void seek(uint64_t offset) override;
+
+      bool has_keying_material() const override;
+
       /**
       * @param skip skip this many initial bytes in the keystream
       */
@@ -37,7 +41,6 @@ class RC4 final : public StreamCipher
 
       ~RC4() { clear(); }
 
-      void seek(uint64_t offset) override;
    private:
       void key_schedule(const uint8_t[], size_t) override;
       void generate();
diff --git a/src/lib/stream/salsa20/salsa20.cpp b/src/lib/stream/salsa20/salsa20.cpp
index d1d7b2bb2..f8bec9a6c 100644
--- a/src/lib/stream/salsa20/salsa20.cpp
+++ b/src/lib/stream/salsa20/salsa20.cpp
@@ -110,7 +110,7 @@ void Salsa20::salsa_core(uint8_t output[64], const uint32_t input[16], size_t ro
 */
 void Salsa20::cipher(const uint8_t in[], uint8_t out[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    while(length >= m_buffer.size() - m_position)
       {
@@ -178,6 +178,11 @@ void Salsa20::initialize_state()
    m_position = 0;
    }
 
+bool Salsa20::has_keying_material() const
+   {
+   return m_state.size() > 0;
+   }
+
 /*
 * Salsa20 Key Schedule
 */
@@ -197,7 +202,7 @@ void Salsa20::key_schedule(const uint8_t key[], size_t length)
 */
 void Salsa20::set_iv(const uint8_t iv[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    if(!valid_iv_length(length))
       throw Invalid_IV_Length(name(), length);
@@ -287,7 +292,7 @@ void Salsa20::clear()
 
 void Salsa20::seek(uint64_t offset)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    // Find the block offset
    const uint64_t counter = offset / 64;
diff --git a/src/lib/stream/salsa20/salsa20.h b/src/lib/stream/salsa20/salsa20.h
index f9f30837a..90cd92b10 100644
--- a/src/lib/stream/salsa20/salsa20.h
+++ b/src/lib/stream/salsa20/salsa20.h
@@ -19,23 +19,21 @@ class Salsa20 final : public StreamCipher
    {
    public:
       void cipher(const uint8_t in[], uint8_t out[], size_t length) override;
-
       void set_iv(const uint8_t iv[], size_t iv_len) override;
-
       bool valid_iv_length(size_t iv_len) const override;
-
       size_t default_iv_length() const override;
-
       Key_Length_Specification key_spec() const override;
-
       void clear() override;
       std::string name() const override;
       std::unique_ptr<StreamCipher> new_object() const override;
+      bool has_keying_material() const override;
+      void seek(uint64_t offset) override;
 
+      // For internal use only
       static void salsa_core(uint8_t output[64], const uint32_t input[16], size_t rounds);
-      static void hsalsa20(uint32_t output[8], const uint32_t input[16]);
 
-      void seek(uint64_t offset) override;
+      // For internal use only
+      static void hsalsa20(uint32_t output[8], const uint32_t input[16]);
    private:
       void key_schedule(const uint8_t key[], size_t key_len) override;
 
diff --git a/src/lib/stream/shake_cipher/shake_cipher.cpp b/src/lib/stream/shake_cipher/shake_cipher.cpp
index 35ea77833..9ad388acc 100644
--- a/src/lib/stream/shake_cipher/shake_cipher.cpp
+++ b/src/lib/stream/shake_cipher/shake_cipher.cpp
@@ -44,7 +44,7 @@ void SHAKE_Cipher::seek(uint64_t /*offset*/)
 
 void SHAKE_Cipher::cipher(const uint8_t in[], uint8_t out[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    while(length >= m_shake_rate - m_buf_pos)
       {
@@ -64,7 +64,7 @@ void SHAKE_Cipher::cipher(const uint8_t in[], uint8_t out[], size_t length)
 
 void SHAKE_Cipher::write_keystream(uint8_t out[], size_t length)
    {
-   verify_key_set(m_state.empty() == false);
+   assert_key_material_set();
 
    if(m_buf_pos > 0)
       {
@@ -100,6 +100,11 @@ void SHAKE_Cipher::write_keystream(uint8_t out[], size_t length)
    m_buf_pos += length;
    }
 
+bool SHAKE_Cipher::has_keying_material() const
+   {
+   return !m_state.empty();
+   }
+
 void SHAKE_Cipher::key_schedule(const uint8_t key[], size_t length)
    {
    const size_t SHAKE_BITRATE = m_shake_rate*8;
diff --git a/src/lib/stream/shake_cipher/shake_cipher.h b/src/lib/stream/shake_cipher/shake_cipher.h
index c14a827b2..21fdd601c 100644
--- a/src/lib/stream/shake_cipher/shake_cipher.h
+++ b/src/lib/stream/shake_cipher/shake_cipher.h
@@ -44,6 +44,8 @@ class SHAKE_Cipher : public StreamCipher
 
       Key_Length_Specification key_spec() const override final;
 
+      bool has_keying_material() const override final;
+
    private:
       void key_schedule(const uint8_t key[], size_t key_len) override final;
 
diff --git a/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp b/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp
index de4302258..8dceb79b4 100644
--- a/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp
+++ b/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp
@@ -92,6 +92,11 @@ Key_Length_Specification TLS_CBC_HMAC_AEAD_Mode::key_spec() const
    return Key_Length_Specification(m_cipher_keylen + m_mac_keylen);
    }
 
+bool TLS_CBC_HMAC_AEAD_Mode::has_keying_material() const
+   {
+   return mac().has_keying_material() && cbc().has_keying_material();
+   }
+
 void TLS_CBC_HMAC_AEAD_Mode::key_schedule(const uint8_t key[], size_t keylen)
    {
    // Both keys are of fixed length specified by the ciphersuite
diff --git a/src/lib/tls/tls12/tls_cbc/tls_cbc.h b/src/lib/tls/tls12/tls_cbc/tls_cbc.h
index 76248c8b4..8b500b12a 100644
--- a/src/lib/tls/tls12/tls_cbc/tls_cbc.h
+++ b/src/lib/tls/tls12/tls_cbc/tls_cbc.h
@@ -47,7 +47,8 @@ class BOTAN_TEST_API TLS_CBC_HMAC_AEAD_Mode : public AEAD_Mode
 
       void reset() override final;
 
- protected:
+      bool has_keying_material() const override final;
+   protected:
       TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir direction,
                              std::unique_ptr<BlockCipher> cipher,
                              std::unique_ptr<MessageAuthenticationCode> mac,
diff --git a/src/lib/utils/ghash/ghash.cpp b/src/lib/utils/ghash/ghash.cpp
index 2216646a8..6adae5f4e 100644
--- a/src/lib/utils/ghash/ghash.cpp
+++ b/src/lib/utils/ghash/ghash.cpp
@@ -88,7 +88,7 @@ void GHASH::ghash_multiply(secure_vector<uint8_t>& x,
 void GHASH::ghash_update(secure_vector<uint8_t>& ghash,
                          const uint8_t input[], size_t length)
    {
-   verify_key_set(!m_HM.empty());
+   assert_key_material_set(m_H.size() > 0);
 
    /*
    This assumes if less than block size input then we're just on the
@@ -112,6 +112,11 @@ void GHASH::ghash_update(secure_vector<uint8_t>& ghash,
       }
    }
 
+bool GHASH::has_keying_material() const
+   {
+   return m_ghash.size() > 0;
+   }
+
 void GHASH::key_schedule(const uint8_t key[], size_t length)
    {
    m_H.assign(key, key+length);
@@ -174,14 +179,14 @@ void GHASH::set_associated_data(const uint8_t input[], size_t length)
 
 void GHASH::update_associated_data(const uint8_t ad[], size_t length)
    {
-   verify_key_set(m_ghash.size() == GCM_BS);
+   assert_key_material_set();
    m_ad_len += length;
    ghash_update(m_ghash, ad, length);
    }
 
 void GHASH::update(const uint8_t input[], size_t length)
    {
-   verify_key_set(m_ghash.size() == GCM_BS);
+   assert_key_material_set();
    m_text_len += length;
    ghash_update(m_ghash, input, length);
    }
@@ -202,7 +207,7 @@ void GHASH::final(uint8_t mac[], size_t mac_len)
    {
    BOTAN_ARG_CHECK(mac_len > 0 && mac_len <= 16, "GHASH output length");
 
-   verify_key_set(m_ghash.size() == GCM_BS);
+   assert_key_material_set();
    add_final_block(m_ghash, m_ad_len, m_text_len);
 
    for(size_t i = 0; i != mac_len; ++i)
diff --git a/src/lib/utils/ghash/ghash.h b/src/lib/utils/ghash/ghash.h
index 630c86b21..c8814b45d 100644
--- a/src/lib/utils/ghash/ghash.h
+++ b/src/lib/utils/ghash/ghash.h
@@ -14,8 +14,6 @@ namespace Botan {
 
 /**
 * GCM's GHASH
-* This is not intended for general use, but is exposed to allow
-* shared code between GCM and GMAC
 */
 class GHASH final : public SymmetricAlgorithm
    {
@@ -41,6 +39,8 @@ class GHASH final : public SymmetricAlgorithm
       Key_Length_Specification key_spec() const override
          { return Key_Length_Specification(16); }
 
+      bool has_keying_material() const override;
+
       void clear() override;
 
       void reset();


Test patch:
diff --git a/src/tests/test_aead.cpp b/src/tests/test_aead.cpp
index 688dcaef9..6508a004c 100644
--- a/src/tests/test_aead.cpp
+++ b/src/tests/test_aead.cpp
@@ -57,8 +57,12 @@ class AEAD_Tests final : public Text_Based_Test
                                [&]() { enc->set_associated_data(ad.data(), ad.size()); });
             }
 
+         result.test_eq("key is not set", enc->has_keying_material(), false);
+
          // Ensure that test resets AD and message state
+         result.test_eq("key is not set", enc->has_keying_material(), false);
          enc->set_key(key);
+         result.test_eq("key is set", enc->has_keying_material(), true);
 
          if(is_siv == false)
             {
@@ -180,6 +184,7 @@ class AEAD_Tests final : public Text_Based_Test
             }
 
          enc->clear();
+         result.test_eq("key is not set", enc->has_keying_material(), false);
 
          result.test_throws("Unkeyed object throws for encrypt after clear",
                             [&]() { enc->finish(buf); });
@@ -227,7 +232,9 @@ class AEAD_Tests final : public Text_Based_Test
 
          // First some tests for reset() to make sure it resets what we need it to
          // set garbage values
+         result.test_eq("key is not set", dec->has_keying_material(), false);
          dec->set_key(key);
+         result.test_eq("key is set", dec->has_keying_material(), true);
          dec->set_ad(mutate_vec(ad));
 
          if(is_siv == false)
@@ -416,6 +423,7 @@ class AEAD_Tests final : public Text_Based_Test
             }
 
          dec->clear();
+         result.test_eq("key is not set", dec->has_keying_material(), false);
 
          result.test_throws("Unkeyed object throws for decrypt",
                             [&]() { dec->finish(buf); });
diff --git a/src/tests/test_block.cpp b/src/tests/test_block.cpp
index 8d0254d15..94e35b9f3 100644
--- a/src/tests/test_block.cpp
+++ b/src/tests/test_block.cpp
@@ -62,7 +62,9 @@ class Block_Cipher_Tests final : public Text_Based_Test
             result.test_gte(provider, cipher->block_size(), 8);
             result.test_gte(provider, cipher->parallel_bytes(), cipher->block_size() * cipher->parallelism());
 
-            // Test that trying to encrypt or decrypt with now key set throws Botan::Invalid_State
+            result.test_eq("no key set", cipher->has_keying_material(), false);
+
+            // Test that trying to encrypt or decrypt with no key set throws Botan::Invalid_State
             try
                {
                std::vector<uint8_t> block(cipher->block_size());
@@ -103,6 +105,7 @@ class Block_Cipher_Tests final : public Text_Based_Test
                }
 
             cipher->set_key(key);
+            result.test_eq("key set", cipher->has_keying_material(), true);
 
             if(!tweak.empty())
                {
@@ -165,7 +168,9 @@ class Block_Cipher_Tests final : public Text_Based_Test
                            buf.data() + 1, buf.size() - 1,
                            input.data(), input.size());
 
+            result.test_eq("key set", cipher->has_keying_material(), true);
             cipher->clear();
+            result.test_eq("key set", cipher->has_keying_material(), false);
 
             try
                {
diff --git a/src/tests/test_mac.cpp b/src/tests/test_mac.cpp
index 6b2698cef..6f205096e 100644
--- a/src/tests/test_mac.cpp
+++ b/src/tests/test_mac.cpp
@@ -70,7 +70,9 @@ class Message_Auth_Tests final : public Text_Based_Test
                result.test_success("Trying to MAC with no key set fails");
                }
 
+            result.test_eq("key not set", mac->has_keying_material(), false);
             mac->set_key(key);
+            result.test_eq("key set", mac->has_keying_material(), true);
             mac->start(iv);
             mac->update(input);
             result.test_eq(provider, "correct mac", mac->final(), expected);
@@ -95,6 +97,7 @@ class Message_Auth_Tests final : public Text_Based_Test
             mac->start(iv);
             mac->update("some discarded input");
             mac->clear();
+            result.test_eq("key not set", mac->has_keying_material(), false);
 
             // do the same to test verify_mac()
             mac->set_key(key);
diff --git a/src/tests/test_modes.cpp b/src/tests/test_modes.cpp
index fd476bc2d..a1d8a17cf 100644
--- a/src/tests/test_modes.cpp
+++ b/src/tests/test_modes.cpp
@@ -101,14 +101,14 @@ class Cipher_Mode_Tests final : public Text_Based_Test
 
    private:
       static void test_mode(Test::Result& result,
-                     const std::string& algo,
-                     const std::string& provider,
-                     const std::string& direction,
-                     Botan::Cipher_Mode& mode,
-                     const std::vector<uint8_t>& key,
-                     const std::vector<uint8_t>& nonce,
-                     const std::vector<uint8_t>& input,
-                     const std::vector<uint8_t>& expected)
+                            const std::string& algo,
+                            const std::string& provider,
+                            const std::string& direction,
+                            Botan::Cipher_Mode& mode,
+                            const std::vector<uint8_t>& key,
+                            const std::vector<uint8_t>& nonce,
+                            const std::vector<uint8_t>& input,
+                            const std::vector<uint8_t>& expected)
          {
          const bool is_cbc = (algo.find("/CBC") != std::string::npos);
          const bool is_ctr = (algo.find("CTR") != std::string::npos);
@@ -129,6 +129,8 @@ class Cipher_Mode_Tests final : public Text_Based_Test
          // FFI currently requires this, so assure it is true for all modes
          result.test_gt("buffer sizes ok", mode.ideal_granularity(), min_final_bytes);
 
+         result.test_eq("key not set", mode.has_keying_material(), false);
+
          result.test_throws("Unkeyed object throws", [&]() {
             Botan::secure_vector<uint8_t> bad(update_granularity);
             mode.finish(bad);
@@ -193,6 +195,7 @@ class Cipher_Mode_Tests final : public Text_Based_Test
          mode.reset();
 
          mode.set_key(key);
+         result.test_eq("key is set", mode.has_keying_material(), true);
          mode.start(nonce);
 
          Botan::secure_vector<uint8_t> buf;
@@ -255,6 +258,7 @@ class Cipher_Mode_Tests final : public Text_Based_Test
             }
 
          mode.clear();
+         result.test_eq("key is not set", mode.has_keying_material(), false);
 
          result.test_throws("Unkeyed object throws after clear", [&]() {
             Botan::secure_vector<uint8_t> bad(update_granularity);
diff --git a/src/tests/test_ocb.cpp b/src/tests/test_ocb.cpp
index 70f700ca7..c4eb57303 100644
--- a/src/tests/test_ocb.cpp
+++ b/src/tests/test_ocb.cpp
@@ -35,6 +35,11 @@ class OCB_Wide_Test_Block_Cipher final : public Botan::BlockCipher
          return std::make_unique<OCB_Wide_Test_Block_Cipher>(m_bs);
          }
 
+      bool has_keying_material() const override
+         {
+         return m_key.size() > 0;
+         }
+
       void key_schedule(const uint8_t key[], size_t length) override
          {
          m_key.assign(key, key + length);
diff --git a/src/tests/test_stream.cpp b/src/tests/test_stream.cpp
index 532b6ed18..c89c6fa78 100644
--- a/src/tests/test_stream.cpp
+++ b/src/tests/test_stream.cpp
@@ -119,7 +119,9 @@ class Stream_Cipher_Tests final : public Text_Based_Test
                continue;
                }
 
+            result.test_eq("key not set", cipher->has_keying_material(), false);
             cipher->set_key(key);
+            result.test_eq("key set", cipher->has_keying_material(), true);
 
             /*
             Test invalid nonce sizes. this assumes no implemented cipher supports a nonce of 65000
@@ -206,7 +208,9 @@ class Stream_Cipher_Tests final : public Text_Based_Test
             result.test_eq(provider, "write_keystream", buf, expected);
             }
 
+            result.test_eq("key set", cipher->has_keying_material(), true);
             cipher->clear();
+            result.test_eq("key not set", cipher->has_keying_material(), false);
 
             try
                {
diff --git a/src/tests/test_tls.cpp b/src/tests/test_tls.cpp
index 18aa0f024..11062d377 100644
--- a/src/tests/test_tls.cpp
+++ b/src/tests/test_tls.cpp
@@ -155,6 +155,8 @@ class TLS_CBC_Tests final : public Text_Based_Test
                   out[i] = 0;
                }
 
+            bool has_keying_material() const override { return true; }
+
             Botan::Key_Length_Specification key_spec() const override
                {
                return Botan::Key_Length_Specification(0, 0, 1);
@@ -190,6 +192,8 @@ class TLS_CBC_Tests final : public Text_Based_Test
             void clear() override { }
             std::string name() const override { return "noop"; }
 
+            bool has_keying_material() const override { return true; }
+
             Botan::Key_Length_Specification key_spec() const override
                {
                return Botan::Key_Length_Specification(0, 0, 1);