Add new EVP_KEM API

This adds a new EVP_KEM type which provides a more ergonomic API for KEM
encapsulate and decapsulate operations, and implements them for
ML-KEM-768 and 1024.

Bug: 449751916
Change-Id: Ie9b3a68a6e9e835a5055810cd23401716a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/91267
Commit-Queue: Lily Chen <chlily@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/build.json b/build.json
index f909e33..88d962c 100644
--- a/build.json
+++ b/build.json
@@ -259,6 +259,7 @@
             "crypto/evp/evp.cc",
             "crypto/evp/evp_asn1.cc",
             "crypto/evp/evp_ctx.cc",
+            "crypto/evp/evp_kem.cc",
             "crypto/evp/p_dh.cc",
             "crypto/evp/p_dsa.cc",
             "crypto/evp/p_ec.cc",
diff --git a/crypto/err/evp.errordata b/crypto/err/evp.errordata
index daf989a..b929efa 100644
--- a/crypto/err/evp.errordata
+++ b/crypto/err/evp.errordata
@@ -11,6 +11,7 @@
 EVP,106,EXPECTING_A_EC_KEY
 EVP,109,ILLEGAL_OR_UNSUPPORTED_PADDING_MODE
 EVP,137,INVALID_BUFFER_SIZE
+EVP,141,INVALID_CIPHERTEXT_LENGTH
 EVP,110,INVALID_DIGEST_LENGTH
 EVP,111,INVALID_DIGEST_TYPE
 EVP,112,INVALID_KEYBITS
@@ -20,6 +21,7 @@
 EVP,133,INVALID_PARAMETERS
 EVP,134,INVALID_PEER_KEY
 EVP,116,INVALID_PSS_SALTLEN
+EVP,142,INVALID_SECRET_LENGTH
 EVP,131,INVALID_SIGNATURE
 EVP,117,KEYS_NOT_SET
 EVP,132,MEMORY_LIMIT_EXCEEDED
diff --git a/crypto/evp/evp_kem.cc b/crypto/evp/evp_kem.cc
new file mode 100644
index 0000000..a74ec8d
--- /dev/null
+++ b/crypto/evp/evp_kem.cc
@@ -0,0 +1,78 @@
+// Copyright 2026 The BoringSSL Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <openssl/evp.h>
+
+#include <openssl/err.h>
+
+#include "internal.h"
+
+using namespace bssl;
+
+// Checks whether KEM function invocation is valid.
+// `ciphertext_len` may be nullptr if it is not required to match.
+// `secret_len` must always match.
+static bool check_kem_invocation(const EVP_KEM *kem,
+                                 const size_t *ciphertext_len,
+                                 size_t secret_len,
+                                 const bssl::EvpPkey *pkey_impl) {
+  if (pkey_impl == nullptr || pkey_impl->pkey == nullptr ||
+      pkey_impl->ameth == nullptr) {
+    OPENSSL_PUT_ERROR(EVP, ERR_R_PASSED_NULL_PARAMETER);
+    return false;
+  }
+  if (kem->pkey_id != EVP_PKEY_id(pkey_impl)) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_ALGORITHM);
+    return false;
+  }
+  if (ciphertext_len && *ciphertext_len != kem->ciphertext_len) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_CIPHERTEXT_LENGTH);
+    return false;
+  }
+  if (secret_len != kem->secret_len) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SECRET_LENGTH);
+    return false;
+  }
+  return true;
+}
+
+size_t EVP_KEM_ciphertext_len(const EVP_KEM *kem) {
+  return kem->ciphertext_len;
+}
+
+size_t EVP_KEM_secret_len(const EVP_KEM *kem) {
+  return kem->secret_len;
+}
+
+int EVP_KEM_encap(const EVP_KEM *kem, uint8_t *out_ciphertext,
+                  size_t ciphertext_len, uint8_t *out_secret, size_t secret_len,
+                  const EVP_PKEY *peer_key) {
+  auto *pkey_impl = FromOpaque(peer_key);
+  if (!check_kem_invocation(kem, &ciphertext_len, secret_len, pkey_impl)) {
+    return 0;
+  }
+  return kem->encap(out_ciphertext, ciphertext_len, out_secret, secret_len,
+                    pkey_impl);
+}
+
+int EVP_KEM_decap(const EVP_KEM *kem, uint8_t *out_secret, size_t secret_len,
+                  const uint8_t *ciphertext, size_t ciphertext_len,
+                  const EVP_PKEY *key) {
+  auto *pkey_impl = FromOpaque(key);
+  if (!check_kem_invocation(kem, nullptr, secret_len, pkey_impl)) {
+    return 0;
+  }
+  return kem->decap(out_secret, secret_len, ciphertext, ciphertext_len,
+                    pkey_impl);
+}
diff --git a/crypto/evp/evp_test.cc b/crypto/evp/evp_test.cc
index 7fa1d95..7c4546a 100644
--- a/crypto/evp/evp_test.cc
+++ b/crypto/evp/evp_test.cc
@@ -90,27 +90,46 @@
 
 struct AlgorithmInfo {
   const EVP_PKEY_ALG *alg;
+  const EVP_KEM *kem;
   int pkey_id;
   bool is_default;
 };
 
 const std::map<std::string, AlgorithmInfo> kAllAlgorithms = {
-    {"RSA", {EVP_pkey_rsa(), EVP_PKEY_RSA, true}},
-    {"RSA-PSS-SHA-256", {EVP_pkey_rsa_pss_sha256(), EVP_PKEY_RSA_PSS, false}},
-    {"RSA-PSS-SHA-384", {EVP_pkey_rsa_pss_sha384(), EVP_PKEY_RSA_PSS, false}},
-    {"RSA-PSS-SHA-512", {EVP_pkey_rsa_pss_sha512(), EVP_PKEY_RSA_PSS, false}},
-    {"EC-P-224", {EVP_pkey_ec_p224(), EVP_PKEY_EC, true}},
-    {"EC-P-256", {EVP_pkey_ec_p256(), EVP_PKEY_EC, true}},
-    {"EC-P-384", {EVP_pkey_ec_p384(), EVP_PKEY_EC, true}},
-    {"EC-P-521", {EVP_pkey_ec_p521(), EVP_PKEY_EC, true}},
-    {"X25519", {EVP_pkey_x25519(), EVP_PKEY_X25519, true}},
-    {"Ed25519", {EVP_pkey_ed25519(), EVP_PKEY_ED25519, true}},
-    {"DSA", {EVP_pkey_dsa(), EVP_PKEY_DSA, true}},
-    {"ML-DSA-44", {EVP_pkey_ml_dsa_44(), EVP_PKEY_ML_DSA_44, true}},
-    {"ML-DSA-65", {EVP_pkey_ml_dsa_65(), EVP_PKEY_ML_DSA_65, true}},
-    {"ML-DSA-87", {EVP_pkey_ml_dsa_87(), EVP_PKEY_ML_DSA_87, true}},
-    {"ML-KEM-768", {EVP_pkey_ml_kem_768(), EVP_PKEY_ML_KEM_768, false}},
-    {"ML-KEM-1024", {EVP_pkey_ml_kem_1024(), EVP_PKEY_ML_KEM_1024, false}},
+    {"RSA",
+     {EVP_pkey_rsa(),
+      /*kem=*/nullptr, EVP_PKEY_RSA, true}},
+
+    {"RSA-PSS-SHA-256",
+     {EVP_pkey_rsa_pss_sha256(), /*kem=*/nullptr, EVP_PKEY_RSA_PSS, false}},
+    {"RSA-PSS-SHA-384",
+     {EVP_pkey_rsa_pss_sha384(), /*kem=*/nullptr, EVP_PKEY_RSA_PSS, false}},
+    {"RSA-PSS-SHA-512",
+     {EVP_pkey_rsa_pss_sha512(), /*kem=*/nullptr, EVP_PKEY_RSA_PSS, false}},
+
+    {"EC-P-224", {EVP_pkey_ec_p224(), /*kem=*/nullptr, EVP_PKEY_EC, true}},
+    {"EC-P-256", {EVP_pkey_ec_p256(), /*kem=*/nullptr, EVP_PKEY_EC, true}},
+    {"EC-P-384", {EVP_pkey_ec_p384(), /*kem=*/nullptr, EVP_PKEY_EC, true}},
+    {"EC-P-521", {EVP_pkey_ec_p521(), /*kem=*/nullptr, EVP_PKEY_EC, true}},
+
+    {"X25519", {EVP_pkey_x25519(), /*kem=*/nullptr, EVP_PKEY_X25519, true}},
+
+    {"Ed25519", {EVP_pkey_ed25519(), /*kem=*/nullptr, EVP_PKEY_ED25519, true}},
+
+    {"DSA", {EVP_pkey_dsa(), /*kem=*/nullptr, EVP_PKEY_DSA, true}},
+
+    {"ML-DSA-44",
+     {EVP_pkey_ml_dsa_44(), /*kem=*/nullptr, EVP_PKEY_ML_DSA_44, true}},
+    {"ML-DSA-65",
+     {EVP_pkey_ml_dsa_65(), /*kem=*/nullptr, EVP_PKEY_ML_DSA_65, true}},
+    {"ML-DSA-87",
+     {EVP_pkey_ml_dsa_87(), /*kem=*/nullptr, EVP_PKEY_ML_DSA_87, true}},
+
+    {"ML-KEM-768",
+     {EVP_pkey_ml_kem_768(), EVP_kem_ml_kem_768(), EVP_PKEY_ML_KEM_768, false}},
+    {"ML-KEM-1024",
+     {EVP_pkey_ml_kem_1024(), EVP_kem_ml_kem_1024(), EVP_PKEY_ML_KEM_1024,
+      false}},
 };
 
 using KeyMap = std::map<std::string, bssl::UniquePtr<EVP_PKEY>>;
