OSSL_HPKE_CTX_NEW(3SSL)

โœจ OSSL_HPKE_CTX_NEW(3SSL) โœจ

okay so this is the OpenSSL 3SSL manual page for OSSL_HPKE_CTX_NEW and bestie, we are going deep into Hybrid Public Key Encryption. it's giving cryptography but make it readable. part 1 of 3, so don't go anywhere ๐Ÿ’–


๐Ÿ’– NAME ๐Ÿ’–

okay so the whole squad on this page is: 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 โ€” and they're all Hybrid Public Key Encryption (HPKE) functions. periodt. ๐Ÿ’…


๐ŸŒธ SYNOPSIS ๐ŸŒธ

here's the actual C vibes, keep the formatting exact bestie, no cap:

        #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 ๐Ÿฆ‹

so these functions are literally the API for using the form of Hybrid Public Key Encryption (HPKE) that's defined in RFC9180. lowkey, understanding the HPKE specification is probably required before you start using these APIs. HPKE gets used by various other IETF specifications, including the TLS Encrypted Client Hello (ECH) specification and others. it's that girl.

HPKE is a standardised, highly flexible construct for encrypting "to" a public key that supports combinations of a key encapsulation method (KEM), a key derivation function (KDF) and an authenticated encryption with additional data (AEAD) algorithm, with optional sender authentication. yes bestie, it's a whole stack.

the sender and a receiver here will generally be using some application or protocol making use of HPKE. for example, with ECH, the sender will be a browser and the receiver will be a web server. main character energy for both tbh.

๐Ÿงš Data Structures ๐Ÿงš

OSSL_HPKE_SUITE is a structure that holds identifiers for the algorithms used for KEM, KDF and AEAD operations. it's basically the algorithm lineup.

OSSL_HPKE_CTX is a context that maintains internal state as HPKE operations are carried out. separate OSSL_HPKE_CTX objects must be used for the sender and receiver. attempting to use a single context for both will result in errors. so don't try to make one context do both jobs, it will not slay. ๐Ÿ’€

๐ŸŒˆ OSSL_HPKE_SUITE Identifiers ๐ŸŒˆ

the identifiers used by OSSL_HPKE_SUITE are:

the KEM identifier kem_id is one of the following:

  • 0x10 OSSL_HPKE_KEM_ID_P256
  • 0x11 OSSL_HPKE_KEM_ID_P384
  • 0x12 OSSL_HPKE_KEM_ID_P521
  • 0x20 OSSL_HPKE_KEM_ID_X25519
  • 0x21 OSSL_HPKE_KEM_ID_X448

the KDF identifier kdf_id is one of the following:

  • 0x01 OSSL_HPKE_KDF_ID_HKDF_SHA256
  • 0x02 OSSL_HPKE_KDF_ID_HKDF_SHA384
  • 0x03 OSSL_HPKE_KDF_ID_HKDF_SHA512

the AEAD identifier aead_id is one of the following:

  • 0x01 OSSL_HPKE_AEAD_ID_AES_GCM_128
  • 0x02 OSSL_HPKE_AEAD_ID_AES_GCM_256
  • 0x03 OSSL_HPKE_AEAD_ID_CHACHA_POLY1305
  • 0xFFFF OSSL_HPKE_AEAD_ID_EXPORTONLY
    • the last identifier above indicates that AEAD operations are not needed. OSSL_HPKE_export() can be used, but OSSL_HPKE_open() and OSSL_HPKE_seal() will return an error if called with a context using that AEAD identifier. so export-only means exactly what it says on the tin, bestie.

๐Ÿ’œ HPKE Modes ๐Ÿ’œ

HPKE supports the following variants of Authentication using a mode Identifier:

  • OSSL_HPKE_MODE_BASE, 0x00 โ€” authentication is not used.
  • 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 combination of OSSL_HPKE_MODE_PSK and OSSL_HPKE_MODE_AUTH. both the PSK and the senders authentication public/private must be supplied before the encapsulation/decapsulation operation will work.

for further information related to authentication see "Pre-Shared Key HPKE modes" and "Sender-authenticated HPKE Modes". iykyk, those sections are coming up later.

๐ŸŽ€ HPKE Roles ๐ŸŽ€

