OSSL_HPKE_CTX_NEW(3ossl)
π OSSL_HPKE_CTX_NEW(3ossl) π
okay bestie, so this is straight out of the OpenSSL manual, section 3ossl, and the page is called OSSL_HPKE_CTX_NEW(3ossl). it's giving β¨cryptographyβ¨, it's giving "i understand RFC9180 and you're about to also". buckle up π
πΈ NAME πΈ
OSSL_HPKE_CTX_new, OSSL_HPKE_CTX_free, OSSL_HPKE_encap, OSSL_HPKE_decap, OSSL_HPKE_seal, OSSL_HPKE_open, OSSL_HPKE_export, OSSL_HPKE_suite_check, OSSL_HPKE_str2suite, OSSL_HPKE_keygen, OSSL_HPKE_get_grease_value, OSSL_HPKE_get_ciphertext_size, OSSL_HPKE_get_public_encap_size, OSSL_HPKE_get_recommended_ikmelen, OSSL_HPKE_CTX_set1_psk, OSSL_HPKE_CTX_set1_ikme, OSSL_HPKE_CTX_set1_authpriv, OSSL_HPKE_CTX_set1_authpub, OSSL_HPKE_CTX_get_seq, OSSL_HPKE_CTX_set_seq β Hybrid Public Key Encryption (HPKE) functions
no cap, that's a lot of functions and they all live together on this one page π«Ά
β¨ SYNOPSIS β¨
#include <openssl/hpke.h>
typedef struct {
uint16_t kem_id;
uint16_t kdf_id;
uint16_t aead_id;
} OSSL_HPKE_SUITE;
OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite, int role,
OSSL_LIB_CTX *libctx, const char *propq);
void OSSL_HPKE_CTX_free(OSSL_HPKE_CTX *ctx);
int OSSL_HPKE_encap(OSSL_HPKE_CTX *ctx,
unsigned char *enc, size_t *enclen,
const unsigned char *pub, size_t publen,
const unsigned char *info, size_t infolen);
int OSSL_HPKE_seal(OSSL_HPKE_CTX *ctx,
unsigned char *ct, size_t *ctlen,
const unsigned char *aad, size_t aadlen,
const unsigned char *pt, size_t ptlen);
int OSSL_HPKE_keygen(OSSL_HPKE_SUITE suite,
unsigned char *pub, size_t *publen, EVP_PKEY **priv,
const unsigned char *ikm, size_t ikmlen,
OSSL_LIB_CTX *libctx, const char *propq);
int OSSL_HPKE_decap(OSSL_HPKE_CTX *ctx,
const unsigned char *enc, size_t enclen,
EVP_PKEY *recippriv,
const unsigned char *info, size_t infolen);
int OSSL_HPKE_open(OSSL_HPKE_CTX *ctx,
unsigned char *pt, size_t *ptlen,
const unsigned char *aad, size_t aadlen,
const unsigned char *ct, size_t ctlen);
int OSSL_HPKE_export(OSSL_HPKE_CTX *ctx,
unsigned char *secret, size_t secretlen,
const unsigned char *label, size_t labellen);
int OSSL_HPKE_CTX_set1_authpriv(OSSL_HPKE_CTX *ctx, EVP_PKEY *priv);
int OSSL_HPKE_CTX_set1_authpub(OSSL_HPKE_CTX *ctx,
unsigned char *pub, size_t publen);
int OSSL_HPKE_CTX_set1_psk(OSSL_HPKE_CTX *ctx,
const char *pskid,
const unsigned char *psk, size_t psklen);
int OSSL_HPKE_CTX_get_seq(OSSL_HPKE_CTX *ctx, uint64_t *seq);
int OSSL_HPKE_CTX_set_seq(OSSL_HPKE_CTX *ctx, uint64_t seq);
int OSSL_HPKE_CTX_set1_ikme(OSSL_HPKE_CTX *ctx,
const unsigned char *ikme, size_t ikmelen);
int OSSL_HPKE_suite_check(OSSL_HPKE_SUITE suite);
int OSSL_HPKE_get_grease_value(const OSSL_HPKE_SUITE *suite_in,
OSSL_HPKE_SUITE *suite,
unsigned char *enc, size_t *enclen,
unsigned char *ct, size_t ctlen,
OSSL_LIB_CTX *libctx, const char *propq);
int OSSL_HPKE_str2suite(const char *str, OSSL_HPKE_SUITE *suite);
size_t OSSL_HPKE_get_ciphertext_size(OSSL_HPKE_SUITE suite, size_t clearlen);
size_t OSSL_HPKE_get_public_encap_size(OSSL_HPKE_SUITE suite);
size_t OSSL_HPKE_get_recommended_ikmelen(OSSL_HPKE_SUITE suite);
π DESCRIPTION π
these functions hand you an API for the flavour of Hybrid Public Key Encryption (HPKE) that's defined in RFC9180. and like, real talk? you're probably gonna wanna actually understand the HPKE spec before you start poking at these APIs, bestie. HPKE gets used by a bunch of other IETF specs too, including the TLS Encrypted Client Hello (ECH) spec and others π
so what even is HPKE? it's a standardised, super flexible construct for encrypting "to" a public key π. it supports combinations of a key encapsulation method (KEM), a key derivation function (KDF) and an authenticated encryption with additional data (AEAD) algorithm β plus optional sender authentication. iconic behaviour honestly.
the sender and the receiver here are generally some application or protocol that's making use of HPKE. like, with ECH, the sender is a browser and the receiver is a web server. main character energy on both sides π¦
π Data Structures π
OSSL_HPKE_SUITE is a structure that holds identifiers for the algorithms used for KEM, KDF and AEAD operations.
OSSL_HPKE_CTX is a context that keeps the internal state while HPKE operations are happening. and this part is important: you must use separate OSSL_HPKE_CTX objects for the sender and the receiver. if you try to use one single context for both, you get errors. no sharing, it's not that kind of friendship π
π OSSL_HPKE_SUITE Identifiers π
the identifiers that OSSL_HPKE_SUITE uses are:
the KEM identifier kem_id is one of these:
0x10βOSSL_HPKE_KEM_ID_P2560x11βOSSL_HPKE_KEM_ID_P3840x12βOSSL_HPKE_KEM_ID_P5210x20βOSSL_HPKE_KEM_ID_X255190x21βOSSL_HPKE_KEM_ID_X448
the KDF identifier kdf_id is one of these:
0x01βOSSL_HPKE_KDF_ID_HKDF_SHA2560x02βOSSL_HPKE_KDF_ID_HKDF_SHA3840x03βOSSL_HPKE_KDF_ID_HKDF_SHA512
the AEAD identifier aead_id is one of these:
0x01βOSSL_HPKE_AEAD_ID_AES_GCM_1280x02βOSSL_HPKE_AEAD_ID_AES_GCM_2560x03βOSSL_HPKE_AEAD_ID_CHACHA_POLY13050xFFFFβOSSL_HPKE_AEAD_ID_EXPORTONLY
and that last one up there? that one means you don't need AEAD operations at all. OSSL_HPKE_export() still works fine, but OSSL_HPKE_open() and OSSL_HPKE_seal() will return an error if you call them on a context that's using that AEAD identifier. π«
π HPKE Modes π
HPKE supports these variants of Authentication, picked via a mode identifier:
OSSL_HPKE_MODE_BASE,0x00β no authentication at all, we're just vibing.OSSL_HPKE_MODE_PSK,0x01β authenticates possession of a pre-shared key (PSK).OSSL_HPKE_MODE_AUTH,0x02β authenticates possession of a KEM-based sender private key.OSSL_HPKE_MODE_PSKAUTH,0x03β a combo ofOSSL_HPKE_MODE_PSKandOSSL_HPKE_MODE_AUTH. both the PSK and the sender's authentication public/private pair have to be supplied before the encapsulation/decapsulation operation will actually work. both or nothing, periodt.
for more on authentication, go look at "Pre-Shared Key HPKE modes" and "Sender-authenticated HPKE Modes".
π¦ HPKE Roles π¦
HPKE contexts have a role β either sender or receiver. this controls which functions you're allowed to call, and it also exists so that senders don't go reusing a key and nonce with different plaintexts (which would be, and i cannot stress this enough, extremely bad π).
these can be called no matter what role you're in: OSSL_HPKE_CTX_free(), OSSL_HPKE_export(), OSSL_HPKE_CTX_set1_psk(), and OSSL_HPKE_CTX_get_seq().
OSSL_HPKE_ROLE_SENDER,0β anOSSL_HPKE_CTXwith this role can be used withOSSL_HPKE_encap(),OSSL_HPKE_seal(),OSSL_HPKE_CTX_set1_ikme()andOSSL_HPKE_CTX_set1_authpriv().OSSL_HPKE_ROLE_RECEIVER,1β anOSSL_HPKE_CTXwith this role can be used withOSSL_HPKE_decap(),OSSL_HPKE_open(),OSSL_HPKE_CTX_set1_authpub()andOSSL_HPKE_CTX_set_seq().
call a function with the wrong role set on the OSSL_HPKE_CTX and you get an error. the vibes check fails, sorry π
π Parameter Size Limits π
to keep interoperability nice and friendly, RFC9180, section 7.2.1 suggests a RECOMMENDED maximum size of 64 octets for various input parameters. in this implementation we apply a limit of 66 octets for the ikmlen, psklen, and labellen parameters, and for the length of the string pskid in the HPKE functions below. the constant OSSL_HPKE_MAX_PARMLEN is defined as the limit of this value. (we picked 66 octets so we can validate all the test vectors that show up in RFC9180, Appendix A. strategic β¨)
and per RFC9180, section 9.5, we define a constant OSSL_HPKE_MIN_PSKLEN with a value of 32, which is the minimum length of a pre-shared key passed in psklen.
also: RFC9180 RECOMMENDS a 64 octet limit for the infolen parameter too, but that is not enough for TLS Encrypted ClientHello (ECH) processing, so we enforce a limit of OSSL_HPKE_MAX_INFOLEN with a value of 1024 as the limit for infolen. we said what we said π
π Context Construct/Free π
OSSL_HPKE_CTX_new() creates an OSSL_HPKE_CTX context object that you'll use for all the HPKE operations that follow. you hand it a mode (see "HPKE Modes"), a suite (see "OSSL_HPKE_SUITE Identifiers") and a role (see "HPKE Roles"). the libctx and propq are what's used when fetching algorithms from providers, and both may be set to NULL.
OSSL_HPKE_CTX_free() frees the ctx OSSL_HPKE_CTX that some earlier call to OSSL_HPKE_CTX_new() created. if the argument you pass to OSSL_HPKE_CTX_free() is NULL, nothing happens. it's a no-op, it's chill, it's fine π«Ά
π₯ Sender APIs π₯
a sender's whole goal is to use HPKE to encrypt with a public key β going through a KEM, then a KDF, and finally an AEAD. first step: encapsulate (using OSSL_HPKE_encap()) the sender's public value using the recipient's public key, pub, and derive secrets internally. that produces the encapsulated public value enc, which gets sent off to the recipient in whatever protocol is using HPKE. once the encapsulation step is done, the sender can make one or more calls to OSSL_HPKE_seal() to encrypt plaintexts using the secret stored inside ctx. chef's kiss π
OSSL_HPKE_encap() uses the HPKE context ctx, the recipient public value pub of size publen, and an optional info parameter of size infolen, to produce the encapsulated public value enc. on input, enclen should hold the maximum size of the enc buffer, and on the way out it holds the output size. an error happens if the input enclen is smaller than the value returned from OSSL_HPKE_get_public_encap_size(). info can be used to bind other protocol or application artefacts like identifiers. generally, the encapsulated public value enc corresponds to a single-use ephemeral private value created as part of the encapsulation process. and heads up: only a single call to OSSL_HPKE_encap() is allowed for a given OSSL_HPKE_CTX. one and done π
OSSL_HPKE_seal() takes the OSSL_HPKE_CTX context ctx, the plaintext buffer pt of size ptlen, and the optional additional authenticated data buffer aad of size aadlen, and returns the ciphertext ct of size ctlen. on input, ctlen should hold the maximum size of the ct buffer, and on the way out it holds the output size. an error happens if the input ctlen is smaller than the value returned from OSSL_HPKE_get_public_encap_size().
OSSL_HPKE_encap()must be called beforeOSSL_HPKE_seal(). no skipping ahead bestie.
OSSL_HPKE_seal() can be called multiple times, and there's an internal "nonce" that gets incremented by one after every call. little counter doing its thing β
π Recipient APIs π
So bestie, recipients are the ones receiving the vibe, and here's the tea: they need a private value that's typically less ephemeral than the sender's, because the matching public value has to get out there to all the potential senders through whatever protocol is running the HPKE show. That's why recipients generally start by generating a key pair and then have to babysit their private key value using standard mechanisms outside the scope of this API. Good news though: private keys are just normal EVP_PKEY(3) pointers, so all your usual private key management machinery works on them. π«Ά
For encapsulation to even happen, the recipient has to get its public value over to the sender. And here's the plot twist β there's no generic HPKE format defined for that. The formatting is meant to be defined by the application/protocols that use HPKE. Like, ECH for example defines an ECHConfig data structure that smooshes the public value together with other ECH data items. So you gotta use normal library functions to pull out the public value in whatever format you need, working from the EVP_PKEY(3) for the private value. iykyk π
OSSL_HPKE_keygen()β this is the glow-up moment for recipients who need to generate a key pair based on the HPKE suite they're gonna use. It hands you back anEVP_PKEY(3)pointer for the private valueprivand an encoded public keypubof sizepublen. On the way in,publenshould hold the maximum size of thepubbuffer, and on the way out it holds the output size. If the inputpublenis too small β error, sorry babe. π Thelibctxandpropqget used when fetching algorithms from providers and can be set toNULL. Also, the HPKE spec defines a deterministic key generation scheme where the private value is derived from initial keying material (IKM), soOSSL_HPKE_keygen()has an option for that too, using theikmparameter of sizeikmlen. If eitherikmisNULLorikmlenis zero, you just get a randomly generated key for the relevant suite. And if you do needikmlen, it should be greater than or equal toOSSL_HPKE_get_recommended_ikmelen().OSSL_HPKE_decap()β takes the sender's encapsulated public value produced byOSSL_HPKE_encap()(that'senc) plus the recipient'sEVP_PKEY(3)pointer (prov), and then re-generates the internal secret the sender derived. Same as before, there's an optionalinfoparameter so you can bind that derived secret to other application/protocol artefacts. β¨ Big note: only a single call toOSSL_HPKE_decap()is allowed for a givenOSSL_HPKE_CTX. One and done, periodt.OSSL_HPKE_open()β recipient's decryption moment. It decrypts the ciphertextctof sizectlenusing thectxand additional authenticated dataaadof sizeaadlen, producing the plaintextptof sizeptlen. On input,ptlenshould contain the maximum size of theptbuffer, and it returns the output size. Aptbuffer that's the same size as thectbuffer will suffice β generally the plaintext output comes out a little smaller than the ciphertext input. If the inputptlenis too small: error. πOSSL_HPKE_open()may be called multiple times, but just like withOSSL_HPKE_seal()there's an internally incrementing nonce value, so ciphertexts have to be presented in the same order thatOSSL_HPKE_seal()used. Need to process multiple ciphertexts out of order? Peep "Re-sequencing" below. π¦
πΈ Exporting Secrets πΈ
HPKE defines a whole way to produce exported secrets for the application to use. Iconic, honestly.
OSSL_HPKE_export()β takes theOSSL_HPKE_CTXplus an application-supplied labellabelof sizelabellen, and produces a secretsecretof sizesecretlen. Important sequencing: the sender must first callOSSL_HPKE_encap(), and the receiver must callOSSL_HPKE_decap(), to derive the same shared secret. β¨
Multiple calls to OSSL_HPKE_export() with the same inputs will produce the same secret β no surprises, no chaos, just consistency. π
And if you need to produce a shared secret but don't actually wanna do any HPKE encryption, OSSL_HPKE_AEAD_ID_EXPORTONLY can be used as the OSSL_HPKE_SUITE aead_id that you pass to OSSL_HPKE_CTX_new(). Bestie really said "just the secret, hold the encryption." π
π Sender-authenticated HPKE Modes π
HPKE defines modes that support KEM-based sender-authentication: OSSL_HPKE_MODE_AUTH and OSSL_HPKE_MODE_PSKAUTH. The way this works is by binding the sender's authentication private/public values right into the encapsulation and decapsulation operations. The key used for these modes must also use the same KEM as the overall exchange β no mix-and-matching. And OSSL_HPKE_keygen() can be used to generate the private value you need. π₯
OSSL_HPKE_CTX_set1_authpriv()β the sender's move. Sets the sender's privateprivEVP_PKEYkey into theOSSL_HPKE_CTXctxbefore callingOSSL_HPKE_encap().OSSL_HPKE_CTX_set1_authpub()β the receiver's move. Sets the sender's encoded pub keypubof sizepubleninto theOSSL_HPKE_CTXctxbefore callingOSSL_HPKE_decap().
π§ Pre-Shared Key HPKE modes π§
HPKE also defines a symmetric equivalent to the authentication above, using a pre-shared key (PSK) and a PSK identifier. PSKs can be used with the OSSL_HPKE_MODE_PSK and OSSL_HPKE_MODE_PSKAUTH modes. Symmetry? We love her. π«Ά
OSSL_HPKE_CTX_set1_psk()β sets the PSK identifierpskidstring and the PSK bufferpskof sizepskleninto thectx. If required, this must be called beforeOSSL_HPKE_encap()orOSSL_HPKE_decap(). And per RFC9180: if required, bothpskandpskidmust be set to non-NULLvalues. Since PSKs are symmetric, the same calls must happen on both the sender and receiver sides β no skipping, fr fr. β
π Deterministic key generation for senders π
Normally, the sender's ephemeral private key is generated randomly inside OSSL_HPKE_encap() and stays secret. That's the default, and it's a good default. π
OSSL_HPKE_CTX_set1_ikme()β lets you override that behaviour by setting a deterministic input key materialikmof sizeikmleninto theOSSL_HPKE_CTXctx. If required, it can optionally be called beforeOSSL_HPKE_encap(). Andikmlenshould be greater than or equal toOSSL_HPKE_get_recommended_ikmelen().
β οΈ Okay listen, it's generally undesirable to use OSSL_HPKE_CTX_set1_ikme(), because it exposes the relevant secret to the application instead of keeping it safe inside the library, and it's way more likely to result in predictable values or values that leak. Proceed with caution, bestie. π
π¦ Re-sequencing π¦
Some protocols have to deal with packet loss while still being able to decrypt arriving packets later. Rude of the network, but okay. We provide a way to set the increment used for the nonce for the next subsequent call to OSSL_HPKE_open() β but not to OSSL_HPKE_seal(), and we'll explain why below. π
The OSSL_HPKE_CTX_set_seq() API can be used for this, with the seq parameter value resetting the internal nonce increment to be used for the next call.
So here's how the counting works: a baseline nonce value is established based on the encapsulation or decapsulation operation, and then it gets incremented by 1 for each call to seal or open. In other words, the first seq increment defaults to zero. β¨
If you need to figure out how many calls to seal or open have happened, use the OSSL_HPKE_CTX_get_seq() API to retrieve the increment (in the seq output) that will be used in the next call to seal or open. That would return 0 before the first call a sender made to OSSL_HPKE_seal(), and 1 after that first call. πΈ
π Big scary warning: reuse of the same nonce and key with different plaintexts would be very dangerous and could lead to loss of confidentiality and integrity. That's why we only support application control overseqfor decryption (i.e.OSSL_HPKE_open()) operations. Periodt.
And for compatibility with other implementations, these seq increments are represented as uint64_t.
β Protocol Convenience Functions β
Extra convenience APIs that let the caller peek at internal details of local HPKE support and/or algorithms β things like parameter lengths. So handy. π
OSSL_HPKE_suite_check()β checks if a specificOSSL_HPKE_SUITEsuiteis supported locally.OSSL_HPKE_get_ciphertext_size()β helps with memory allocation: tells the caller by how much the ciphertext will be longer than a plaintext of lengthclearlen. (AEAD algorithms add a data integrity tag, so there's a small amount of ciphertext expansion. Little tag tax. π )OSSL_HPKE_get_public_encap_size()β lets senders know how big the encapsulated public value will be for a given HPKE suite.OSSL_HPKE_get_recommended_ikmelen()β returns the recommended Input Key Material size (in bytes) for a given suite. You need this in cases where the same public value has to be regenerated by a sender before callingOSSL_HPKE_seal(). Andikmlenshould be at least this size.OSSL_HPKE_get_grease_value()β produces values of the appropriate length for a givensuite_invalue (or a random value ifsuite_inisNULL), so that a protocol using HPKE can send so-called GREASE (see RFC8701) values that are harder to distinguish from a real use of HPKE. The buffer sizes should be supplied on input. The outputencvalue will have an appropriate length forsuite_outand a random value, and thectoutput will be a random value. You can find the relevant sizes for buffers usingOSSL_HPKE_get_ciphertext_size()andOSSL_HPKE_get_public_encap_size(). Sneaky but valid. πOSSL_HPKE_str2suite()β maps inputstrstrings to anOSSL_HPKE_SUITEobject. The inputstrshould be a comma-separated string with a KEM, KDF and AEAD name in that order, for example"x25519,hkdf-sha256,aes-128-gcm". This is great for command line tools that accept string form names for HPKE codepoints. Valid (case-insensitive) names are:"p-256","p-384","p-521","x25519"and"x448"for KEM,"hkdf-sha256","hkdf-sha384"and"hkdf-sha512"for KDF, and"aes-128-gcm","aes-256-gcm","chacha20-poly1305"and"exporter"for AEAD. String variants of the numbers listed in "OSSL_HPKE_SUITE Identifiers" can also be used. π
π RETURN VALUES π
Okay so here's what you get back from everything, bestie:
OSSL_HPKE_CTX_new()returns anOSSL_HPKE_CTXpointer, orNULLon error. πOSSL_HPKE_get_ciphertext_size(),OSSL_HPKE_get_public_encap_size(), andOSSL_HPKE_get_recommended_ikmelen()all return asize_twith the relevant value, or zero on error.- All other functions return
1for success or zero for error. Simple as that. β¨
β¨ EXAMPLES β¨
okay bestie this is the part where it all clicks, I promise π Here's a lil minimal round-trip using HPKE. It's giving the whole lifecycle β keygen, encap, seal, decap, open, and then we say bye to everybody π«Ά
#include <stddef.h>
#include <string.h>
#include <openssl/hpke.h>
#include <openssl/evp.h>
/*
* this is big enough for this example, real code would need different
* handling
*/
#define LBUFSIZE 48
/* Do a round-trip, generating a key, encrypting and decrypting */
int main(int argc, char **argv)
{
int ok = 0;
int hpke_mode = OSSL_HPKE_MODE_BASE;
OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
OSSL_HPKE_CTX *sctx = NULL, *rctx = NULL;
EVP_PKEY *priv = NULL;
unsigned char pub[LBUFSIZE];
size_t publen = sizeof(pub);
unsigned char enc[LBUFSIZE];
size_t enclen = sizeof(enc);
unsigned char ct[LBUFSIZE];
size_t ctlen = sizeof(ct);
unsigned char clear[LBUFSIZE];
size_t clearlen = sizeof(clear);
const unsigned char *pt = "a message not in a bottle";
size_t ptlen = strlen((char *)pt);
const unsigned char *info = "Some info";
size_t infolen = strlen((char *)info);
unsigned char aad[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
size_t aadlen = sizeof(aad);
/*
* Generate receiver's key pair.
* The receiver gives this public key to the sender.
*/
if (OSSL_HPKE_keygen(hpke_suite, pub, &publen, &priv,
NULL, 0, NULL, NULL) != 1)
goto err;
/* sender's actions - encrypt data using the receivers public key */
if ((sctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
OSSL_HPKE_ROLE_SENDER,
NULL, NULL)) == NULL)
goto err;
if (OSSL_HPKE_encap(sctx, enc, &enclen, pub, publen, info, infolen) != 1)
goto err;
if (OSSL_HPKE_seal(sctx, ct, &ctlen, aad, aadlen, pt, ptlen) != 1)
goto err;
/* receiver's actions - decrypt data using the receivers private key */
if ((rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
OSSL_HPKE_ROLE_RECEIVER,
NULL, NULL)) == NULL)
goto err;
if (OSSL_HPKE_decap(rctx, enc, enclen, priv, info, infolen) != 1)
goto err;
if (OSSL_HPKE_open(rctx, clear, &clearlen, aad, aadlen, ct, ctlen) != 1)
goto err;
ok = 1;
err:
/* clean up */
printf(ok ? "All Good!\n" : "Error!\n");
OSSL_HPKE_CTX_free(rctx);
OSSL_HPKE_CTX_free(sctx);
EVP_PKEY_free(priv);
return 0;
}
like, notice the shape of it? π¦ Receiver makes a key pair and hands the public half over. Sender spins up a sender-role ctx, OSSL_HPKE_encaps (that's the encapsulated key going out with the message), then OSSL_HPKE_seals the actual data with the aad riding along. Receiver does the mirror image: OSSL_HPKE_decap with the private key, then OSSL_HPKE_open to get the plaintext back. Then everybody gets freed, no leaks, no chaos, main character energy π
π WARNINGS π
okay bestie listen. listen. This part is serious and I need you to actually read it, not just skim it while scrolling π
Note that the OSSL_HPKE_CTX_set_seq() API could be dangerous β if you use it with GCM that could lead to nonce-reuse, which is a known danger. So avoid that entirely, or be very very careful when using that API. Like, nonce-reuse is the villain of this whole story, fr fr. Just don't. Or if you truly must, be so careful it hurts π
Use of an IKM value for deterministic key generation (via OSSL_HPKE_CTX_set1_ikme() or OSSL_HPKE_keygen()) creates the potential for leaking keys (or IKM values). Only use that if it's really needed, and only if you understand how keys or IKM values could be abused. Deterministic = reproducible = someone else can reproduce it too, and that's not the vibe π₯
π SEE ALSO π
- The RFC9180 specification: https://datatracker.ietf.org/doc/rfc9180/
go read the spec, it's iconic, iykyk π
π HISTORY π
This functionality described here was added in OpenSSL 3.2. so it's a 3.2-and-up girlie β¨
Β©οΈ COPYRIGHT Β©οΈ
Copyright 2022-2026 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at <https://www.openssl.org/source/license.html>.
and that's the whole page bestie π tysm for reading all three parts, you ate that. this one came from OpenSSL 3.5.9, dated 2026-09-29, and the page is OSSL_HPKE_CTX_NEW(3ossl) β now go encrypt something responsibly, mwah πππΈ
π¦ where this page comes from
- fedora 43: openssl-devel 1:3.5.9-1.fc43 Β· Apache-2.0
- fedora 43: openssl-devel 1:3.5.9-1.fc43 Β· Apache-2.0
- fedora 44: openssl-devel 1:3.5.9-1.fc44 Β· Apache-2.0
- fedora 44: openssl-devel 1:3.5.9-1.fc44 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms