]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-134531: use `EVP_MAC` API for `_hashlib.HMAC` when possible (#135235)
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>
Wed, 10 Sep 2025 08:35:01 +0000 (10:35 +0200)
committerGitHub <noreply@github.com>
Wed, 10 Sep 2025 08:35:01 +0000 (10:35 +0200)
Use `EVP_MAC` API for HMAC on builds with OpenSSL 3.0 or later.

Misc/NEWS.d/next/Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst [new file with mode: 0644]
Modules/_hashopenssl.c

diff --git a/Misc/NEWS.d/next/Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst b/Misc/NEWS.d/next/Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst
new file mode 100644 (file)
index 0000000..032cf12
--- /dev/null
@@ -0,0 +1,3 @@
+:mod:`hmac`: use the :manpage:`EVP_MAC(3ssl)` interface for HMAC when Python
+is built with OpenSSL 3.0 and later instead of the deprecated
+:manpage:`HMAC_CTX(3ssl) <hmac(3)>` interface. Patch by Bénédikt Tran.
index d34dfad33ff6668843724ccbc1d02def8e006dba..442804353756c71a5e9378b67965a28f531ddca0 100644 (file)
@@ -30,7 +30,6 @@
 
 /* EVP is the preferred interface to hashing in OpenSSL */
 #include <openssl/evp.h>
-#include <openssl/hmac.h>
 #include <openssl/crypto.h>              // FIPS_mode()
 /* We use the object interface to discover what hashes OpenSSL supports. */
 #include <openssl/objects.h>
 
 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
 #  define Py_HAS_OPENSSL3_SUPPORT
+#  include <openssl/core_names.h>           // OSSL_MAC_PARAM_DIGEST
+#  include <openssl/params.h>               // OSSL_PARAM_*()
+#else
+#  include <openssl/hmac.h>                 // HMAC()
 #endif
 
 #ifndef OPENSSL_THREADS
 #define PY_EVP_MD_free(md) EVP_MD_free(md)
 
 #define PY_EVP_MD_CTX_md(CTX)   EVP_MD_CTX_get0_md(CTX)
+
+#define PY_HMAC_CTX_TYPE    EVP_MAC_CTX
+#define PY_HMAC_CTX_free    EVP_MAC_CTX_free
+#define PY_HMAC_update      EVP_MAC_update
 #else
 #define PY_EVP_MD const EVP_MD
 #define PY_EVP_MD_fetch(algorithm, properties) EVP_get_digestbyname(algorithm)
 #define PY_EVP_MD_free(md) do {} while(0)
 
 #define PY_EVP_MD_CTX_md(CTX)   EVP_MD_CTX_md(CTX)
+
+#define PY_HMAC_CTX_TYPE    HMAC_CTX
+#define PY_HMAC_CTX_free    HMAC_CTX_free
+#define PY_HMAC_update      HMAC_Update
 #endif
 
 /*
@@ -283,6 +294,9 @@ typedef struct {
     PyObject *constructs;
     PyObject *unsupported_digestmod_error;
     _Py_hashtable_t *hashtable;
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    EVP_MAC *evp_hmac;
+#endif
 } _hashlibstate;
 
 static inline _hashlibstate*
@@ -304,7 +318,12 @@ typedef struct {
 
 typedef struct {
     HASHLIB_OBJECT_HEAD
-    HMAC_CTX *ctx;            /* OpenSSL hmac context */
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    EVP_MAC_CTX *ctx;   /* OpenSSL HMAC EVP-based context */
+    int evp_md_nid;     /* needed to find the message digest name */
+#else
+    HMAC_CTX *ctx;      /* OpenSSL HMAC plain context */
+#endif
 } HMACobject;
 
 #define HMACobject_CAST(op) ((HMACobject *)(op))