HPKE contexts have a role โ€” either sender or receiver. this is used to control which functions can be called and so that senders do not reuse a key and nonce with different plaintexts. safety first, always.

OSSL_HPKE_CTX_free(), OSSL_HPKE_export(), OSSL_HPKE_CTX_set1_psk(), and OSSL_HPKE_CTX_get_seq() can be called regardless of role. those are the universal girlies.

  • OSSL_HPKE_ROLE_SENDER, 0 โ€” an OSSL_HPKE_CTX with this role can be used with OSSL_HPKE_encap(), OSSL_HPKE_seal(), OSSL_HPKE_CTX_set1_ikme() and OSSL_HPKE_CTX_set1_authpriv().
  • OSSL_HPKE_ROLE_RECEIVER, 1 โ€” an OSSL_HPKE_CTX with this role can be used with OSSL_HPKE_decap(), OSSL_HPKE_open(), OSSL_HPKE_CTX_set1_authpub() and OSSL_HPKE_CTX_set_seq().

calling a function with an incorrect role set on OSSL_HPKE_CTX will result in an error. so check your role before you wreck your role, bestie. ๐Ÿ™ˆ

โญ Parameter Size Limits โญ

in order to improve interoperability, 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 for HPKE functions below. the constant OSSL_HPKE_MAX_PARMLEN is defined as the limit of this value. (we chose 66 octets so that we can validate all the test vectors present in RFC9180, Appendix A.) so yes, 66 is a very intentional number.

in accordance with RFC9180, section 9.5, we define a constant OSSL_HPKE_MIN_PSKLEN with a value of 32 for the minimum length of a pre-shared key, passed in psklen. don't skimp on your PSK length, it's giving weak if you do.

while RFC9180 also RECOMMENDS a 64 octet limit for the infolen parameter, that is not sufficient 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 the infolen parameter. ECH said "we need more room" and OpenSSL said bet.

๐Ÿ’– Context Construct/Free ๐Ÿ’–

OSSL_HPKE_CTX_new() creates a OSSL_HPKE_CTX context object used for subsequent HPKE operations, given a mode (see "HPKE Modes"), suite (see "OSSL_HPKE_SUITE Identifiers") and a role (see "HPKE Roles"). the libctx and propq are used when fetching algorithms from providers and may be set to NULL. so pass NULL if you're not doing anything fancy.

OSSL_HPKE_CTX_free() frees the ctx OSSL_HPKE_CTX that was created previously by a call to OSSL_HPKE_CTX_new(). if the argument to OSSL_HPKE_CTX_free() is NULL, nothing is done. it's the "no thoughts, head empty" of free functions. ๐Ÿ’…

๐ŸŒธ Sender APIs ๐ŸŒธ

a sender's goal is to use HPKE to encrypt using a public key, via use of a KEM, then a KDF and finally an AEAD. the first step is to encapsulate (using OSSL_HPKE_encap()) the sender's public value using the recipient's public key, (pub) and to internally derive secrets. this produces the encapsulated public value (enc) to be sent to the recipient in whatever protocol is using HPKE. having done the encapsulation step, the sender can then make one or more calls to OSSL_HPKE_seal() to encrypt plaintexts using the secret stored within ctx. it's a whole pipeline and yes, it eats.

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 contain the maximum size of the enc buffer, and returns the output size. an error will occur if the input enclen is smaller than the value returned from OSSL_HPKE_get_public_encap_size(). info may be used to bind other protocol or application artefacts such as identifiers. generally, the encapsulated public value enc corresponds to a single-use ephemeral private value created as part of the encapsulation process. only a single call to OSSL_HPKE_encap() is allowed for a given OSSL_HPKE_CTX. no take-backs, no double dipping.

OSSL_HPKE_seal() takes the OSSL_HPKE_CTX context ctx, the plaintext buffer pt of size ptlen and optional additional authenticated data buffer aad of size aadlen, and returns the ciphertext ct of size ctlen. on input ctlen should contain the maximum size of the ct buffer, and returns the output size. an error will occur if the input ctlen is smaller than the value returned from OSSL_HPKE_get_public_encap_size().