@@ -704,7 +723,7 @@
     return false;
   }
   const AlgorithmInfo &alg_info = it->second;
-  if (alg_info.alg == nullptr) {
+  if (alg_info.alg == nullptr || alg_info.kem == nullptr) {
     ADD_FAILURE() << "Method not defined: " << alg_name;
     return false;
   }
@@ -834,6 +853,78 @@
         ctx.get(), decapsulated_secret.data(), &secret_size, ciphertext.data(),
         ciphertext.size()));
   }
+
+  // Repeat everything the EVP_KEM way, which is simpler.
+  reset_test_state();
+
+  EXPECT_EQ(EVP_KEM_ciphertext_len(alg_info.kem), expected_ciphertext_len);
+  EXPECT_EQ(EVP_KEM_secret_len(alg_info.kem), expected_secret_len);
+
+  if (encapsulate) {
+    ciphertext.resize(ciphertext_size);
+
+    // Passing the wrong sizes fails (even if larger than required).
+    resize_output_buffers(expected_ciphertext_len - 1, expected_secret_len);
+    EXPECT_EQ(EVP_KEM_encap(alg_info.kem, ciphertext.data(), ciphertext.size(),
+                            secret.data(), secret.size(), pkey),
+              0);
+    EXPECT_TRUE(ErrorEquals(ERR_get_error(), ERR_LIB_EVP,
+                            EVP_R_INVALID_CIPHERTEXT_LENGTH));
+    ERR_clear_error();
+    resize_output_buffers(expected_ciphertext_len + 1, expected_secret_len);
+    EXPECT_EQ(EVP_KEM_encap(alg_info.kem, ciphertext.data(), ciphertext.size(),
+                            secret.data(), secret.size(), pkey),
+              0);
+    EXPECT_TRUE(ErrorEquals(ERR_get_error(), ERR_LIB_EVP,
+                            EVP_R_INVALID_CIPHERTEXT_LENGTH));
+    ERR_clear_error();
+    resize_output_buffers(expected_ciphertext_len, expected_secret_len - 1);
+    EXPECT_EQ(EVP_KEM_encap(alg_info.kem, ciphertext.data(), ciphertext.size(),
+                            secret.data(), secret.size(), pkey),
+              0);
+    EXPECT_TRUE(
+        ErrorEquals(ERR_get_error(), ERR_LIB_EVP, EVP_R_INVALID_SECRET_LENGTH));
+    ERR_clear_error();
+    resize_output_buffers(expected_ciphertext_len, expected_secret_len + 1);
+    EXPECT_EQ(EVP_KEM_encap(alg_info.kem, ciphertext.data(), ciphertext.size(),
+                            secret.data(), secret.size(), pkey),
+              0);
+    EXPECT_TRUE(
+        ErrorEquals(ERR_get_error(), ERR_LIB_EVP, EVP_R_INVALID_SECRET_LENGTH));
+    ERR_clear_error();
+
+    // Only the correct sizes are accepted.
+    resize_output_buffers(expected_ciphertext_len, expected_secret_len);
+    EXPECT_EQ(EVP_KEM_encap(alg_info.kem, ciphertext.data(), ciphertext.size(),
+                            secret.data(), secret.size(), pkey),
+              1);
+  }
+
+  if (decapsulate) {
+    // Passing the wrong sizes fails (even if larger than required).
+    resize_output_buffers(std::nullopt, expected_secret_len - 1, true);
+    EXPECT_EQ(EVP_KEM_decap(alg_info.kem, decapsulated_secret.data(),
+                            decapsulated_secret.size(), ciphertext.data(),
+                            ciphertext.size(), pkey),
+              0);
+    EXPECT_TRUE(
+        ErrorEquals(ERR_get_error(), ERR_LIB_EVP, EVP_R_INVALID_SECRET_LENGTH));
+    ERR_clear_error();
+    resize_output_buffers(std::nullopt, expected_secret_len + 1, true);
+    EXPECT_EQ(EVP_KEM_decap(alg_info.kem, decapsulated_secret.data(),
+                            decapsulated_secret.size(), ciphertext.data(),
+                            ciphertext.size(), pkey),
+              0);
+    EXPECT_TRUE(
+        ErrorEquals(ERR_get_error(), ERR_LIB_EVP, EVP_R_INVALID_SECRET_LENGTH));
+    ERR_clear_error();
+
+    resize_output_buffers(std::nullopt, expected_secret_len, true);
+    check_decapsulate_result(EVP_KEM_decap(
+        alg_info.kem, decapsulated_secret.data(), decapsulated_secret.size(),
+        ciphertext.data(), ciphertext.size(), pkey));
+  }
+
   return true;
 }
 
diff --git a/crypto/evp/internal.h b/crypto/evp/internal.h
index d634585..4e451a2 100644
--- a/crypto/evp/internal.h
+++ b/crypto/evp/internal.h
@@ -294,6 +294,27 @@
   int (*ctrl)(EvpPkeyCtx *ctx, int type, int p1, void *p2);
 } /* EVP_PKEY_CTX_METHOD */;
 
