Skip to content

Commit f9fdce3

Browse files
panvaaduh95
authored andcommitted
crypto: use EVP_MAC for HMAC on OpenSSL >=3
Closes: #59493 Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #63942 Fixes: #59493 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 04c04c8 commit f9fdce3

12 files changed

Lines changed: 323 additions & 17 deletions

File tree

benchmark/crypto/create-hmac.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { createHmac } = require('crypto');
5+
const assert = require('assert');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e5],
9+
algo: ['sha1', 'sha256', 'sha512'],
10+
keylen: [0, 16, 64, 1024],
11+
});
12+
13+
function main({ n, algo, keylen }) {
14+
const key = Buffer.alloc(keylen, 'k');
15+
const hmacs = new Array(n);
16+
17+
bench.start();
18+
for (let i = 0; i < n; ++i) {
19+
hmacs[i] = createHmac(algo, key);
20+
}
21+
bench.end(n);
22+
23+
assert.strictEqual(typeof hmacs[n - 1], 'object');
24+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Throughput benchmark
2+
// creates a single HMAC, then pushes a bunch of data through it
3+
'use strict';
4+
5+
const common = require('../common.js');
6+
const { createHmac } = require('crypto');
7+
8+
const bench = common.createBenchmark(main, {
9+
n: [500],
10+
algo: ['sha1', 'sha256', 'sha512'],
11+
keylen: [64],
12+
type: ['asc', 'utf', 'buf'],
13+
len: [2, 1024, 102400, 1024 * 1024],
14+
api: ['update', 'stream'],
15+
});
16+
17+
function main({ api, type, len, algo, keylen, n }) {
18+
let message;
19+
let encoding;
20+
switch (type) {
21+
case 'asc':
22+
message = 'a'.repeat(len);
23+
encoding = 'ascii';
24+
break;
25+
case 'utf':
26+
message = '\u00fc'.repeat(len / 2);
27+
encoding = 'utf8';
28+
break;
29+
case 'buf':
30+
message = Buffer.alloc(len, 'b');
31+
break;
32+
default:
33+
throw new Error(`unknown message type: ${type}`);
34+
}
35+
36+
const fn = api === 'stream' ? streamWrite : updateDigest;
37+
const key = Buffer.alloc(keylen, 'k');
38+
39+
bench.start();
40+
fn(algo, key, message, encoding, n, len);
41+
}
42+
43+
function updateDigest(algo, key, message, encoding, n, len) {
44+
const written = n * len;
45+
const bits = written * 8;
46+
const gbits = bits / (1024 * 1024 * 1024);
47+
const h = createHmac(algo, key);
48+
49+
while (n-- > 0)
50+
h.update(message, encoding);
51+
52+
h.digest();
53+
54+
bench.end(gbits);
55+
}
56+
57+
function streamWrite(algo, key, message, encoding, n, len) {
58+
const written = n * len;
59+
const bits = written * 8;
60+
const gbits = bits / (1024 * 1024 * 1024);
61+
const h = createHmac(algo, key);
62+
63+
while (n-- > 0)
64+
h.write(message, encoding);
65+
66+
h.end();
67+
h.read();
68+
69+
bench.end(gbits);
70+
}

benchmark/crypto/webcrypto-hmac.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { subtle } = globalThis.crypto;
5+
6+
const signParams = { name: 'HMAC' };
7+
8+
let keys;
9+
let currentHash;
10+
let currentKeyLength;
11+
12+
const bench = common.createBenchmark(main, {
13+
hash: ['SHA-1', 'SHA-256', 'SHA-512'],
14+
mode: ['serial', 'parallel'],
15+
keyReuse: ['shared', 'unique'],
16+
keylen: [512],
17+
len: [0, 256, 4096],
18+
n: [1e3],
19+
}, {
20+
test: {
21+
keylen: 512,
22+
},
23+
combinationFilter(p) {
24+
// Unique only differs from shared when operations overlap (parallel);
25+
// sequential calls have no contention so unique+serial adds no value.
26+
if (p.keyReuse === 'unique') return p.mode === 'parallel';
27+
return true;
28+
},
29+
});
30+
31+
async function createSigningKeys(n, hash, keylen) {
32+
keys = new Array(n);
33+
currentHash = hash;
34+
currentKeyLength = keylen;
35+
36+
const algorithm = { name: 'HMAC', hash, length: keylen };
37+
const key = await subtle.generateKey(algorithm, true, ['sign']);
38+
const raw = await subtle.exportKey('raw', key);
39+
for (let i = 0; i < n; ++i) {
40+
keys[i] = await subtle.importKey('raw', raw, algorithm, false, ['sign']);
41+
}
42+
}
43+
44+
async function measureSerial(n, sharedKey, data) {
45+
bench.start();
46+
for (let i = 0; i < n; ++i) {
47+
await subtle.sign(signParams, sharedKey || keys[i], data);
48+
}
49+
bench.end(n);
50+
}
51+
52+
async function measureParallel(n, sharedKey, data) {
53+
const promises = new Array(n);
54+
bench.start();
55+
for (let i = 0; i < n; ++i) {
56+
promises[i] = subtle.sign(signParams, sharedKey || keys[i], data);
57+
}
58+
await Promise.all(promises);
59+
bench.end(n);
60+
}
61+
62+
async function main({ n, mode, keyReuse, hash, keylen, len }) {
63+
if (!keys || keys.length !== n ||
64+
currentHash !== hash || currentKeyLength !== keylen) {
65+
await createSigningKeys(n, hash, keylen);
66+
}
67+
68+
const data = new Uint8Array(len);
69+
data.fill(0x62);
70+
const sharedKey = keyReuse === 'shared' ? keys[0] : undefined;
71+
72+
switch (mode) {
73+
case 'serial':
74+
await measureSerial(n, sharedKey, data);
75+
break;
76+
case 'parallel':
77+
await measureParallel(n, sharedKey, data);
78+
break;
79+
}
80+
}

deps/ncrypto/ncrypto.cc

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4629,6 +4629,7 @@ bool extractP1363(const Buffer<const unsigned char>& buf,
46294629

46304630
// ============================================================================
46314631

4632+
#if !OPENSSL_WITH_EVP_MAC
46324633
HMACCtxPointer::HMACCtxPointer() : ctx_(nullptr) {}
46334634

46344635
HMACCtxPointer::HMACCtxPointer(HMAC_CTX* ctx) : ctx_(ctx) {}
@@ -4688,8 +4689,9 @@ bool HMACCtxPointer::digestInto(Buffer<void>* buf) {
46884689
HMACCtxPointer HMACCtxPointer::New() {
46894690
return HMACCtxPointer(HMAC_CTX_new());
46904691
}
4692+
#endif // !OPENSSL_WITH_EVP_MAC
46914693

4692-
#if OPENSSL_WITH_KMAC
4694+
#if OPENSSL_WITH_EVP_MAC
46934695
EVPMacPointer::EVPMacPointer(EVP_MAC* mac) : mac_(mac) {}
46944696

46954697
EVPMacPointer::EVPMacPointer(EVPMacPointer&& other) noexcept
@@ -4777,7 +4779,92 @@ EVPMacCtxPointer EVPMacCtxPointer::New(EVP_MAC* mac) {
47774779
if (!mac) return EVPMacCtxPointer();
47784780
return EVPMacCtxPointer(EVP_MAC_CTX_new(mac));
47794781
}
4780-
#endif // OPENSSL_WITH_KMAC
4782+
4783+
HMACCtxPointer::HMACCtxPointer() = default;
4784+
4785+
HMACCtxPointer::HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx)
4786+
: mac_(std::move(mac)), ctx_(std::move(ctx)) {}
4787+
4788+
HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept
4789+
: mac_(std::move(other.mac_)),
4790+
ctx_(std::move(other.ctx_)),
4791+
md_size_(other.md_size_) {
4792+
other.md_size_ = 0;
4793+
}
4794+
4795+
HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept {
4796+
if (this == &other) return *this;
4797+
mac_ = std::move(other.mac_);
4798+
ctx_ = std::move(other.ctx_);
4799+
md_size_ = other.md_size_;
4800+
other.md_size_ = 0;
4801+
return *this;
4802+
}
4803+
4804+
HMACCtxPointer::~HMACCtxPointer() {
4805+
reset();
4806+
}
4807+
4808+
void HMACCtxPointer::reset() {
4809+
ctx_.reset();
4810+
mac_.reset();
4811+
md_size_ = 0;
4812+
}
4813+
4814+
bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) {
4815+
if (!ctx_ || !md) return false;
4816+
4817+
const char* md_name = EVP_MD_get0_name(md);
4818+
if (md_name == nullptr) return false;
4819+
4820+
OSSL_PARAM params[] = {
4821+
OSSL_PARAM_construct_utf8_string(
4822+
OSSL_MAC_PARAM_DIGEST, const_cast<char*>(md_name), 0),
4823+
OSSL_PARAM_construct_end(),
4824+
};
4825+
4826+
if (!ctx_.init(buf, params)) return false;
4827+
md_size_ = md.size();
4828+
return true;
4829+
}
4830+
4831+
bool HMACCtxPointer::update(const Buffer<const void>& buf) {
4832+
if (!ctx_) return false;
4833+
return ctx_.update(buf);
4834+
}
4835+
4836+
DataPointer HMACCtxPointer::digest() {
4837+
if (md_size_ == 0) return {};
4838+
auto data = DataPointer::Alloc(md_size_);
4839+
if (!data) return {};
4840+
Buffer<void> buf = data;
4841+
if (!digestInto(&buf)) return {};
4842+
return data.resize(buf.len);
4843+
}
4844+
4845+
bool HMACCtxPointer::digestInto(Buffer<void>* buf) {
4846+
if (!ctx_) return false;
4847+
4848+
size_t len = buf->len;
4849+
if (EVP_MAC_final(
4850+
ctx_.get(), static_cast<unsigned char*>(buf->data), &len, buf->len) !=
4851+
1)
4852+
return false;
4853+
4854+
buf->len = len;
4855+
return true;
4856+
}
4857+
4858+
HMACCtxPointer HMACCtxPointer::New() {
4859+
auto mac = EVPMacPointer::Fetch(OSSL_MAC_NAME_HMAC);
4860+
if (!mac) return {};
4861+
4862+
auto ctx = EVPMacCtxPointer::New(mac.get());
4863+
if (!ctx) return {};
4864+
4865+
return HMACCtxPointer(std::move(mac), std::move(ctx));
4866+
}
4867+
#endif // OPENSSL_WITH_EVP_MAC
47814868

47824869
DataPointer hashDigest(const Buffer<const unsigned char>& buf,
47834870
const EVP_MD* md) {

deps/ncrypto/ncrypto.h

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@
6565
#endif
6666

6767
#if OPENSSL_VERSION_PREREQ(3, 0)
68-
#define OPENSSL_WITH_KMAC 1
68+
#define OPENSSL_WITH_EVP_MAC 1
6969
#else
70-
#define OPENSSL_WITH_KMAC 0
70+
#define OPENSSL_WITH_EVP_MAC 0
7171
#endif
7272

7373
#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_PREREQ(3, 2)
@@ -1528,6 +1528,7 @@ class EVPMDCtxPointer final {
15281528
DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free> ctx_;
15291529
};
15301530

1531+
#if !OPENSSL_WITH_EVP_MAC
15311532
class HMACCtxPointer final {
15321533
public:
15331534
HMACCtxPointer();
@@ -1554,8 +1555,9 @@ class HMACCtxPointer final {
15541555
private:
15551556
DeleteFnPtr<HMAC_CTX, HMAC_CTX_free> ctx_;
15561557
};
1558+
#endif // !OPENSSL_WITH_EVP_MAC
15571559

1558-
#if OPENSSL_WITH_KMAC
1560+
#if OPENSSL_WITH_EVP_MAC
15591561
class EVPMacPointer final {
15601562
public:
15611563
EVPMacPointer() = default;
@@ -1603,7 +1605,34 @@ class EVPMacCtxPointer final {
16031605
private:
16041606
DeleteFnPtr<EVP_MAC_CTX, EVP_MAC_CTX_free> ctx_;
16051607
};
1606-
#endif // OPENSSL_WITH_KMAC
1608+
1609+
class HMACCtxPointer final {
1610+
public:
1611+
HMACCtxPointer();
1612+
HMACCtxPointer(HMACCtxPointer&& other) noexcept;
1613+
HMACCtxPointer& operator=(HMACCtxPointer&& other) noexcept;
1614+
NCRYPTO_DISALLOW_COPY(HMACCtxPointer)
1615+
~HMACCtxPointer();
1616+
1617+
inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; }
1618+
inline operator bool() const { return ctx_ != nullptr; }
1619+
void reset();
1620+
1621+
bool init(const Buffer<const void>& buf, const Digest& md);
1622+
bool update(const Buffer<const void>& buf);
1623+
DataPointer digest();
1624+
bool digestInto(Buffer<void>* buf);
1625+
1626+
static HMACCtxPointer New();
1627+
1628+
private:
1629+
HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx);
1630+
1631+
EVPMacPointer mac_;
1632+
EVPMacCtxPointer ctx_;
1633+
size_t md_size_ = 0;
1634+
};
1635+
#endif // OPENSSL_WITH_EVP_MAC
16071636

16081637
#ifndef OPENSSL_NO_ENGINE
16091638
class EnginePointer final {

src/crypto/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,18 @@ using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
9090
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
9191
using DHPointer = DeleteFnPtr<DH, DH_free>;
9292
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
93-
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
9493
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
9594
```
9695

9796
Examples of these being used are pervasive through the `src/crypto` code.
9897

98+
`HMACCtxPointer` is a dedicated HMAC state wrapper rather than a plain
99+
`DeleteFnPtr` alias. On OpenSSL 3 and later it owns the provider-backed
100+
`EVP_MAC`/`EVP_MAC_CTX` state. On OpenSSL 1.1.1 and BoringSSL it owns the
101+
legacy `HMAC_CTX` state. HMAC call sites should use `HMACCtxPointer::New()`,
102+
`init()`, `update()`, and `digest()`/`digestInto()` so the backend selection
103+
stays contained in ncrypto.
104+
99105
### `ByteSource`
100106

101107
The `ByteSource` class is a helper utility representing a _read-only_ byte

src/crypto/crypto_hmac.cc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ using v8::Uint32;
3030
using v8::Value;
3131

3232
namespace crypto {
33-
Hmac::Hmac(Environment* env, Local<Object> wrap)
34-
: BaseObject(env, wrap),
35-
ctx_(nullptr) {
33+
Hmac::Hmac(Environment* env, Local<Object> wrap) : BaseObject(env, wrap) {
3634
MakeWeak();
3735
}
3836

0 commit comments

Comments
 (0)