OSSL_HPKE_encap() must be called before the OSSL_HPKE_seal(). OSSL_HPKE_seal() may be called multiple times, with an internal "nonce" being incremented by one after each call. so like, seal as many times as you need, the nonce is keeping count, bestie ๐Ÿ’…

๐Ÿ’– Recipient APIs ๐Ÿ’–

okay so recipients?? that's the receiving side, and real talk: their private value needs to be typically less ephemeral than a sender's, because the whole point is that the public value gets distributed to potential senders through whatever protocol is using HPKE. So the vibe is: recipients generally generate a key pair first, and they have to manage that private key value using standard mechanisms that live outside the scope of this API. ๐Ÿซถ Good news tho โ€” private keys use normal EVP_PKEY(3) pointers, so all your normal private key management mechanisms work on the relevant values. no new tricks required, periodt. ๐Ÿ’…

So to actually enable encapsulation, the recipient needs to get their public value to the sender. And here's the thing: there is no generic HPKE format defined for that ๐Ÿšซ โ€” the formatting is meant to be defined by whatever application/protocols are using HPKE. ECH, for example, defines an ECHConfig data structure that smooshes the public value together with other ECH data items. So you have to use normal library functions to extract the public value in the required format, based on the EVP_PKEY(3) for the private value. iykyk. โœจ

OSSL_HPKE_keygen() is the one that lets recipients generate a key pair based on the HPKE suite they're gonna use. Here's what it hands you:

  • an EVP_PKEY(3) pointer for the private value priv ๐Ÿ‘‘
  • an encoded public key pub of size publen

On input, publen should contain the max size of the pub buffer, and it returns the output size. If the input publen is too small, error โ€” no cap. The libctx and propq are used when fetching algorithms from providers and may be set to NULL. ๐Ÿ“

And bestie, the HPKE spec also defines a deterministic key generation scheme where the private value is derived from initial keying material (IKM) โ€” so OSSL_HPKE_keygen() has an option to use that scheme too, via the ikm parameter of size ikmlen. If either ikm is NULL or ikmlen is zero, then you just get a randomly generated key for the relevant suite. ๐ŸŒˆ If you do need it, ikmlen should be greater than or equal to OSSL_HPKE_get_recommended_ikmelen().

OSSL_HPKE_decap() takes as input the sender's encapsulated public value produced by OSSL_HPKE_encap() (that's enc) plus the recipient's EVP_PKEY(3) pointer (that's prov), and then re-generates the internal secret that the sender derived. As before, an optional info parameter lets you bind that derived secret to other application/protocol artefacts. โš ๏ธ And listen: only a single call to OSSL_HPKE_decap() is allowed for a given OSSL_HPKE_CTX. one and done, no reruns. ๐Ÿ˜ญ

OSSL_HPKE_open() is what the recipient uses to decrypt the ciphertext ct of size ctlen using the ctx and additional authenticated data aad of size aadlen, producing the plaintext pt of size ptlen. ๐ŸŽ€

  • On input, ptlen should contain the maximum size of the pt buffer, and it returns the output size.
  • A pt buffer that's the same size as the ct buffer will totally suffice โ€” generally the plaintext output is a little smaller than the ciphertext input.
  • If the input ptlen is too small โ†’ error.

OSSL_HPKE_open() may be called multiple times, but โ€” just like with OSSL_HPKE_seal() โ€” there's an internally incrementing nonce value, so ciphertexts need to be presented in the same order as they were used by OSSL_HPKE_seal(). ๐Ÿฆ‹ If you need to process multiple ciphertexts in a different order, go read "Re-sequencing".


๐ŸŒธ Exporting Secrets ๐ŸŒธ

HPKE defines a way to produce exported secrets for the application to use. slay. โœจ

OSSL_HPKE_export() takes as input the OSSL_HPKE_CTX, plus an application-supplied label of size labellen, and produces a secret secret of size secretlen. The sender must first call OSSL_HPKE_encap(), and the receiver must call OSSL_HPKE_decap(), in order to derive that same shared secret. ๐Ÿค

Multiple calls to OSSL_HPKE_export() with the same inputs will produce the same secret โ€” deterministic queen. ๐Ÿ’œ And OSSL_HPKE_AEAD_ID_EXPORTONLY may be used as the OSSL_HPKE_SUITE aead_id that gets passed to OSSL_HPKE_CTX_new() if you need a shared secret but genuinely do not wish to perform HPKE encryption. it's giving "just the secret, thanks". ๐Ÿซถ