+BSSL_NAMESPACE_END
+
+// TODO(chlily): Make compatible with `EVP_HPKE_KEM`.
+struct evp_kem_st {
+  // Identifies the type of EVP_PKEYs compatible with this KEM.
+  int pkey_id;
+
+  // Constant lengths of ciphertexts and secrets produced/consumed by this KEM.
+  size_t ciphertext_len;
+  size_t secret_len;
+
+  int (*encap)(uint8_t *out_ciphertext, size_t ciphertext_len,
+               uint8_t *out_secret, size_t secret_len,
+               const EVP_PKEY *peer_key);
+  int (*decap)(uint8_t *out_secret, size_t secret_len,
+               const uint8_t *ciphertext, size_t ciphertext_len,
+               const EVP_PKEY *key);
+} /* EVP_KEM */;
+
+BSSL_NAMESPACE_BEGIN
+
 // evp_pkey_ec_no_curve returns an internal curveless EC |EVP_PKEY_ALG|. This
 // cannot be used to parse anything and is only useful for key generation.
 const EVP_PKEY_ALG *evp_pkey_ec_no_curve();
diff --git a/crypto/evp/p_mlkem.cc b/crypto/evp/p_mlkem.cc
index 34b4b7b..eefb7e0 100644
--- a/crypto/evp/p_mlkem.cc
+++ b/crypto/evp/p_mlkem.cc
@@ -350,6 +350,22 @@
     return 1;
   }
 
+  static int KemEncap(uint8_t *out_ciphertext, size_t ciphertext_len,
+                      uint8_t *out_secret, size_t secret_len,
+                      const EVP_PKEY *peer_key) {
+    const auto *peer_pubkey = GetKeyData(FromOpaque(peer_key))->GetPublicKey();
+    if (ciphertext_len != Traits::kCiphertextBytes) {
+      OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_CIPHERTEXT_LENGTH);
+      return 0;
+    }
+    if (secret_len != MLKEM_SHARED_SECRET_BYTES) {
+      OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SECRET_LENGTH);
+      return 0;
+    }
+    Traits::Encap(out_ciphertext, out_secret, peer_pubkey);
+    return 1;
+  }
+
   static int EncapCtx(EvpPkeyCtx *ctx, uint8_t *out_ciphertext,
                       size_t *out_ciphertext_len, uint8_t *out_secret,
                       size_t *out_secret_len) {
@@ -375,6 +391,21 @@
     return 1;
   }
 
+  static int KemDecap(uint8_t *out_secret, size_t secret_len,
+                      const uint8_t *ciphertext, size_t ciphertext_len,
+                      const EVP_PKEY *key) {
+    const auto *priv = GetKeyData(FromOpaque(key))->AsPrivateKeyData();
+    if (priv == nullptr) {
+      OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY);
+      return 0;
+    }
+    if (secret_len != MLKEM_SHARED_SECRET_BYTES) {
+      OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SECRET_LENGTH);
+      return 0;
+    }
+    return Traits::Decap(out_secret, ciphertext, ciphertext_len, &priv->priv);
+  }
+
   static int DecapCtx(EvpPkeyCtx *ctx, uint8_t *out_secret,
                       size_t *out_secret_len, const uint8_t *ciphertext,
                       size_t ciphertext_len) {
@@ -469,6 +500,14 @@
     return ret;
   }
 
+  static constexpr EVP_KEM evp_kem = {
+      /*pkey_id=*/Traits::kType,
+      /*ciphertext_len=*/Traits::kCiphertextBytes,
+      /*secret_len=*/MLKEM_SHARED_SECRET_BYTES,
+      &KemEncap,
+      &KemDecap,
+  };
+
   static constexpr EVP_PKEY_ASN1_METHOD asn1_method = BuildASN1Method();
   static constexpr EVP_PKEY_ALG pkey_alg = {&asn1_method, &pkey_method};
 };
@@ -482,3 +521,11 @@
 const EVP_PKEY_ALG *EVP_pkey_ml_kem_1024() {
   return &MLKEMImplementation<MLKEM1024Traits>::pkey_alg;
 }
+
+const EVP_KEM *EVP_kem_ml_kem_768() {
+  return &MLKEMImplementation<MLKEM768Traits>::evp_kem;
+}
+
+const EVP_KEM *EVP_kem_ml_kem_1024() {
+  return &MLKEMImplementation<MLKEM1024Traits>::evp_kem;
+}
diff --git a/gen/crypto/err_data.cc b/gen/crypto/err_data.cc
index 567616e..846ca1f 100644
--- a/gen/crypto/err_data.cc
+++ b/gen/crypto/err_data.cc
@@ -80,54 +80,54 @@
     0xc3b00f7,
     0xc3b8921,
     0x10320892,
-    0x103296da,
-    0x103316e6,
-    0x103396ff,
-    0x10341712,
+    0x1032970a,
+    0x10331716,
+    0x1033972f,
+    0x10341742,
     0x10348fd3,
     0x10350d1f,
-    0x10359725,
-    0x1036174f,
-    0x10369762,
-    0x10371781,
-    0x1037979a,
-    0x103817af,
-    0x103897cd,
-    0x103917dc,
-    0x103997f8,
-    0x103a1813,
-    0x103a9822,
-    0x103b183e,
-    0x103b9859,
-    0x103c187f,
+    0x10359755,
+    0x1036177f,
+    0x10369792,
+    0x103717b1,
+    0x103797ca,
+    0x103817df,
+    0x103897fd,
+    0x1039180c,
+    0x10399828,
+    0x103a1843,
+    0x103a9852,
+    0x103b186e,
+    0x103b9889,
+    0x103c18af,
     0x103c80f7,
-    0x103d1890,
-    0x103d98a4,
-    0x103e18c3,
-    0x103e98d2,
-    0x103f18e9,
-    0x103f98fc,
+    0x103d18c0,
+    0x103d98d4,
+    0x103e18f3,
+    0x103e9902,
+    0x103f1919,
+    0x103f992c,
     0x10400ce3,
-    0x1040990f,
-    0x1041192d,
-    0x10419940,
-    0x1042195a,
-    0x1042996a,
-    0x1043197e,
-    0x10439994,
-    0x104419ac,
-    0x104499c1,
-    0x104519d5,
-    0x104599e7,
+    0x1040993f,
+    0x1041195d,
+    0x10419970,
+    0x1042198a,
+    0x1042999a,
+    0x104319ae,
+    0x104399c4,
+    0x104419dc,
+    0x104499f1,
+    0x10451a05,
+    0x10459a17,
     0x10460635,
     0x1046899a,
-    0x104719fc,
-    0x10479a13,
-    0x10481a28,
-    0x10489a36,
+    0x10471a2c,
+    0x10479a43,
+    0x10481a58,
+    0x10489a66,
     0x10490f1f,
-    0x10499870,
-    0x104a173a,
+    0x104998a0,
+    0x104a176a,
     0x14320cb3,
     0x14328cd4,
     0x14330ce3,
@@ -145,55 +145,57 @@
     0x18359072,
     0x1836109a,
     0x183690c1,
-    0x183710f9,
-    0x1837910f,
-    0x18381123,
-    0x18389133,
+    0x18371113,
+    0x18379129,
+    0x1838113d,
+    0x1838914d,
     0x18390ac0,
-    0x18399143,
-    0x183a1169,
-    0x183a918f,
+    0x1839915d,
+    0x183a1183,
+    0x183a91bf,
     0x183b0d2b,
-    0x183b91f1,
-    0x183c1203,
-    0x183c920e,
-    0x183d121e,
-    0x183d922f,
-    0x183e1240,
-    0x183e926c,
-    0x183f1252,
-    0x183f92ae,
-    0x184012c6,
+    0x183b9221,
+    0x183c1233,
+    0x183c923e,
+    0x183d124e,
+    0x183d925f,
+    0x183e1270,
+    0x183e929c,
+    0x183f1282,
+    0x183f92de,
+    0x184012f6,
     0x1840870d,