@@ -617,6 +636,20 @@ get_hashlib_utf8name_by_nid(int nid)
     return e ? e->py_name : get_asn1_utf8name_by_nid(nid);
 }
 
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+/*
+ * Convert the NID to an OpenSSL "canonical" cached, SN_* or LN_* digest name.
+ *
+ * On error, set an exception and return NULL.
+ */
+static const char *
+get_openssl_utf8name_by_nid(int nid)
+{
+    const py_hashentry_t *e = get_hashentry_by_nid(nid);
+    return e ? e->ossl_name : get_asn1_utf8name_by_nid(nid);
+}
+#endif
+
 /* Same as get_hashlib_utf8name_by_nid() but using an EVP_MD object. */
 static const char *
 get_hashlib_utf8name_by_evp_md(const EVP_MD *md)
@@ -733,6 +766,47 @@ get_openssl_evp_md(_hashlibstate *state, PyObject *digestmod, Py_hash_type py_ht
     return get_openssl_evp_md_by_utf8name(state, name, py_ht);
 }
 
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+/*
+ * Get the "canonical" name of an EVP_MD described by 'digestmod' and purpose.
+ *
+ * On error, set an exception and return NULL.
+ *
+ * This function should not be used to construct the exposed Python name,
+ * but rather to invoke OpenSSL EVP_* functions.
+ */
+static const char *
+get_openssl_digest_name(_hashlibstate *state,
+                        PyObject *digestmod, Py_hash_type py_ht,
+                        EVP_MD **evp_md)
+{
+    PY_EVP_MD *md = get_openssl_evp_md(state, digestmod, py_ht);
+    if (md == NULL) {
+        if (evp_md != NULL) {
+            *evp_md = NULL;
+        }
+        return NULL;
+    }
+    int nid = EVP_MD_nid(md);
+    const char *name = get_openssl_utf8name_by_nid(nid);
+    if (name == NULL) {
+        if (evp_md != NULL) {
+            *evp_md = NULL;
+        }
+        PY_EVP_MD_free(md);
+        raise_unsupported_algorithm_error(state, digestmod);
+        return NULL;
+    }
+    if (evp_md != NULL) {
+        *evp_md = md;
+    }
+    else {
+        PY_EVP_MD_free(md);
+    }
+    return name;
+}
+#endif
+
 // --- OpenSSL HASH wrappers --------------------------------------------------
 
 /* Thin wrapper around EVP_MD_CTX_new() which sets an exception on failure. */
@@ -1742,9 +1816,27 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
 #undef HASHLIB_SCRYPT_MAX_DKLEN
 #undef HASHLIB_SCRYPT_MAX_MAXMEM
 