๐Ÿ”ฅ 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 into the encapsulation and decapsulation operations. ๐ŸŽฏ The key used for these modes must also use the same KEM as the overall exchange. OSSL_HPKE_keygen() can be used to generate the private value you need.

  • OSSL_HPKE_CTX_set1_authpriv() โ€” the sender can use this to set the sender's private priv EVP_PKEY key into the OSSL_HPKE_CTX ctx before calling OSSL_HPKE_encap(). ๐Ÿ’…
  • OSSL_HPKE_CTX_set1_authpub() โ€” the receiver can use this to set the sender's encoded pub key, of size publen, into the OSSL_HPKE_CTX ctx before calling OSSL_HPKE_decap(). โœจ

๐ŸŽ€ Pre-Shared Key HPKE modes ๐ŸŽ€

HPKE also defines a symmetric equivalent to the authentication described above, using a pre-shared key (PSK) plus a PSK identifier. PSKs can be used with the OSSL_HPKE_MODE_PSK and OSSL_HPKE_MODE_PSKAUTH modes. ๐Ÿ”‘

OSSL_HPKE_CTX_set1_psk() sets the PSK identifier pskid string, and the PSK buffer psk of size psklen, into the ctx. If you need it, this must be called before OSSL_HPKE_encap() or OSSL_HPKE_decap(). โš ๏ธ As per RFC9180, if required, both psk and pskid must be set to non-NULL values. And because PSKs are symmetric, the same calls must happen on both the sender and receiver sides. no side-eying, y'all do the same thing. ๐Ÿ˜ค

๐Ÿฆ‹ Deterministic key generation for senders ๐Ÿฆ‹

Normally the sender's ephemeral private key is generated randomly inside OSSL_HPKE_encap() and stays secret. ๐Ÿคซ OSSL_HPKE_CTX_set1_ikme() lets you override that behaviour by setting a deterministic input key material ikm of size ikmlen into the OSSL_HPKE_CTX ctx. If required, OSSL_HPKE_CTX_set1_ikme() can optionally be called before OSSL_HPKE_encap(). And ikmlen should be greater than or equal to OSSL_HPKE_get_recommended_ikmelen(). ๐Ÿ“

big warning bestie ๐Ÿšจ It is generally undesirable to use OSSL_HPKE_CTX_set1_ikme(), since it exposes the relevant secret to the application rather than preserving it within the library, and it's more likely to result in the use of predictable values, or values that leak. ๐Ÿ’€ Justโ€ฆ don't, unless you truly have to.

๐Ÿ’œ Re-sequencing ๐Ÿ’œ

Some protocols have to deal with packet loss while still being able to decrypt arriving packets later. ๐Ÿ™ˆ So we provide a way to set the increment used for the nonce on the next subsequent call to OSSL_HPKE_open() โ€” but not to OSSL_HPKE_seal(), and the original explains why below. The OSSL_HPKE_CTX_set_seq() API does exactly this, with the seq parameter value resetting the internal nonce increment to be used for the next call. ๐Ÿ”

A baseline nonce value is established based on the encapsulation or decapsulation operation, and is then incremented by 1 for each call to seal or open. (In other words, the first seq increment defaults to zero.) โญ

If a caller needs to figure out how many calls to seal or open have been made, the OSSL_HPKE_CTX_get_seq() API can retrieve the increment (in the seq output) that will be used in the next call to seal or open. So that would return 0 before the first call a sender made to OSSL_HPKE_seal(), and 1 after that first call. ๐Ÿงฎ

okay this part is SO important: reuse of the same nonce and key with different plaintexts would be very dangerous and could lead to loss of confidentiality and integrity. ๐Ÿ˜ฑ We therefore only support application control over seq for decryption (i.e. OSSL_HPKE_open()) operations.

For compatibility with other implementations, these seq increments are represented as uint64_t. ๐Ÿ’…

โœจ Protocol Convenience Functions โœจ