-    0x184111c5,
-    0x1841917d,
-    0x1842119c,
+    0x184111f5,
+    0x184191ad,
+    0x184211cc,
     0x18428cc1,
-    0x18431158,
-    0x184391d7,
+    0x18431172,
+    0x18439207,
     0x18441068,
     0x184490e5,
     0x18451087,
-    0x18459295,
-    0x184611b2,
-    0x20321300,
-    0x203292ed,
-    0x2432130c,
+    0x184592c5,
+    0x184611e2,
+    0x184690f9,
+    0x18471197,
+    0x20321330,
+    0x2032931d,
+    0x2432133c,
     0x243289e0,
-    0x2433131e,
-    0x2433932b,
-    0x24341338,
-    0x2434934a,
-    0x24351359,
-    0x24359376,
-    0x24361383,
-    0x24369391,
-    0x2437139f,
-    0x243793ad,
-    0x243813b6,
-    0x243893c3,
-    0x243913d6,
-    0x243993ed,
+    0x2433134e,
+    0x2433935b,
+    0x24341368,
+    0x2434937a,
+    0x24351389,
+    0x243593a6,
+    0x243613b3,
+    0x243693c1,
+    0x243713cf,
+    0x243793dd,
+    0x243813e6,
+    0x243893f3,
+    0x24391406,
+    0x2439941d,
     0x28320d13,
     0x28328d2b,
     0x28330ce3,
@@ -203,51 +205,51 @@
     0x283500f7,
     0x28358cc1,
     0x2836099a,
-    0x2c323462,
-    0x2c32940b,
-    0x2c333470,
-    0x2c33b482,
-    0x2c343496,
-    0x2c34b4a8,
-    0x2c3534c3,
-    0x2c35b4d5,
-    0x2c363505,
+    0x2c323492,
+    0x2c32943b,
+    0x2c3334a0,
+    0x2c33b4b2,
+    0x2c3434c6,
+    0x2c34b4d8,
+    0x2c3534f3,
+    0x2c35b505,
+    0x2c363535,
     0x2c36833a,
-    0x2c373512,
-    0x2c37b53e,
-    0x2c38357c,
-    0x2c38b593,
-    0x2c3935b1,
-    0x2c39b5c1,
-    0x2c3a35d3,
-    0x2c3ab5e7,
-    0x2c3b35f8,
-    0x2c3bb617,
-    0x2c3c141d,
-    0x2c3c9433,
-    0x2c3d365c,
-    0x2c3d944c,
-    0x2c3e3686,
-    0x2c3eb694,
-    0x2c3f36ac,
-    0x2c3fb6c4,
-    0x2c4036ee,
-    0x2c409300,
-    0x2c4136ff,
-    0x2c41b712,
-    0x2c4212c6,
-    0x2c42b723,
+    0x2c373542,
+    0x2c37b56e,
+    0x2c3835ac,
+    0x2c38b5c3,
+    0x2c3935e1,
+    0x2c39b5f1,
+    0x2c3a3603,
+    0x2c3ab617,
+    0x2c3b3628,
+    0x2c3bb647,
+    0x2c3c144d,
+    0x2c3c9463,
+    0x2c3d368c,
+    0x2c3d947c,
+    0x2c3e36b6,
+    0x2c3eb6c4,
+    0x2c3f36dc,
+    0x2c3fb6f4,
+    0x2c40371e,
+    0x2c409330,
+    0x2c41372f,
+    0x2c41b742,
+    0x2c4212f6,
+    0x2c42b753,
     0x2c43076d,
-    0x2c43b609,
-    0x2c443551,
-    0x2c44b6d1,
-    0x2c4534e8,
-    0x2c45b524,
-    0x2c4635a1,
-    0x2c46b62b,
-    0x2c473640,
-    0x2c47b679,
-    0x2c483563,
+    0x2c43b639,
+    0x2c443581,
+    0x2c44b701,
+    0x2c453518,
+    0x2c45b554,
+    0x2c4635d1,
+    0x2c46b65b,
+    0x2c473670,
+    0x2c47b6a9,
+    0x2c483593,
     0x30320000,
     0x30328015,
     0x3033001f,
@@ -387,271 +389,271 @@
     0x3c418e13,
     0x3c420f1f,
     0x3c428ea9,
-    0x40321aa2,
-    0x40329ab8,
-    0x40331ae6,
-    0x40339af0,
-    0x40341b07,
-    0x40349b25,
-    0x40351b35,
-    0x40359b47,
-    0x40361b54,
-    0x40369b60,
-    0x40371b75,
-    0x40379b87,
-    0x40381b92,
-    0x40389ba4,
+    0x40321ad2,
+    0x40329ae8,
+    0x40331b16,
+    0x40339b20,
+    0x40341b37,
+    0x40349b55,
+    0x40351b65,
+    0x40359b77,
+    0x40361b84,
+    0x40369b90,
+    0x40371ba5,
+    0x40379bb7,
+    0x40381bc2,
+    0x40389bd4,
     0x40390fd3,
-    0x40399bb4,
-    0x403a1bc7,
-    0x403a9be8,
-    0x403b1bf9,
-    0x403b9c09,
+    0x40399be4,
+    0x403a1bf7,
+    0x403a9c18,
+    0x403b1c29,
+    0x403b9c39,
     0x403c0071,
     0x403c8090,
-    0x403d1c6a,
-    0x403d9c80,
-    0x403e1c8f,
-    0x403e9cc7,
-    0x403f1ce1,
-    0x403f9d09,
-    0x40401d1e,
-    0x40409d32,
-    0x40411d6d,
-    0x40419d88,
-    0x40421da1,
-    0x40429db4,
-    0x40431dc8,
-    0x40439df6,
-    0x40441e0d,
+    0x403d1c9a,
+    0x403d9cb0,
+    0x403e1cbf,
+    0x403e9cf7,
+    0x403f1d11,
+    0x403f9d39,
+    0x40401d4e,
+    0x40409d62,
+    0x40411d9d,
+    0x40419db8,
+    0x40421dd1,
+    0x40429de4,
+    0x40431df8,
+    0x40439e26,
+    0x40441e3d,
     0x404480b9,
-    0x40451e22,
-    0x40459e34,
-    0x40461e58,
-    0x40469e78,
-    0x40471e86,
-    0x40479ead,
-    0x40481f2e,
-    0x40489fe8,
-    0x40491fff,
-    0x4049a019,
-    0x404a2030,
-    0x404aa04e,
-    0x404b2066,
-    0x404ba093,
-    0x404c20a9,
-    0x404ca0bb,
-    0x404d20dc,
-    0x404da115,
-    0x404e2129,
-    0x404ea136,
-    0x404f2209,
-    0x404fa27f,
-    0x40502324,
-    0x4050a338,
-    0x40512385,
-    0x40522395,
-    0x4052a3b9,
-    0x405323d1,
-    0x4053a3e4,
-    0x405423f9,
-    0x4054a41c,
-    0x40552447,
-    0x4055a484,
-    0x405624a9,
-    0x4056a4c2,
-    0x405724da,
-    0x4057a4ed,
-    0x40582502,
-    0x4058a529,
-    0x40592558,
-    0x4059a598,
-    0x405aa5ac,
-    0x405b25c4,
-    0x405ba5d5,
-    0x405c25e8,
-    0x405ca63d,
-    0x405d264a,
-    0x405da66f,
-    0x405e26ad,
+    0x40451e52,
+    0x40459e64,
+    0x40461e88,
+    0x40469ea8,
+    0x40471eb6,
+    0x40479edd,
+    0x40481f5e,
+    0x4048a018,
+    0x4049202f,
+    0x4049a049,
+    0x404a2060,
+    0x404aa07e,
+    0x404b2096,
+    0x404ba0c3,
+    0x404c20d9,
+    0x404ca0eb,
+    0x404d210c,
+    0x404da145,
+    0x404e2159,
+    0x404ea166,
+    0x404f2239,
+    0x404fa2af,
+    0x40502354,
+    0x4050a368,
+    0x405123b5,
+    0x405223c5,
+    0x4052a3e9,
+    0x40532401,
+    0x4053a414,
+    0x40542429,
+    0x4054a44c,
+    0x40552477,
+    0x4055a4b4,
+    0x405624d9,
+    0x4056a4f2,
+    0x4057250a,
+    0x4057a51d,
+    0x40582532,
+    0x4058a559,
+    0x40592588,
+    0x4059a5c8,
+    0x405aa5dc,
+    0x405b25f4,
+    0x405ba605,
+    0x405c2618,
+    0x405ca66d,
+    0x405d267a,
+    0x405da69f,
+    0x405e26dd,
     0x405e8afe,