-/* Fast HMAC for hmac.digest()
+// --- OpenSSL HMAC interface -------------------------------------------------
+
+/*
+ * Functions prefixed by hashlib_openssl_HMAC_* are wrappers around OpenSSL
+ * and implement "atomic" operations (e.g., "free"). These functions are used
+ * by those prefixed by _hashlib_HMAC_* that are methods for HMAC objects, or
+ * other (local) helper functions prefixed by hashlib_HMAC_*.
  */
 
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+/* EVP_MAC_CTX array of parameters specifying the "digest" */
+#define HASHLIB_HMAC_OSSL_PARAMS(DIGEST)                        \
+    (const OSSL_PARAM []) {                                     \
+        OSSL_PARAM_utf8_string(OSSL_MAC_PARAM_DIGEST,           \
+                               (char *)DIGEST, strlen(DIGEST)), \
+        OSSL_PARAM_END                                          \
+    }
+#endif
+
+// --- One-shot HMAC interface ------------------------------------------------
+
 /*[clinic input]
 _hashlib.hmac_digest as _hashlib_hmac_singleshot
 
@@ -1762,9 +1854,14 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
 {
     _hashlibstate *state = get_hashlib_state(module);
     unsigned char md[EVP_MAX_MD_SIZE] = {0};
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    size_t md_len = 0;
+    const char *digest_name = NULL;
+#else
     unsigned int md_len = 0;
-    unsigned char *result;
-    PY_EVP_MD *evp;
+#endif
+    unsigned char *result = NULL;
+    PY_EVP_MD *evp = NULL;
     int is_xof;
 
     if (key->len > INT_MAX) {
@@ -1778,13 +1875,34 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
         return NULL;
     }
 
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    digest_name = get_openssl_digest_name(state, digest, Py_ht_mac, &evp);
+    if (digest_name == NULL) {
+        assert(evp == NULL);
+        return NULL;
+    }
+    assert(evp != NULL);
+    is_xof = PY_EVP_MD_xof(evp);
+
+    Py_BEGIN_ALLOW_THREADS
+    result = EVP_Q_mac(
+        NULL, OSSL_MAC_NAME_HMAC, NULL, NULL,
+        HASHLIB_HMAC_OSSL_PARAMS(digest_name),
+        (const void *)key->buf, (size_t)key->len,
+        (const unsigned char *)msg->buf, (size_t)msg->len,
+        md, sizeof(md), &md_len
+    );
+    Py_END_ALLOW_THREADS
+    PY_EVP_MD_free(evp);
+    assert(md_len < (size_t)PY_SSIZE_T_MAX);
+#else
     evp = get_openssl_evp_md(state, digest, Py_ht_mac);
     if (evp == NULL) {
         return NULL;
     }
+    is_xof = PY_EVP_MD_xof(evp);
 
     Py_BEGIN_ALLOW_THREADS
-    is_xof = PY_EVP_MD_xof(evp);
     result = HMAC(
         evp,
         (const void *)key->buf, (int)key->len,
@@ -1793,23 +1911,27 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
     );
     Py_END_ALLOW_THREADS
     PY_EVP_MD_free(evp);
-
+#endif
     if (result == NULL) {
         if (is_xof) {
             /* use a better default error message if an XOF is used */
             raise_unsupported_algorithm_error(state, digest);
         }
         else {
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+            notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_Q_mac));
+#else
             notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC));
+#endif
         }
         return NULL;
     }
     return PyBytes_FromStringAndSize((const char*)md, md_len);
 }
 
-/* OpenSSL-based HMAC implementation
- */
+// --- HMAC Object ------------------------------------------------------------
 
+#ifndef Py_HAS_OPENSSL3_SUPPORT
 /* Thin wrapper around HMAC_CTX_new() which sets an exception on failure. */
 static HMAC_CTX *
 py_openssl_wrapper_HMAC_CTX_new(void)
@@ -1821,18 +1943,179 @@ py_openssl_wrapper_HMAC_CTX_new(void)
     }
     return ctx;
 }
+#endif
 
 static int _hmac_update(HMACobject*, PyObject*);
 
+#ifndef Py_HAS_OPENSSL3_SUPPORT
 static const EVP_MD *
 _hashlib_hmac_get_md(HMACobject *self)
 {
+    assert(self->ctx != NULL);
     const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
     if (md == NULL) {
         notify_ssl_error_occurred("missing EVP_MD for HMAC context");
     }
     return md;
 }