Additional convenience APIs let the caller access internal details of local HPKE support and/or algorithms, like parameter lengths. it's giving helpful. ๐Ÿซถ

  • OSSL_HPKE_suite_check() โ€” checks if a specific OSSL_HPKE_SUITE suite is supported locally. โœ…
  • OSSL_HPKE_get_ciphertext_size() โ€” to assist with memory allocation, this lets the caller know by how much the ciphertext will be longer than a plaintext of length clearlen. (AEAD algorithms add a data integrity tag, so there's a small amount of ciphertext expansion.) ๐Ÿ“ˆ
  • 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. This is needed in cases where the same public value needs to be regenerated by a sender before calling OSSL_HPKE_seal(). ikmlen should be at least this size. ๐ŸŒŸ
  • OSSL_HPKE_get_grease_value() โ€” produces values of the appropriate length for a given suite_in value (or a random value if suite_in is NULL) 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. Buffer sizes should be supplied on input. The output enc value will have an appropriate length for suite_out and a random value, and the ct output will be a random value. You can find the relevant sizes for buffers using OSSL_HPKE_get_ciphertext_size() and OSSL_HPKE_get_public_encap_size(). ๐Ÿงˆ
  • OSSL_HPKE_str2suite() โ€” maps input str strings to an OSSL_HPKE_SUITE object. The input str should be a comma-separated string with a KEM, KDF and AEAD name, in that order, for example "x25519,hkdf-sha256,aes-128-gcm". This can be used by 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 ๐ŸŒˆ

  • OSSL_HPKE_CTX_new() returns an OSSL_HPKE_CTX pointer, or NULL on error. ๐Ÿ’€
  • OSSL_HPKE_get_ciphertext_size(), OSSL_HPKE_get_public_encap_size(), OSSL_HPKE_get_recommended_ikmelen() โ€” all three return a size_t with the relevant value, or zero on error. ๐Ÿ“
  • All other functions return 1 for success or zero for error. โœ…

โœจ EXAMPLES โœจ

okay bestie, here's a lil round-trip demo โ€” the bare minimum HPKE moment, sender encrypts, receiver decrypts, everyone goes home happy ๐ŸŽ€

#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;
}

so basically: keygen on the receiver side ๐Ÿ‘‘, receiver hands over the public key, sender OSSL_HPKE_CTX_new()s it up as OSSL_HPKE_ROLE_SENDER and does the OSSL_HPKE_encap() + OSSL_HPKE_seal() combo, then the receiver shows up with OSSL_HPKE_ROLE_RECEIVER and does OSSL_HPKE_decap() + OSSL_HPKE_open(). every single step checks != 1 and bails to err: โ€” no skipping the vibe check. then cleanup, printf, done. it literally prints All Good! or Error! and dips. iconic behavior honestly ๐Ÿ’…


๐ŸŒธ WARNINGS ๐ŸŒธ

okay this is the part where we get serious, bestie, pay attention ๐Ÿšจ

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 not a cute lil oopsie, it's a cryptographic catastrophe. avoid OSSL_HPKE_CTX_set_seq() or handle it like it's a lit candle near a curtain, fr fr ๐Ÿ˜ญ

and second one:

Using 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 really needed and if you understand how keys or IKM values could be abused.

so like, deterministic keygen = same inputs, same keys, forever and ever. great for reproducibility, terrible if that input ever gets out โ€” then your keys leak too ๐Ÿ’€ only do it if you genuinely need it and you actually understand the ways keys or IKM values could get abused. otherwise, don't. periodt.


๐Ÿ” SEE ALSO ๐Ÿ”

that's the source material, the blueprint, the original main character. go read it if you wanna know what's actually happening under the hood ๐Ÿงš


๐Ÿ“œ HISTORY ๐Ÿ“œ

Everything described here slid into the chat in OpenSSL 3.2 โœจ so if you're on something older, bestie, it's giving "not found". upgrade era.


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 a wrap on OSSL_HPKE_CTX_NEW(3SSL) bestie ๐Ÿซถ this lil page came from OpenSSL 4.0.3, dated 2026-09-29 โ€” go forth and encapsulate responsibly, no nonce-reuse, no leaking IKMs, and remember: encrypting a message is literally just putting your secrets in a bottle with a really good cork. mwah ๐Ÿ’–โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from