-    0x405f26fc,
-    0x405fa709,
-    0x40602717,
-    0x4060a739,
-    0x406127ad,
-    0x4061a7e5,
-    0x406227fc,
-    0x4062a80d,
-    0x4063285a,
-    0x4063a86f,
-    0x40642886,
-    0x4064a8b2,
-    0x406528cd,
-    0x4065a8e4,
-    0x406628fc,
-    0x4066a926,
-    0x40672951,
-    0x4067a996,
-    0x406829de,
-    0x4068a9ff,
-    0x40692a31,
-    0x4069aa5f,
-    0x406a2a80,
-    0x406aaaa0,
-    0x406b2c28,
-    0x406bac4b,
-    0x406c2c61,
-    0x406caf6b,
-    0x406d2f9a,
-    0x406dafc2,
-    0x406e2ff0,
-    0x406eb03d,
-    0x406f3096,
-    0x406fb0ce,
-    0x407030e1,
-    0x4070b0fe,
+    0x405f272c,
+    0x405fa739,
+    0x40602747,
+    0x4060a769,
+    0x406127dd,
+    0x4061a815,
+    0x4062282c,
+    0x4062a83d,
+    0x4063288a,
+    0x4063a89f,
+    0x406428b6,
+    0x4064a8e2,
+    0x406528fd,
+    0x4065a914,
+    0x4066292c,
+    0x4066a956,
+    0x40672981,
+    0x4067a9c6,
+    0x40682a0e,
+    0x4068aa2f,
+    0x40692a61,
+    0x4069aa8f,
+    0x406a2ab0,
+    0x406aaad0,
+    0x406b2c58,
+    0x406bac7b,
+    0x406c2c91,
+    0x406caf9b,
+    0x406d2fca,
+    0x406daff2,
+    0x406e3020,
+    0x406eb06d,
+    0x406f30c6,
+    0x406fb0fe,
+    0x40703111,
+    0x4070b12e,
     0x4071084d,
-    0x4071b110,
-    0x40723123,
-    0x4072b159,
-    0x40733171,
-    0x40739635,
-    0x40743185,
-    0x4074b19f,
-    0x407531b0,
-    0x4075b1c4,
-    0x407631d2,
-    0x407693c3,
-    0x407731f7,
-    0x4077b253,
-    0x4078326e,
-    0x4078b2a7,
-    0x407932be,
-    0x4079b2d4,
-    0x407a3300,
-    0x407ab313,
-    0x407b3328,
-    0x407bb33a,
-    0x407c336b,
-    0x407cb374,
-    0x407d2a1a,
-    0x407da2a7,
-    0x407e3283,
-    0x407ea539,
-    0x407f1e9a,
-    0x407fa07d,
-    0x40802219,
-    0x40809ec2,
-    0x408123a7,
-    0x4081a184,
-    0x40822fdb,
-    0x40829c15,
-    0x40832514,
-    0x4083a897,
-    0x40841ee6,
-    0x4084a571,
-    0x408525f9,
-    0x4085a774,
-    0x4086268f,
-    0x4086a2dc,
-    0x40873021,
-    0x4087a7c2,
-    0x40881c53,
-    0x4088a9a9,
-    0x40891ca2,
-    0x40899c2f,
-    0x408a2c99,
-    0x408a9a4d,
-    0x408b334f,
-    0x408bb0ab,
-    0x408c261f,
-    0x408d1fce,
-    0x408d9f18,
-    0x408e20fe,
-    0x408ea464,
-    0x408f29bd,
-    0x408fa790,
-    0x40902972,
-    0x4090a661,
-    0x40912c81,
-    0x40919a85,
-    0x40921cef,
-    0x4092b05c,
-    0x4093313c,
-    0x4093a2ed,
-    0x40941efa,
-    0x4094acb2,
-    0x4095281e,
-    0x4095b2e0,
-    0x40963008,
-    0x4096a232,
-    0x4097236d,
-    0x4097a14d,
-    0x40981d4f,
-    0x4098a832,
-    0x40993078,
-    0x4099a491,
-    0x409a242a,
-    0x409a9a69,
-    0x409b1f54,
-    0x409b9f7f,
-    0x409c3235,
-    0x409c9fa7,
-    0x409d21ee,
-    0x409da19a,
-    0x409e1de0,
-    0x409ea267,
-    0x409f224f,
-    0x409f9f47,
-    0x40a0228f,
-    0x40a0a167,
-    0x40a121b5,
-    0x40a1a585,
-    0x40a22309,
-    0x40a2a6ed,
-    0x40a32761,
-    0x40a3b219,
-    0x40a42353,
-    0x40a4a1cc,
-    0x40a51ed6,
-    0x40a5a2c1,
-    0x40a62609,
-    0x41f42b53,
-    0x41f92be5,
-    0x41fe2ad8,
-    0x41fead8e,
-    0x41ff2ebc,
-    0x42032b6c,
-    0x42082b8e,
-    0x4208abca,
-    0x42092abc,
-    0x4209ac04,
-    0x420a2b13,
-    0x420aaaf3,
-    0x420b2b33,
-    0x420babac,
-    0x420c2ed8,
-    0x420cacc2,
-    0x420d2d75,
-    0x420dadac,
-    0x42122ddf,
-    0x42172e9f,
-    0x4217ae21,
-    0x421c2e43,
-    0x421f2dfe,
-    0x42212f50,
-    0x42262e82,
-    0x422b2f2e,
-    0x422bad50,
-    0x422c2f10,
-    0x422cad03,
-    0x422d2cdc,
-    0x422daeef,
-    0x422e2d2f,
-    0x42302e5e,
-    0x4230adc6,
-    0x423126ce,
+    0x4071b140,
+    0x40723153,
+    0x4072b189,
+    0x407331a1,
+    0x40739665,
+    0x407431b5,
+    0x4074b1cf,
+    0x407531e0,
+    0x4075b1f4,
+    0x40763202,
+    0x407693f3,
+    0x40773227,
+    0x4077b283,
+    0x4078329e,
+    0x4078b2d7,
+    0x407932ee,
+    0x4079b304,
+    0x407a3330,
+    0x407ab343,
+    0x407b3358,
+    0x407bb36a,
+    0x407c339b,
+    0x407cb3a4,
+    0x407d2a4a,
+    0x407da2d7,
+    0x407e32b3,
+    0x407ea569,
+    0x407f1eca,
+    0x407fa0ad,
+    0x40802249,
+    0x40809ef2,
+    0x408123d7,
+    0x4081a1b4,
+    0x4082300b,
+    0x40829c45,
+    0x40832544,
+    0x4083a8c7,
+    0x40841f16,
+    0x4084a5a1,
+    0x40852629,
+    0x4085a7a4,
+    0x408626bf,
+    0x4086a30c,
+    0x40873051,
+    0x4087a7f2,
+    0x40881c83,
+    0x4088a9d9,
+    0x40891cd2,
+    0x40899c5f,
+    0x408a2cc9,
+    0x408a9a7d,
+    0x408b337f,
+    0x408bb0db,
+    0x408c264f,
+    0x408d1ffe,
+    0x408d9f48,
+    0x408e212e,
+    0x408ea494,
+    0x408f29ed,
+    0x408fa7c0,
+    0x409029a2,
+    0x4090a691,
+    0x40912cb1,
+    0x40919ab5,
+    0x40921d1f,
+    0x4092b08c,
+    0x4093316c,
+    0x4093a31d,
+    0x40941f2a,
+    0x4094ace2,
+    0x4095284e,
+    0x4095b310,
+    0x40963038,
+    0x4096a262,
+    0x4097239d,
+    0x4097a17d,
+    0x40981d7f,
+    0x4098a862,
+    0x409930a8,
+    0x4099a4c1,
+    0x409a245a,
+    0x409a9a99,
+    0x409b1f84,
+    0x409b9faf,
+    0x409c3265,
+    0x409c9fd7,
+    0x409d221e,
+    0x409da1ca,
+    0x409e1e10,
+    0x409ea297,
+    0x409f227f,
+    0x409f9f77,
+    0x40a022bf,
+    0x40a0a197,
+    0x40a121e5,
+    0x40a1a5b5,
+    0x40a22339,
+    0x40a2a71d,
+    0x40a32791,
+    0x40a3b249,
+    0x40a42383,
+    0x40a4a1fc,
+    0x40a51f06,
+    0x40a5a2f1,
+    0x40a62639,
+    0x41f42b83,
+    0x41f92c15,
+    0x41fe2b08,
+    0x41feadbe,
+    0x41ff2eec,
+    0x42032b9c,
+    0x42082bbe,
+    0x4208abfa,
+    0x42092aec,
+    0x4209ac34,
+    0x420a2b43,
+    0x420aab23,
+    0x420b2b63,
+    0x420babdc,
+    0x420c2f08,
+    0x420cacf2,
+    0x420d2da5,
+    0x420daddc,
+    0x42122e0f,
+    0x42172ecf,
+    0x4217ae51,
+    0x421c2e73,
+    0x421f2e2e,
+    0x42212f80,
+    0x42262eb2,
+    0x422b2f5e,
+    0x422bad80,
+    0x422c2f40,
+    0x422cad33,
+    0x422d2d0c,
+    0x422daf1f,
+    0x422e2d5f,
+    0x42302e8e,
+    0x4230adf6,
+    0x423126fe,
     0x44320778,
     0x44328787,
     0x44330793,