+#endif
+
+static const char *
+hashlib_HMAC_get_hashlib_digest_name(HMACobject *self)
+{
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    return get_hashlib_utf8name_by_nid(self->evp_md_nid);
+#else
+    const EVP_MD *md = _hashlib_hmac_get_md(self);
+    return md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md);
+#endif
+}
+
+static int
+hashlib_openssl_HMAC_update_once(PY_HMAC_CTX_TYPE *ctx, const Py_buffer *v)
+{
+    if (!PY_HMAC_update(ctx, (const unsigned char *)v->buf, (size_t)v->len)) {
+        notify_smart_ssl_error_occurred_in(Py_STRINGIFY(PY_HMAC_update));
+        return -1;
+    }
+    return 0;
+}
+
+/* Thin wrapper around PY_HMAC_CTX_free that allows to pass a NULL 'ctx'. */
+static inline void
+hashlib_openssl_HMAC_CTX_free(PY_HMAC_CTX_TYPE *ctx)
+{
+    /* The NULL check was not present in every OpenSSL versions. */
+    if (ctx) {
+        PY_HMAC_CTX_free(ctx);
+    }
+}
+
+static PY_HMAC_CTX_TYPE *
+hashlib_openssl_HMAC_ctx_copy_with_lock(HMACobject *self)
+{
+    PY_HMAC_CTX_TYPE *ctx = NULL;
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    HASHLIB_ACQUIRE_LOCK(self);
+    ctx = EVP_MAC_CTX_dup(self->ctx);
+    HASHLIB_RELEASE_LOCK(self);
+    if (ctx == NULL) {
+        notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_CTX_dup));
+        goto error;
+    }
+#else
+    int r;
+    ctx = py_openssl_wrapper_HMAC_CTX_new();
+    if (ctx == NULL) {
+        return NULL;
+    }
+    HASHLIB_ACQUIRE_LOCK(self);
+    r = HMAC_CTX_copy(ctx, self->ctx);
+    HASHLIB_RELEASE_LOCK(self);
+    if (r == 0) {
+        notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy));
+        goto error;
+    }
+#endif
+    return ctx;
+
+error:
+    hashlib_openssl_HMAC_CTX_free(ctx);
+    return NULL;
+}
+
+static PY_HMAC_CTX_TYPE *
+hashlib_HMAC_CTX_new_from_digestmod(_hashlibstate *state,
+                                    Py_buffer *key, PyObject *digestmod,
+                                    int *nid)
+{
+    PY_HMAC_CTX_TYPE *ctx = NULL;
+    PY_EVP_MD *md = NULL;
+    int is_xof, r;
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    const char *digest = NULL;
+#endif
+
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    /*
+     * OpenSSL 3.0 does not provide a way to extract the NID from an EVP_MAC
+     * object and does not expose the underlying digest name. The reason is
+     * that OpenSSL 3.0 treats HMAC objects as being the "same", differing
+     * only by their *context* parameters. While it is *required* to set
+     * the digest name when constructing EVP_MAC_CTX objects, that name
+     * is unfortunately not recoverable through EVP_MAC_CTX_get_params().
+     *
+     * On the other hand, the (deprecated) interface based on HMAC_CTX is
+     * based on EVP_MD, which allows to treat HMAC objects as if they were
+     * hash functions when querying the digest name.
+     *
+     * Since HMAC objects are constructed from DIGESTMOD values and since
+     * we have a way to map DIGESTMOD to EVP_MD objects, and then to NIDs,
+     * HMAC objects based on EVP_MAC will store the NID of the EVP_MD we
+     * used to deduce the digest name to pass to EVP_MAC_CTX_set_params().
+     */
+    assert(nid != NULL);
+    digest = get_openssl_digest_name(state, digestmod, Py_ht_mac, &md);
+    assert((digest == NULL && md == NULL) || (digest != NULL && md != NULL));
+    if (digest == NULL) {
+        *nid = NID_undef;
+        return NULL;
+    }
+    *nid = EVP_MD_nid(md);
+    is_xof = PY_EVP_MD_xof(md);
+    PY_EVP_MD_free(md);
+
+    /*
+     * OpenSSL is responsible for managing the EVP_MAC object's ref. count
+     * by calling EVP_MAC_up_ref() and EVP_MAC_free() in EVP_MAC_CTX_new()
+     * and EVP_MAC_CTX_free() respectively.
+     */
+    ctx = EVP_MAC_CTX_new(state->evp_hmac);
+    if (ctx == NULL) {
+        /* EVP_MAC_CTX_new() may also set an ERR_R_EVP_LIB error */
+        notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_CTX_new));
+        return NULL;
+    }
+
+    r = EVP_MAC_init(
+        ctx,
+        (const unsigned char *)key->buf,
+        (size_t)key->len,
+        HASHLIB_HMAC_OSSL_PARAMS(digest)
+    );
+#else
+    assert(nid == NULL);
+    md = get_openssl_evp_md(state, digestmod, Py_ht_mac);
+    if (md == NULL) {
+        return NULL;
+    }
+    is_xof = PY_EVP_MD_xof(md);
+
+    ctx = py_openssl_wrapper_HMAC_CTX_new();
+    if (ctx == NULL) {
+        PY_EVP_MD_free(md);
+        return NULL;
+    }
+
+    r = HMAC_Init_ex(ctx, key->buf, (int)key->len, md, NULL /* impl */);
+    PY_EVP_MD_free(md);
+#endif
+    if (r == 0) {
+        if (is_xof) {
+            /* use a better default error message if an XOF is used */
+            raise_unsupported_algorithm_error(state, digestmod);
+        }
+        else {
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+            notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_init));
+#else
+            notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex));
+#endif
+        }
+        return NULL;
+    }
+    return ctx;
+}
 
 /*[clinic input]
 _hashlib.hmac_new
@@ -1850,10 +2133,11 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
 /*[clinic end generated code: output=c20d9e4d9ed6d219 input=5f4071dcc7f34362]*/
 {
     _hashlibstate *state = get_hashlib_state(module);
-    PY_EVP_MD *digest;
-    HMAC_CTX *ctx = NULL;
+    PY_HMAC_CTX_TYPE *ctx = NULL;
     HMACobject *self = NULL;
-    int is_xof, r;
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    int nid;
+#endif
 
     if (key->len > INT_MAX) {
         PyErr_SetString(PyExc_OverflowError,
@@ -1867,29 +2151,15 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
         return NULL;
     }
 
-    digest = get_openssl_evp_md(state, digestmod, Py_ht_mac);
-    if (digest == NULL) {
-        return NULL;
-    }
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    ctx = hashlib_HMAC_CTX_new_from_digestmod(state, key, digestmod, &nid);
+#else
+    ctx = hashlib_HMAC_CTX_new_from_digestmod(state, key, digestmod, NULL);
+#endif
 
-    ctx = py_openssl_wrapper_HMAC_CTX_new();
     if (ctx == NULL) {
-        PY_EVP_MD_free(digest);
-        goto error;
-    }
-
-    is_xof = PY_EVP_MD_xof(digest);
-    r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */);
-    PY_EVP_MD_free(digest);
-    if (r == 0) {
-        if (is_xof) {
-            /* use a better default error message if an XOF is used */
-            raise_unsupported_algorithm_error(state, digestmod);
-        }
-        else {
-            notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex));
-        }
-        goto error;
+        assert(PyErr_Occurred());
+        return NULL;
     }
 
     self = PyObject_New(HMACobject, state->HMAC_type);
