Commit 1eaaeee72c2 for nodejs
commit 1eaaeee72c288766d5e52c7a90a757f397dfdb5d
Author: Yagiz Nizipli <yagiz@nizipli.com>
Date: Thu Oct 29 11:21:04 2026 -0400
crypto: reuse HMAC contexts in createHmac()
Every HMAC context was created with EVP_MAC_fetch() and keyed with the
digest passed by name, so the provider fetched the digest again on
each init. Both lookups go through OpenSSL's method store.
Keep an HMAC context without a key for each digest in the
per-Environment digest cache, and duplicate it in createHmac(). A
duplicated context already has its digest, so keying it with the same
digest no longer fetches anything.
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Assisted-by: Grok
PR-URL: https://github.com/nodejs/node/pull/67109
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index ba0e1f2e09d..f0940bb38f5 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -5506,16 +5506,39 @@ DigestCache::Result DigestCache::insert(const char* name,
#endif
}
+DigestCache::~DigestCache() = default;
+
void DigestCache::reset(uint64_t generation) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (generation_ == generation) return;
aliases_.clear();
+ hmac_templates_.clear();
digests_.clear();
first_id_ = next_id_;
#endif
generation_ = generation;
}
+#if NCRYPTO_USE_OPENSSL_PROVIDER
+const HMACCtxPointer* DigestCache::getHmacTemplate(const EVP_MD* digest,
+ uint64_t generation) {
+ if (generation_ != generation || digest == nullptr) return nullptr;
+ for (size_t index = 0; index < digests_.size(); index++) {
+ if (digests_[index].get() != digest) continue;
+ if (hmac_templates_.size() <= index) {
+ hmac_templates_.resize(digests_.size());
+ }
+ if (!hmac_templates_[index]) {
+ auto ctx = HMACCtxPointer::New(Digest::Ref(digest));
+ if (!ctx) return nullptr;
+ hmac_templates_[index] = std::make_unique<HMACCtxPointer>(std::move(ctx));
+ }
+ return hmac_templates_[index].get();
+ }
+ return nullptr;
+}
+#endif
+
const DigestCache::AliasMap& DigestCache::aliases() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return aliases_;
@@ -8584,7 +8607,9 @@ HMACCtxPointer::HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx)
HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept
: mac_(std::move(other.mac_)),
ctx_(std::move(other.ctx_)),
+ md_(other.md_),
md_size_(other.md_size_) {
+ other.md_ = nullptr;
other.md_size_ = 0;
}
@@ -8592,7 +8617,9 @@ HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept {
if (this == &other) return *this;
mac_ = std::move(other.mac_);
ctx_ = std::move(other.ctx_);
+ md_ = other.md_;
md_size_ = other.md_size_;
+ other.md_ = nullptr;
other.md_size_ = 0;
return *this;
}
@@ -8604,11 +8631,13 @@ HMACCtxPointer::~HMACCtxPointer() {
void HMACCtxPointer::reset() {
ctx_.reset();
mac_.reset();
+ md_ = nullptr;
md_size_ = 0;
}
bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) {
if (!ctx_ || !md) return false;
+ if (md.get() == md_) return ctx_.init(buf);
const char* md_name = EVP_MD_get0_name(md);
if (md_name == nullptr) return false;
@@ -8620,6 +8649,7 @@ bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) {
};
if (!ctx_.init(buf, params)) return false;
+ md_ = md.get();
md_size_ = md.size();
return true;
}
@@ -8651,6 +8681,16 @@ bool HMACCtxPointer::digestInto(Buffer<void>* buf) {
return true;
}
+HMACCtxPointer HMACCtxPointer::clone() const {
+ if (!ctx_) return {};
+ EVPMacCtxPointer ctx(EVP_MAC_CTX_dup(ctx_.get()));
+ if (!ctx) return {};
+ HMACCtxPointer result(EVPMacPointer(), std::move(ctx));
+ result.md_ = md_;
+ result.md_size_ = md_size_;
+ return result;
+}
+
HMACCtxPointer HMACCtxPointer::New() {
auto mac = EVPMacPointer::Fetch(OSSL_MAC_NAME_HMAC);
if (!mac) return {};
@@ -8660,6 +8700,25 @@ HMACCtxPointer HMACCtxPointer::New() {
return HMACCtxPointer(std::move(mac), std::move(ctx));
}
+
+HMACCtxPointer HMACCtxPointer::New(const Digest& md) {
+ if (!md) return {};
+ const char* md_name = EVP_MD_get0_name(md);
+ if (md_name == nullptr) return {};
+
+ auto result = New();
+ if (!result) return {};
+
+ OSSL_PARAM params[] = {
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_MAC_PARAM_DIGEST, const_cast<char*>(md_name), 0),
+ OSSL_PARAM_construct_end(),
+ };
+ if (EVP_MAC_CTX_set_params(result.ctx_.get(), params) != 1) return {};
+ result.md_ = md.get();
+ result.md_size_ = md.size();
+ return result;
+}
#endif // OPENSSL_WITH_EVP_MAC
DataPointer hashDigest(const Buffer<const unsigned char>& buf,
diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h
index adafe98b307..7b972258ad7 100644
--- a/deps/ncrypto/ncrypto.h
+++ b/deps/ncrypto/ncrypto.h
@@ -307,6 +307,7 @@ class DataPointer;
class DHPointer;
class ECKeyPointer;
class EVPKeyPointer;
+class HMACCtxPointer;
class KeyAlgorithm;
class MacCache;
class EVPMacCtxPointer;
@@ -403,6 +404,7 @@ class DigestCache final {
CaseInsensitiveNameEqual>;
DigestCache() = default;
+ ~DigestCache();
NCRYPTO_DISALLOW_COPY_AND_MOVE(DigestCache)
Result lookup(const char* name, uint64_t generation) const;
@@ -425,6 +427,12 @@ class DigestCache final {
Result insert(const char* name, const EVP_MD* digest, uint64_t generation);
void reset(uint64_t generation);
const AliasMap& aliases() const;
+#if NCRYPTO_USE_OPENSSL_PROVIDER
+ // Returns an HMAC context for the cached digest that has no key yet.
+ // Duplicating it avoids fetching HMAC and the digest for every context.
+ const HMACCtxPointer* getHmacTemplate(const EVP_MD* digest,
+ uint64_t generation);
+#endif
private:
uint64_t generation_ = 0;
@@ -436,6 +444,7 @@ class DigestCache final {
uint32_t first_id_ = 0;
uint32_t next_id_ = 0;
std::vector<EVPMDPointer> digests_;
+ std::vector<std::unique_ptr<HMACCtxPointer>> hmac_templates_;
AliasMap aliases_;
#endif
};
@@ -2151,14 +2160,19 @@ class HMACCtxPointer final {
bool update(const Buffer<const void>& buf);
DataPointer digest();
bool digestInto(Buffer<void>* buf);
+ HMACCtxPointer clone() const;
static HMACCtxPointer New();
+ // Creates a context that already uses md, so that init() does not have to
+ // fetch md again when it is called with the same digest.
+ static HMACCtxPointer New(const Digest& md);
private:
HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx);
EVPMacPointer mac_;
EVPMacCtxPointer ctx_;
+ const EVP_MD* md_ = nullptr;
size_t md_size_ = 0;
};
#endif // OPENSSL_WITH_EVP_MAC
diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc
index 492abc7c722..b0ea75e88b2 100644
--- a/src/crypto/crypto_hash.cc
+++ b/src/crypto/crypto_hash.cc
@@ -382,6 +382,18 @@ const ncrypto::Digest GetDigestForJob(Environment* env,
return ncrypto::Digest::FromName(name);
}
+ncrypto::HMACCtxPointer NewHmacContext(Environment* env,
+ const ncrypto::Digest& md) {
+#if NCRYPTO_USE_OPENSSL_PROVIDER
+ SynchronizeHashCache(env);
+ if (const auto* hmac_template = env->provider_digest_cache->getHmacTemplate(
+ md, env->hash_cache_generation)) {
+ if (auto ctx = hmac_template->clone()) return ctx;
+ }
+#endif
+ return ncrypto::HMACCtxPointer::New();
+}
+
void MarkInvalidXofLength() {
#if NCRYPTO_USE_OPENSSL_PROVIDER
ERR_raise(ERR_LIB_EVP, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
diff --git a/src/crypto/crypto_hash.h b/src/crypto/crypto_hash.h
index 7aaac944786..322ff83da69 100644
--- a/src/crypto/crypto_hash.h
+++ b/src/crypto/crypto_hash.h
@@ -24,6 +24,12 @@ const ncrypto::Digest GetDigestForJob(Environment* env,
CryptoJobMode mode,
const char* name);
+// Returns an HMAC context to be keyed with md. Contexts for digests from
+// GetDigest() are duplicated from a cached template, so keying them does not
+// fetch HMAC or md again.
+ncrypto::HMACCtxPointer NewHmacContext(Environment* env,
+ const ncrypto::Digest& md);
+
class Hash final : public BaseObject {
public:
static void Initialize(Environment* env, v8::Local<v8::Object> target);
diff --git a/src/crypto/crypto_hmac.cc b/src/crypto/crypto_hmac.cc
index 36f07c7d98d..8345b68bc90 100644
--- a/src/crypto/crypto_hmac.cc
+++ b/src/crypto/crypto_hmac.cc
@@ -79,7 +79,7 @@ void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
key = "";
}
- ctx_ = HMACCtxPointer::New();
+ ctx_ = NewHmacContext(env(), md);
ncrypto::Buffer<const void> key_buf{
.data = key,
.len = static_cast<size_t>(key_len),