@@ -669,109 +671,109 @@
     0x4439084d,
     0x4439885b,
     0x443a086e,
-    0x4832140b,
-    0x4832941d,
-    0x48331433,
-    0x4833944c,
-    0x4c321489,
-    0x4c329499,
-    0x4c3314ac,
-    0x4c3394cc,
+    0x4832143b,
+    0x4832944d,
+    0x48331463,
+    0x4833947c,
+    0x4c3214b9,
+    0x4c3294c9,
+    0x4c3314dc,
+    0x4c3394fc,
     0x4c3400b9,
     0x4c3480f7,
-    0x4c3514d8,
-    0x4c3594e6,
-    0x4c361502,
-    0x4c369528,
-    0x4c371537,
-    0x4c379545,
-    0x4c38155a,
-    0x4c389566,
-    0x4c391586,
-    0x4c3995b0,
-    0x4c3a15c9,
-    0x4c3a95e2,
+    0x4c351508,
+    0x4c359516,
+    0x4c361532,
+    0x4c369558,
+    0x4c371567,
+    0x4c379575,
+    0x4c38158a,
+    0x4c389596,
+    0x4c3915b6,
+    0x4c3995e0,
+    0x4c3a15f9,
+    0x4c3a9612,
     0x4c3b0635,
-    0x4c3b95fb,
-    0x4c3c160d,
-    0x4c3c961c,
-    0x4c3d1635,
+    0x4c3b962b,
+    0x4c3c163d,
+    0x4c3c964c,
+    0x4c3d1665,
     0x4c3d8d06,
-    0x4c3e16a2,
-    0x4c3e9644,
-    0x4c3f16c4,
-    0x4c3f93c3,
-    0x4c40165a,
-    0x4c409475,
-    0x4c411692,
-    0x4c419515,
-    0x4c42167e,
-    0x4c42945d,
-    0x50323735,
-    0x5032b744,
-    0x5033374f,
-    0x5033b75f,
-    0x50343778,
-    0x5034b792,
-    0x503537a0,
-    0x5035b7b6,
-    0x503637c8,
-    0x5036b7de,
-    0x503737f7,
-    0x5037b80a,
-    0x50383822,
-    0x5038b833,
-    0x50393848,
-    0x5039b85c,
-    0x503a387c,
-    0x503ab892,
-    0x503b38aa,
-    0x503bb8bc,
-    0x503c38d8,
-    0x503cb8ef,
-    0x503d3908,
-    0x503db91e,
-    0x503e392b,
-    0x503eb941,
-    0x503f3953,
+    0x4c3e16d2,
+    0x4c3e9674,
+    0x4c3f16f4,
+    0x4c3f93f3,
+    0x4c40168a,
+    0x4c4094a5,
+    0x4c4116c2,
+    0x4c419545,
+    0x4c4216ae,
+    0x4c42948d,
+    0x50323765,
+    0x5032b774,
+    0x5033377f,
+    0x5033b78f,
+    0x503437a8,
+    0x5034b7c2,
+    0x503537d0,
+    0x5035b7e6,
+    0x503637f8,
+    0x5036b80e,
+    0x50373827,
+    0x5037b83a,
+    0x50383852,
+    0x5038b863,
+    0x50393878,
+    0x5039b88c,
+    0x503a38ac,
+    0x503ab8c2,
+    0x503b38da,
+    0x503bb8ec,
+    0x503c3908,
+    0x503cb91f,
+    0x503d3938,
+    0x503db94e,
+    0x503e395b,
+    0x503eb971,
+    0x503f3983,
     0x503f83b3,
-    0x50403966,
-    0x5040b976,
-    0x50413990,
-    0x5041b99f,
-    0x504239b9,
-    0x5042b9d6,
-    0x504339e6,
-    0x5043b9f6,
-    0x50443a13,
+    0x50403996,
+    0x5040b9a6,
+    0x504139c0,
+    0x5041b9cf,
+    0x504239e9,
+    0x5042ba06,
+    0x50433a16,
+    0x5043ba26,
+    0x50443a43,
     0x50448469,
-    0x50453a27,
-    0x5045ba45,
-    0x50463a58,
-    0x5046ba6e,
-    0x50473a80,
-    0x5047ba95,
-    0x50483abb,
-    0x5048bac9,
-    0x50493adc,
-    0x5049baf1,
-    0x504a3b07,
-    0x504abb17,
-    0x504b3b37,
-    0x504bbb4a,
-    0x504c3b6d,
-    0x504cbb9b,
-    0x504d3bc8,
-    0x504dbbe5,
-    0x504e3c00,
-    0x504ebc1c,
-    0x504f3c2e,
-    0x504fbc45,
-    0x50503c54,
+    0x50453a57,
+    0x5045ba75,
+    0x50463a88,
+    0x5046ba9e,
+    0x50473ab0,
+    0x5047bac5,
+    0x50483aeb,
+    0x5048baf9,
+    0x50493b0c,
+    0x5049bb21,
+    0x504a3b37,
+    0x504abb47,
+    0x504b3b67,
+    0x504bbb7a,
+    0x504c3b9d,
+    0x504cbbcb,
+    0x504d3bf8,
+    0x504dbc15,
+    0x504e3c30,
+    0x504ebc4c,
+    0x504f3c5e,
+    0x504fbc75,
+    0x50503c84,
     0x50508729,