@@ -1899,36 +2169,27 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
 
     self->ctx = ctx;
     ctx = NULL;  // 'ctx' is now owned by 'self'
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    assert(nid != NID_undef);
+    self->evp_md_nid = nid;
+#endif
     HASHLIB_INIT_MUTEX(self);
 
+    /* feed initial data */
     if ((msg_obj != NULL) && (msg_obj != Py_None)) {
-        if (!_hmac_update(self, msg_obj)) {
+        if (_hmac_update(self, msg_obj) < 0) {
             goto error;
         }
     }
     return (PyObject *)self;
 
 error:
-    if (ctx) HMAC_CTX_free(ctx);
+    hashlib_openssl_HMAC_CTX_free(ctx);
     Py_XDECREF(self);
     return NULL;
 }
 
 /* helper functions */
-static int
-locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
-{
-    int result;
-    HASHLIB_ACQUIRE_LOCK(self);
-    result = HMAC_CTX_copy(new_ctx_p, self->ctx);
-    HASHLIB_RELEASE_LOCK(self);
-    if (result == 0) {
-        notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy));
-        return -1;
-    }
-    return 0;
-}
-
 #define BAD_DIGEST_SIZE 0
 
 /*
@@ -1939,6 +2200,12 @@ locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
 static unsigned int
 _hashlib_hmac_digest_size(HMACobject *self)
 {
+    assert(EVP_MAX_MD_SIZE < INT_MAX);
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    assert(self->ctx != NULL);
+    size_t digest_size = EVP_MAC_CTX_get_mac_size(self->ctx);
+    assert(digest_size <= (size_t)EVP_MAX_MD_SIZE);
+#else
     const EVP_MD *md = _hashlib_hmac_get_md(self);
     if (md == NULL) {
         return BAD_DIGEST_SIZE;
@@ -1947,6 +2214,7 @@ _hashlib_hmac_digest_size(HMACobject *self)
     /* digest_size < 0 iff EVP_MD context is NULL (which is impossible here) */
     assert(digest_size >= 0);
     assert(digest_size <= (int)EVP_MAX_MD_SIZE);
