Add a description
Description
The current cryptographic API in userver::crypto (e.g., crypto::PrivateKey::GetPemString, crypto::CmsSigner::Sign) heavily uses std::string for both input and output data. While this approach is simple and convenient, it results in unpredictable heap allocations on every call.
This creates serious problems for high‑load microservices built on userver, especially with the advent of post‑quantum cryptography (PQC), where key and signature sizes can reach tens of kilobytes. Frequent allocations on the hot path of signing/verification operations can lead to unpredictable latency, increased allocator pressure, and memory fragmentation — contradicting userver’s philosophy of writing predictable and efficient asynchronous applications.
Concrete example
The current GetPemString() method returns std::optional<std::string>. Even for a single PEM blob (e.g., a large PQC private key), this causes at least one heap allocation per call. Under thousands of requests per second, this becomes a bottleneck.
Proposed solution
Introduce a set of overloaded methods that take a pre‑allocated output buffer via std::span<std::byte> (or std::span<char>). This allows users to manage memory themselves, reuse buffers, and completely eliminate dynamic allocation overhead on the hot path.
API changes
-
PrivateKey
- Existing method:
std::optional<std::string> GetPemString(std::string_view password) const;
- New overload:
bool GetPemString(std::span<char> out_buffer, std::string_view password, size_t& out_len) const;
Returns true on success and writes the exact output size to out_len. If the buffer is too small, it returns false (or optionally writes the required size to out_len without copying).
-
crypto:: Certificate, crypto::ComputeHash (hash.cpp) and crypto::ComputeHmac (signers.cpp) – similar overloads accepting std::span<char> for output.
-
crypto::CmsSigner – an overload for Sign that takes an output buffer.
Usage example
// Old (allocation):
std::optional<std::string> pem_opt = private_key.GetPemString("pass");
if (pem_opt) {
// use pem_opt.value()
}
// New (zero‑allocation if buffer is reused):
std::vector<char> buffer(4096); // reusable buffer
size_t written = 0;
if (private_key.GetPemString(buffer, "pass", written)) {
std::string_view pem(buffer.data(), written);
// use pem
}
Migration plan (stranger pattern)
To keep the change safe and backward‑compatible, we propose a gradual migration:
- Add the new overloads while keeping the old methods unchanged.
- Deprecate the old methods in a future major release (add [[deprecated]] with a message pointing to the new API).
- Remove the old methods after one or two major releases, giving the community enough time to migrate.
Expected benefits
· Performance: zero heap allocations on the hot crypto path.
· Predictability: latency is not affected by allocator contention.
· Future‑proof: ready for large PQC keys and signatures without hidden overhead.
Add a description
Description
The current cryptographic API in
userver::crypto(e.g.,crypto::PrivateKey::GetPemString,crypto::CmsSigner::Sign) heavily usesstd::stringfor both input and output data. While this approach is simple and convenient, it results in unpredictable heap allocations on every call.This creates serious problems for high‑load microservices built on userver, especially with the advent of post‑quantum cryptography (PQC), where key and signature sizes can reach tens of kilobytes. Frequent allocations on the hot path of signing/verification operations can lead to unpredictable latency, increased allocator pressure, and memory fragmentation — contradicting userver’s philosophy of writing predictable and efficient asynchronous applications.
Concrete example
The current
GetPemString()method returnsstd::optional<std::string>. Even for a single PEM blob (e.g., a large PQC private key), this causes at least one heap allocation per call. Under thousands of requests per second, this becomes a bottleneck.Proposed solution
Introduce a set of overloaded methods that take a pre‑allocated output buffer via
std::span<std::byte>(orstd::span<char>). This allows users to manage memory themselves, reuse buffers, and completely eliminate dynamic allocation overhead on the hot path.API changes
PrivateKeyReturns
trueon success and writes the exact output size toout_len. If the buffer is too small, it returnsfalse(or optionally writes the required size toout_lenwithout copying).crypto:: Certificate,crypto::ComputeHash(hash.cpp) andcrypto::ComputeHmac(signers.cpp) – similar overloads acceptingstd::span<char>for output.crypto::CmsSigner– an overload forSignthat takes an output buffer.Usage example
Migration plan (stranger pattern)
To keep the change safe and backward‑compatible, we propose a gradual migration:
Expected benefits
· Performance: zero heap allocations on the hot crypto path.
· Predictability: latency is not affected by allocator contention.
· Future‑proof: ready for large PQC keys and signatures without hidden overhead.