-    0x50513c67,
-    0x5051ba05,
-    0x50523bad,
+    0x50513c97,
+    0x5051ba35,
+    0x50523bdd,
     0x58321011,
     0x68320fd3,
     0x68328d2b,
@@ -813,22 +815,22 @@
     0x783d8b97,
     0x783e0aed,
     0x783e8a9f,
-    0x7c3212dc,
-    0x80321528,
+    0x7c32130c,
+    0x80321558,
     0x80328090,
-    0x80333431,
+    0x80333461,
     0x803380b9,
-    0x80343440,
-    0x8034b3a8,
-    0x803533c6,
-    0x8035b454,
-    0x80363408,
-    0x8036b3b7,
-    0x803733fa,
-    0x8037b395,
-    0x8038341b,
-    0x8038b3d7,
-    0x803933ec,
+    0x80343470,
+    0x8034b3d8,
+    0x803533f6,
+    0x8035b484,
+    0x80363438,
+    0x8036b3e7,
+    0x8037342a,
+    0x8037b3c5,
+    0x8038344b,
+    0x8038b407,
+    0x8039341c,
     0x84320bb0,
     0x84328bc9,
 };
@@ -1062,6 +1064,7 @@
     "EXPECTING_A_EC_KEY\0"
     "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE\0"
     "INVALID_BUFFER_SIZE\0"
+    "INVALID_CIPHERTEXT_LENGTH\0"
     "INVALID_DIGEST_LENGTH\0"
     "INVALID_DIGEST_TYPE\0"
     "INVALID_KEYBITS\0"
@@ -1069,6 +1072,7 @@
     "INVALID_PADDING_MODE\0"
     "INVALID_PEER_KEY\0"
     "INVALID_PSS_SALTLEN\0"
+    "INVALID_SECRET_LENGTH\0"
     "INVALID_SIGNATURE\0"
     "KEYS_NOT_SET\0"
     "MEMORY_LIMIT_EXCEEDED\0"
diff --git a/gen/sources.bzl b/gen/sources.bzl
index 81be628..8955e91 100644
--- a/gen/sources.bzl
+++ b/gen/sources.bzl
@@ -386,6 +386,7 @@
     "crypto/evp/evp.cc",
     "crypto/evp/evp_asn1.cc",
     "crypto/evp/evp_ctx.cc",
+    "crypto/evp/evp_kem.cc",
     "crypto/evp/p_dh.cc",
     "crypto/evp/p_dsa.cc",
     "crypto/evp/p_ec.cc",
diff --git a/gen/sources.cmake b/gen/sources.cmake
index 18292b9..c875958 100644
--- a/gen/sources.cmake
+++ b/gen/sources.cmake
@@ -404,6 +404,7 @@
   crypto/evp/evp.cc
   crypto/evp/evp_asn1.cc
   crypto/evp/evp_ctx.cc
+  crypto/evp/evp_kem.cc
   crypto/evp/p_dh.cc
   crypto/evp/p_dsa.cc
   crypto/evp/p_ec.cc
diff --git a/gen/sources.gni b/gen/sources.gni
index 6677107..0deaacb 100644
--- a/gen/sources.gni
+++ b/gen/sources.gni
@@ -386,6 +386,7 @@
   "crypto/evp/evp.cc",
   "crypto/evp/evp_asn1.cc",
   "crypto/evp/evp_ctx.cc",
+  "crypto/evp/evp_kem.cc",
   "crypto/evp/p_dh.cc",
   "crypto/evp/p_dsa.cc",
   "crypto/evp/p_ec.cc",
diff --git a/gen/sources.json b/gen/sources.json
index e7ca6a3..a844ca9 100644
--- a/gen/sources.json
+++ b/gen/sources.json
@@ -370,6 +370,7 @@
       "crypto/evp/evp.cc",
       "crypto/evp/evp_asn1.cc",
       "crypto/evp/evp_ctx.cc",
+      "crypto/evp/evp_kem.cc",
       "crypto/evp/p_dh.cc",
       "crypto/evp/p_dsa.cc",
       "crypto/evp/p_ec.cc",
diff --git a/gen/sources.mk b/gen/sources.mk
index d7e61b9..66c192f 100644
--- a/gen/sources.mk
+++ b/gen/sources.mk
@@ -378,6 +378,7 @@
   crypto/evp/evp.cc \
   crypto/evp/evp_asn1.cc \
   crypto/evp/evp_ctx.cc \
+  crypto/evp/evp_kem.cc \
   crypto/evp/p_dh.cc \
   crypto/evp/p_dsa.cc \
   crypto/evp/p_ec.cc \
diff --git a/include/openssl/base.h b/include/openssl/base.h
index 5cb2bd5..008b162 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -343,6 +343,7 @@
 typedef struct evp_hpke_kdf_st EVP_HPKE_KDF;
 typedef struct evp_hpke_kem_st EVP_HPKE_KEM;
 typedef struct evp_hpke_key_st EVP_HPKE_KEY;
+typedef struct evp_kem_st EVP_KEM;
 typedef struct evp_pkey_alg_st EVP_PKEY_ALG;
 typedef struct evp_pkey_ctx_st EVP_PKEY_CTX;
 typedef struct evp_pkey_st EVP_PKEY;
diff --git a/include/openssl/evp.h b/include/openssl/evp.h
index 8f17bcb..a437550 100644
--- a/include/openssl/evp.h
+++ b/include/openssl/evp.h
@@ -1135,6 +1135,63 @@
 OPENSSL_EXPORT int EVP_PKEY_CTX_set_dh_pad(EVP_PKEY_CTX *ctx, int pad);
 
 