+#endif
     /* digest_size == 0 means that the context is not entirely initialized */
     if (digest_size == 0) {
         raise_ssl_error(PyExc_ValueError, "missing digest size");
@@ -1960,21 +2228,13 @@ _hmac_update(HMACobject *self, PyObject *obj)
 {
     int r;
     Py_buffer view = {0};
-
-    GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0);
+    GET_BUFFER_VIEW_OR_ERROR(obj, &view, return -1);
     HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
         self, view.len,
-        r = HMAC_Update(
-            self->ctx, (const unsigned char *)view.buf, (size_t)view.len
-        )
+        r = hashlib_openssl_HMAC_update_once(self->ctx, &view)
     );
     PyBuffer_Release(&view);
-
-    if (r == 0) {
-        notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Update));
-        return 0;
-    }
-    return 1;
+    return r;
 }
 
 /*[clinic input]
@@ -1988,24 +2248,17 @@ _hashlib_HMAC_copy_impl(HMACobject *self)
 /*[clinic end generated code: output=29aa28b452833127 input=e2fa6a05db61a4d6]*/
 {
     HMACobject *retval;
-
-    HMAC_CTX *ctx = py_openssl_wrapper_HMAC_CTX_new();
+    PY_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self);
     if (ctx == NULL) {
         return NULL;
     }
-    if (locked_HMAC_CTX_copy(ctx, self) < 0) {
-        HMAC_CTX_free(ctx);
-        return NULL;
-    }
-
     retval = PyObject_New(HMACobject, Py_TYPE(self));
     if (retval == NULL) {
-        HMAC_CTX_free(ctx);
+        PY_HMAC_CTX_free(ctx);
         return NULL;
     }
     retval->ctx = ctx;
     HASHLIB_INIT_MUTEX(retval);
-
     return (PyObject *)retval;
 }
 
@@ -2015,7 +2268,7 @@ _hmac_dealloc(PyObject *op)
     HMACobject *self = HMACobject_CAST(op);
     PyTypeObject *tp = Py_TYPE(self);
     if (self->ctx != NULL) {
-        HMAC_CTX_free(self->ctx);
+        PY_HMAC_CTX_free(self->ctx);
         self->ctx = NULL;
     }
     PyObject_Free(self);
