diff --git a/include/psa/crypto_builtin_composites.h b/include/psa/crypto_builtin_composites.h
index fd7f6f91a9..2fb0633ab4 100644
--- a/include/psa/crypto_builtin_composites.h
+++ b/include/psa/crypto_builtin_composites.h
@@ -53,6 +53,8 @@ typedef struct
         /** The HMAC part of the context. */
         uint8_t opad[PSA_HMAC_MAX_HASH_BLOCK_SIZE];
 } psa_hmac_internal_data;
+
+#define MBEDTLS_PSA_HMAC_OPERATION_INIT {0, {0}, {0}}
 #endif /* PSA_WANT_ALG_HMAC */
 
 #include "mbedtls/cmac.h"
diff --git a/library/psa_crypto.c b/library/psa_crypto.c
index 3ca8c9d8f6..0ef73dff7a 100644
--- a/library/psa_crypto.c
+++ b/library/psa_crypto.c
@@ -3331,19 +3331,19 @@ static psa_status_t psa_key_derivation_hkdf_read( psa_hkdf_key_derivation_t *hkd
             return( status );
         if( hkdf->block_number != 1 )
         {
-            status = psa_hash_update( &hkdf->hmac.hash_ctx,
-                                      hkdf->output_block,
-                                      hash_length );
+            status = psa_hmac_update_internal( &hkdf->hmac,
+                                               hkdf->output_block,
+                                               hash_length );
             if( status != PSA_SUCCESS )
                 return( status );
         }
-        status = psa_hash_update( &hkdf->hmac.hash_ctx,
-                                  hkdf->info,
-                                  hkdf->info_length );
+        status = psa_hmac_update_internal( &hkdf->hmac,
+                                           hkdf->info,
+                                           hkdf->info_length );
         if( status != PSA_SUCCESS )
             return( status );
-        status = psa_hash_update( &hkdf->hmac.hash_ctx,
-                                  &hkdf->block_number, 1 );
+        status = psa_hmac_update_internal( &hkdf->hmac,
+                                           &hkdf->block_number, 1 );
         if( status != PSA_SUCCESS )
             return( status );
         status = psa_hmac_finish_internal( &hkdf->hmac,
@@ -3365,7 +3365,7 @@ static psa_status_t psa_key_derivation_tls12_prf_generate_next_block(
 {
     psa_algorithm_t hash_alg = PSA_ALG_HKDF_GET_HASH( alg );
     uint8_t hash_length = PSA_HASH_LENGTH( hash_alg );
-    psa_hash_operation_t backup = PSA_HASH_OPERATION_INIT;
+    psa_hmac_internal_data backup = MBEDTLS_PSA_HMAC_OPERATION_INIT;
     psa_status_t status, cleanup_status;
 
     /* We can't be wanting more output after block 0xff, otherwise
@@ -3400,7 +3400,7 @@ static psa_status_t psa_key_derivation_tls12_prf_generate_next_block(
     /* Save the hash context before using it, to preserve the hash state with
      * only the inner padding in it. We need this, because inner padding depends
      * on the key (secret in the RFC's terminology). */
-    status = psa_hash_clone( &tls12_prf->hmac.hash_ctx, &backup );
+    status = psa_hmac_clone_internal( &tls12_prf->hmac, &backup );
     if( status != PSA_SUCCESS )
         goto cleanup;
 
@@ -3410,20 +3410,22 @@ static psa_status_t psa_key_derivation_tls12_prf_generate_next_block(
         /* A(1) = HMAC_hash(secret, A(0)), where A(0) = seed. (The RFC overloads
          * the variable seed and in this instance means it in the context of the
          * P_hash function, where seed = label + seed.) */
-        status = psa_hash_update( &tls12_prf->hmac.hash_ctx,
-                                  tls12_prf->label, tls12_prf->label_length );
+        status = psa_hmac_update_internal( &tls12_prf->hmac,
+                                           tls12_prf->label,
+                                           tls12_prf->label_length );
         if( status != PSA_SUCCESS )
             goto cleanup;
-        status = psa_hash_update( &tls12_prf->hmac.hash_ctx,
-                                  tls12_prf->seed, tls12_prf->seed_length );
+        status = psa_hmac_update_internal( &tls12_prf->hmac,
+                                           tls12_prf->seed,
+                                           tls12_prf->seed_length );
         if( status != PSA_SUCCESS )
             goto cleanup;
     }
     else
     {
         /* A(i) = HMAC_hash(secret, A(i-1)) */
-        status = psa_hash_update( &tls12_prf->hmac.hash_ctx,
-                                  tls12_prf->Ai, hash_length );
+        status = psa_hmac_update_internal( &tls12_prf->hmac,
+                                           tls12_prf->Ai, hash_length );
         if( status != PSA_SUCCESS )
             goto cleanup;
     }
@@ -3432,35 +3434,35 @@ static psa_status_t psa_key_derivation_tls12_prf_generate_next_block(
                                        tls12_prf->Ai, hash_length );
     if( status != PSA_SUCCESS )
         goto cleanup;
-    status = psa_hash_clone( &backup, &tls12_prf->hmac.hash_ctx );
+    status = psa_hmac_clone_internal( &backup, &tls12_prf->hmac );
     if( status != PSA_SUCCESS )
         goto cleanup;
 
     /* Calculate HMAC_hash(secret, A(i) + label + seed). */
-    status = psa_hash_update( &tls12_prf->hmac.hash_ctx,
-                              tls12_prf->Ai, hash_length );
+    status = psa_hmac_update_internal( &tls12_prf->hmac,
+                                       tls12_prf->Ai, hash_length );
     if( status != PSA_SUCCESS )
         goto cleanup;
-    status = psa_hash_update( &tls12_prf->hmac.hash_ctx,
-                              tls12_prf->label, tls12_prf->label_length );
+    status = psa_hmac_update_internal( &tls12_prf->hmac,
+                                       tls12_prf->label, tls12_prf->label_length );
     if( status != PSA_SUCCESS )
         goto cleanup;
-    status = psa_hash_update( &tls12_prf->hmac.hash_ctx,
-                              tls12_prf->seed, tls12_prf->seed_length );
+    status = psa_hmac_update_internal( &tls12_prf->hmac,
+                                       tls12_prf->seed, tls12_prf->seed_length );
     if( status != PSA_SUCCESS )
         goto cleanup;
     status = psa_hmac_finish_internal( &tls12_prf->hmac,
                                        tls12_prf->output_block, hash_length );
     if( status != PSA_SUCCESS )
         goto cleanup;
-    status = psa_hash_clone( &backup, &tls12_prf->hmac.hash_ctx );
+    status = psa_hmac_clone_internal( &backup, &tls12_prf->hmac );
     if( status != PSA_SUCCESS )
         goto cleanup;
 
 
 cleanup:
 
-    cleanup_status = psa_hash_abort( &backup );
+    cleanup_status = psa_hmac_abort_internal( &backup );
     if( status == PSA_SUCCESS && cleanup_status != PSA_SUCCESS )
         status = cleanup_status;
 
@@ -3806,8 +3808,8 @@ static psa_status_t psa_hkdf_input( psa_hkdf_key_derivation_t *hkdf,
             }
             if( hkdf->state != HKDF_STATE_STARTED )
                 return( PSA_ERROR_BAD_STATE );
-            status = psa_hash_update( &hkdf->hmac.hash_ctx,
-                                      data, data_length );
+            status = psa_hmac_update_internal( &hkdf->hmac,
+                                               data, data_length );
             if( status != PSA_SUCCESS )
                 return( status );
             status = psa_hmac_finish_internal( &hkdf->hmac,
diff --git a/library/psa_crypto_mac.c b/library/psa_crypto_mac.c
index 72386d873e..5a7bc2c86a 100644
--- a/library/psa_crypto_mac.c
+++ b/library/psa_crypto_mac.c
@@ -139,6 +139,13 @@ cleanup:
     return( status );
 }
 
+psa_status_t psa_hmac_update_internal( psa_hmac_internal_data *hmac,
+                                       const uint8_t *data,
+                                       size_t data_length )
+{
+    return( psa_hash_update( &hmac->hash_ctx, data, data_length ) );
+}
+
 psa_status_t psa_hmac_finish_internal( psa_hmac_internal_data *hmac,
                                        uint8_t *mac,
                                        size_t mac_size )
@@ -176,6 +183,22 @@ exit:
     mbedtls_platform_zeroize( tmp, hash_size );
     return( status );
 }
+
+psa_status_t psa_hmac_clone_internal( const psa_hmac_internal_data *source,
+                                      psa_hmac_internal_data *destination )
+{
+    psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
+
+    destination->alg = source->alg;
+    destination->hash_ctx = psa_hash_operation_init();
+    status = psa_hash_clone( &source->hash_ctx, &destination->hash_ctx );
+    memcpy( destination->opad, source->opad, sizeof( destination->opad ) );
+
+    if( status != PSA_SUCCESS )
+        memset( destination, 0, sizeof( *destination ) );
+
+    return( status );
+}
 #endif /* MBEDTLS_PSA_BUILTIN_ALG_HMAC || PSA_CRYPTO_DRIVER_TEST */
 
 /* Implement the PSA driver MAC interface on top of mbed TLS if either the
diff --git a/library/psa_crypto_mac.h b/library/psa_crypto_mac.h
index d923511605..6a5629618d 100644
--- a/library/psa_crypto_mac.h
+++ b/library/psa_crypto_mac.h
@@ -23,15 +23,93 @@
 
 #include <psa/crypto.h>
 
+/** Internal API for starting an HMAC operation, using PSA hash primitives.
+ *
+ * \note This API is not meant for application use. Applications should always
+ *       use the top-level psa_mac_xxx APIs for doing HMAC operations.
+ *
+ * \param[in] hmac      Context structure for this HMAC operation. Needs to have
+ *                      been zero-initialized prior to calling this function.
+ * \param[in] key       Key to initialize the HMAC operation with.
+ * \param key_length    Length (in bytes) of key \p key.
+ * \param hash_alg      Hash algorithm to use for calculating the HMAC.
+ *
+ * \retval #PSA_SUCCESS
+ *         Success.
+ * \return Any error code reported by psa_hash_compute(), psa_hash_setup() or
+ *         psa_hash_update().
+ */
 psa_status_t psa_hmac_setup_internal( psa_hmac_internal_data *hmac,
                                       const uint8_t *key,
                                       size_t key_length,
                                       psa_algorithm_t hash_alg );
 
+/** Internal API for adding data to an HMAC operation, using PSA hash primitives.
+ *
+ * \note This API is not meant for application use. Applications should always
+ *       use the top-level psa_mac_xxx APIs for doing HMAC operations.
+ *
+ * \param[in] hmac      Context structure for this HMAC operation. Needs to have
+ *                      been initialized with psa_hmac_setup_internal().
+ * \param[in] data      Buffer containing the data to add to the current HMAC
+ *                      calculation.
+ * \param data_length   Length (in bytes) of the input buffer \p data.
+ *
+ * \retval #PSA_SUCCESS
+ *         Success.
+ * \return Any error code reported by psa_hash_update().
+ */
+psa_status_t psa_hmac_update_internal( psa_hmac_internal_data *hmac,
+                                       const uint8_t *data,
+                                       size_t data_length );
+
+/** Internal API for finalizing an HMAC operation, using PSA hash primitives.
+ *
+ * \note This API is not meant for application use. Applications should always
+ *       use the top-level psa_mac_xxx APIs for doing HMAC operations.
+ *
+ * \param[in] hmac      Context structure for this HMAC operation. Needs to have
+ *                      been initialized with psa_hmac_setup_internal().
+ * \param[out] mac      Buffer to output the calculated HMAC into.
+ * \param mac_size      Size (in bytes) of the output buffer \p mac.
+ *
+ * \retval #PSA_SUCCESS
+ *         Success.
+ * \return Any error code reported by psa_hash_setup(), psa_hash_update() or
+ *         psa_hash_finish().
+ */
 psa_status_t psa_hmac_finish_internal( psa_hmac_internal_data *hmac,
                                        uint8_t *mac,
                                        size_t mac_size );
 
+/** Internal API for cloning an HMAC operation, using PSA hash primitives.
+ *
+ * \note This API is not meant for application use. Applications should always
+ *       use the top-level psa_mac_xxx APIs for doing HMAC operations.
+ *
+ * \param[in] source        Context structure to clone from. Needs to have been
+ *                          initialized with psa_hmac_setup_internal().
+ * \param[out] destination  Context structure to clone to. Needs to have been
+ *                          zero-initialized.
+ *
+ * \retval #PSA_SUCCESS
+ *         Success.
+ * \return Any error code reported by psa_hash_clone().
+ */
+psa_status_t psa_hmac_clone_internal( const psa_hmac_internal_data *source,
+                                      psa_hmac_internal_data *destination );
+
+/** Internal API for aborting an HMAC operation, using PSA hash primitives.
+ *
+ * \note This API is not meant for application use. Applications should always
+ *       use the top-level psa_mac_xxx APIs for doing HMAC operations.
+ *
+ * \param[in] hmac      Context structure for the HMAC operation to abort.
+ *
+ * \retval #PSA_SUCCESS
+ *         Success.
+ * \return Any error code reported by psa_hash_abort().
+ */
 psa_status_t psa_hmac_abort_internal( psa_hmac_internal_data *hmac );
 
 /** Calculate the MAC (message authentication code) of a message using Mbed TLS.