+// Key encapsulation mechanism algorithms.
+//
+// Two APIs for working with key encapsulation mechanism (KEM) algorithms are
+// provided:
+//
+//  1. Create an |EVP_PKEY_CTX|, initialize it for the appropriate KEM operation
+//     (see |EVP_PKEY_encapsulate_init| and |EVP_PKEY_decapsulate_init|), then
+//     run the operation. This matches the OpenSSL API.
+//
+//  2. Pass an appropriate |EVP_KEM| object to the functions below to use it
+//     for encapsulation and decapsulation operations with compatible |EVP_PKEY|
+//     objects. This API requires fewer steps.
+//
+// The |EVP_KEM| API is only compatible with KEMs that use fixed-length
+// ciphertexts and secrets.
+
+// EVP_kem_ml_kem_* implement ML-KEM, defined in FIPS 203.
+OPENSSL_EXPORT const EVP_KEM *EVP_kem_ml_kem_768(void);
+OPENSSL_EXPORT const EVP_KEM *EVP_kem_ml_kem_1024(void);
+
+// TODO(crbug.com/449751916): Add more supported KEMs.
+
+// EVP_KEM_ciphertext_len returns the fixed length, in bytes, of a ciphertext
+// produced and consumed by |kem|.
+OPENSSL_EXPORT size_t EVP_KEM_ciphertext_len(const EVP_KEM *kem);
+
+// EVP_KEM_secret_len returns the fixed length, in bytes, of the shared
+// secret produced and consumed by |kem|.
+OPENSSL_EXPORT size_t EVP_KEM_secret_len(const EVP_KEM *kem);
+
+// EVP_KEM_encap uses |kem| to encapsulate a |peer_key|. It outputs a
+// ciphertext of length |ciphertext_len| into |*out_ciphertext| and outputs a
+// shared secret of length |secret_len| into |*out_secret|. |peer_key| must be
+// a public key of the type expected by |kem|. |ciphertext_len| and
+// |secret_len| must match the output of |EVP_KEM_ciphertext_len| and
+// |EVP_KEM_secret_len|, respectively, when called with |kem|. This function
+// returns one on success or zero on failure.
+OPENSSL_EXPORT int EVP_KEM_encap(const EVP_KEM *kem, uint8_t *out_ciphertext,
+                                 size_t ciphertext_len, uint8_t *out_secret,
+                                 size_t secret_len, const EVP_PKEY *peer_key);
+
+// EVP_KEM_decap uses |kem| to decapsulate a |ciphertext| of length
+// |ciphertext_len|, using |key| as a decapsulation key. It outputs a shared
+// secret of length |secret_len| into |*out_secret|. |key| must be a private key
+// of the type expected by |kem|. |secret_len| must match the output of
+// |EVP_KEM_secret_len| when called with |kem|. This function returns one on
+// success or zero on failure. If |ciphertext| is invalid (but of the correct
+// length), this will return one and fill |out_secret| with a key that will
+// always be the same for the same |ciphertext| and |key|, but which appears to
+// be random unless one has access to the private |key|. Any subsequent
+// symmetric encryption using |*out_secret| must use an authenticated encryption
+// scheme in order to discover the decapsulation failure.
+OPENSSL_EXPORT int EVP_KEM_decap(const EVP_KEM *kem, uint8_t *out_secret,
+                                 size_t secret_len, const uint8_t *ciphertext,
+                                 size_t ciphertext_len, const EVP_PKEY *key);
+
+
 // Deprecated functions.
 
 // EVP_PKEY_RSA2 was historically an alternate form for RSA public keys (OID
diff --git a/include/openssl/evp_errors.h b/include/openssl/evp_errors.h
index eb062ca..77b7ab5 100644
--- a/include/openssl/evp_errors.h
+++ b/include/openssl/evp_errors.h
@@ -56,6 +56,8 @@
 #define EVP_R_EXPECTING_A_DH_KEY 138
 #define EVP_R_PRIVATE_KEY_WAS_NOT_SEED 139
 #define EVP_R_MISSING_PUBLIC_KEY 140
+#define EVP_R_INVALID_CIPHERTEXT_LENGTH 141
+#define EVP_R_INVALID_SECRET_LENGTH 142
 
 // Compatibility defines.
 #define EVP_R_OPERATON_NOT_INITIALIZED EVP_R_OPERATION_NOT_INITIALIZED
diff --git a/include/openssl/prefix_symbols.h b/include/openssl/prefix_symbols.h
index 40ee107..ee35d09 100644
--- a/include/openssl/prefix_symbols.h
+++ b/include/openssl/prefix_symbols.h
@@ -1053,6 +1053,10 @@
 #pragma redefine_extname EVP_HPKE_KEY_private_key BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_HPKE_KEY_private_key))
 #pragma redefine_extname EVP_HPKE_KEY_public_key BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_HPKE_KEY_public_key))
 #pragma redefine_extname EVP_HPKE_KEY_zero BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_HPKE_KEY_zero))
+#pragma redefine_extname EVP_KEM_ciphertext_len BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_KEM_ciphertext_len))
+#pragma redefine_extname EVP_KEM_decap BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_KEM_decap))
+#pragma redefine_extname EVP_KEM_encap BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_KEM_encap))
+#pragma redefine_extname EVP_KEM_secret_len BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_KEM_secret_len))
 #pragma redefine_extname EVP_MD_CTX_block_size BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_MD_CTX_block_size))
 #pragma redefine_extname EVP_MD_CTX_cleanse BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_MD_CTX_cleanse))
 #pragma redefine_extname EVP_MD_CTX_cleanup BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_MD_CTX_cleanup))
@@ -1280,6 +1284,8 @@
 #pragma redefine_extname EVP_hpke_p256_hkdf_sha256 BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_hpke_p256_hkdf_sha256))
 #pragma redefine_extname EVP_hpke_x25519_hkdf_sha256 BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_hpke_x25519_hkdf_sha256))
 #pragma redefine_extname EVP_hpke_xwing BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_hpke_xwing))
+#pragma redefine_extname EVP_kem_ml_kem_1024 BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_kem_ml_kem_1024))
+#pragma redefine_extname EVP_kem_ml_kem_768 BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_kem_ml_kem_768))
 #pragma redefine_extname EVP_marshal_digest_algorithm BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_marshal_digest_algorithm))
 #pragma redefine_extname EVP_marshal_digest_algorithm_no_params BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_marshal_digest_algorithm_no_params))
 #pragma redefine_extname EVP_marshal_private_key BORINGSSL_SYMBOL(BORINGSSL_ADD_PREFIX(EVP_marshal_private_key))
@@ -4124,6 +4130,10 @@
 #define EVP_HPKE_KEY_private_key BORINGSSL_ADD_PREFIX(EVP_HPKE_KEY_private_key)
 #define EVP_HPKE_KEY_public_key BORINGSSL_ADD_PREFIX(EVP_HPKE_KEY_public_key)
 #define EVP_HPKE_KEY_zero BORINGSSL_ADD_PREFIX(EVP_HPKE_KEY_zero)
+#define EVP_KEM_ciphertext_len BORINGSSL_ADD_PREFIX(EVP_KEM_ciphertext_len)
+#define EVP_KEM_decap BORINGSSL_ADD_PREFIX(EVP_KEM_decap)
+#define EVP_KEM_encap BORINGSSL_ADD_PREFIX(EVP_KEM_encap)
+#define EVP_KEM_secret_len BORINGSSL_ADD_PREFIX(EVP_KEM_secret_len)
 #define EVP_MD_CTX_block_size BORINGSSL_ADD_PREFIX(EVP_MD_CTX_block_size)
 #define EVP_MD_CTX_cleanse BORINGSSL_ADD_PREFIX(EVP_MD_CTX_cleanse)
 #define EVP_MD_CTX_cleanup BORINGSSL_ADD_PREFIX(EVP_MD_CTX_cleanup)
@@ -4351,6 +4361,8 @@
 #define EVP_hpke_p256_hkdf_sha256 BORINGSSL_ADD_PREFIX(EVP_hpke_p256_hkdf_sha256)
 #define EVP_hpke_x25519_hkdf_sha256 BORINGSSL_ADD_PREFIX(EVP_hpke_x25519_hkdf_sha256)
 #define EVP_hpke_xwing BORINGSSL_ADD_PREFIX(EVP_hpke_xwing)
+#define EVP_kem_ml_kem_1024 BORINGSSL_ADD_PREFIX(EVP_kem_ml_kem_1024)
+#define EVP_kem_ml_kem_768 BORINGSSL_ADD_PREFIX(EVP_kem_ml_kem_768)
 #define EVP_marshal_digest_algorithm BORINGSSL_ADD_PREFIX(EVP_marshal_digest_algorithm)
 #define EVP_marshal_digest_algorithm_no_params BORINGSSL_ADD_PREFIX(EVP_marshal_digest_algorithm_no_params)
 #define EVP_marshal_private_key BORINGSSL_ADD_PREFIX(EVP_marshal_private_key)