@@ -2025,10 +2278,8 @@ _hmac_dealloc(PyObject *op)
 static PyObject *
 _hmac_repr(PyObject *op)
 {
-    const char *digest_name;
     HMACobject *self = HMACobject_CAST(op);
-    const EVP_MD *md = _hashlib_hmac_get_md(self);
-    digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md);
+    const char *digest_name = hashlib_HMAC_get_hashlib_digest_name(self);
     if (digest_name == NULL) {
         assert(PyErr_Occurred());
         return NULL;
@@ -2047,7 +2298,7 @@ static PyObject *
 _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg)
 /*[clinic end generated code: output=f31f0ace8c625b00 input=1829173bb3cfd4e6]*/
 {
-    if (!_hmac_update(self, msg)) {
+    if (_hmac_update(self, msg) < 0) {
         return NULL;
     }
     Py_RETURN_NONE;
@@ -2056,7 +2307,7 @@ _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg)
 /*
  * Extract the MAC value to 'buf' and return the digest size.
  *
- * The buffer 'buf' must have at least hashlib_openssl_HMAC_digest_size(self)
+ * The buffer 'buf' must have at least _hashlib_hmac_digest_size(self)
  * bytes. Smaller buffers lead to undefined behaviors.
  *
  * On error, set an exception and return -1.
@@ -2070,18 +2321,22 @@ _hmac_digest(HMACobject *self, unsigned char *buf)
         assert(PyErr_Occurred());
         return -1;
     }
-    HMAC_CTX *temp_ctx = py_openssl_wrapper_HMAC_CTX_new();
-    if (temp_ctx == NULL) {
-        return -1;
-    }
-    if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) {
-        HMAC_CTX_free(temp_ctx);
+    PY_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self);
+    if (ctx == NULL) {
         return -1;
     }
-    int r = HMAC_Final(temp_ctx, buf, NULL);
-    HMAC_CTX_free(temp_ctx);
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    int r = EVP_MAC_final(ctx, buf, NULL, digest_size);
+#else
+    int r = HMAC_Final(ctx, buf, NULL);
+#endif
+    PY_HMAC_CTX_free(ctx);
     if (r == 0) {
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+        notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_final));
+#else
         notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Final));
+#endif
         return -1;
     }
     return digest_size;
@@ -2133,19 +2388,20 @@ static PyObject *
 _hashlib_hmac_get_block_size(PyObject *op, void *Py_UNUSED(closure))
 {
     HMACobject *self = HMACobject_CAST(op);
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    assert(self->ctx != NULL);
+    return PyLong_FromSize_t(EVP_MAC_CTX_get_block_size(self->ctx));
+#else
     const EVP_MD *md = _hashlib_hmac_get_md(self);
     return md == NULL ? NULL : PyLong_FromLong(EVP_MD_block_size(md));
+#endif
 }
 
 static PyObject *
 _hashlib_hmac_get_name(PyObject *op, void *Py_UNUSED(closure))
 {
     HMACobject *self = HMACobject_CAST(op);
-    const EVP_MD *md = _hashlib_hmac_get_md(self);
-    if (md == NULL) {
-        return NULL;
-    }
-    const char *digest_name = get_hashlib_utf8name_by_evp_md(md);
+    const char *digest_name = hashlib_HMAC_get_hashlib_digest_name(self);
     if (digest_name == NULL) {
         assert(PyErr_Occurred());
         return NULL;
@@ -2472,6 +2728,12 @@ hashlib_clear(PyObject *m)
         _Py_hashtable_destroy(state->hashtable);
         state->hashtable = NULL;
     }
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    if (state->evp_hmac != NULL) {
+        EVP_MAC_free(state->evp_hmac);
+        state->evp_hmac = NULL;
+    }
+#endif
 
     return 0;
 }
@@ -2546,6 +2808,15 @@ hashlib_init_hmactype(PyObject *module)
     if (PyModule_AddType(module, state->HMAC_type) < 0) {
         return -1;
     }
+#ifdef Py_HAS_OPENSSL3_SUPPORT
+    state->evp_hmac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+    if (state->evp_hmac == NULL) {
+        ERR_clear_error();
+        PyErr_SetString(PyExc_ImportError, "cannot initialize EVP_MAC HMAC");
+        return -1;
+    }
+#endif
+
     return 0;
 }