Merge commit '053b5b8c4214ec379daac2cc2bf208c51cc1cd66' into main

Current Tink mirror in Fuchsia has missing include issue with the latest version of Clang. The missing include is #include <cstdlib> (https://github.com/tink-crypto/tink/blob/master/cc/util/secret_data_internal.h#L21)

Tink introduced it in https://github.com/tink-crypto/tink/commit/053b5b8c4214ec379daac2cc2bf208c51cc1cd66
We should at least uprev Tink to this commit to unblock the issue.

Command to uprev Tink:
```
git checkout origin/main -b ${USER}-merge
git merge 053b5b8c4214ec379daac2cc2bf208c51cc1cd66
python3 ./tools/convert_for_cobalt
fx format-code --files=${gn build files that convert_for_cobalt wrote}
git commit --no-verify
```

Locally patch to include missing absl/strings/str_cat.h header:
cc/mac/hmac_parameters.cc
cc/mac/aes_cmac_parameters.cc

Bug: 376297654

Change-Id: I7cc47556e5ddec6be01bd7a523f527abea47e20a
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/tink/+/1153894
Commit-Queue: Anivia Li <aniviali@google.com>
Reviewed-by: Alex Pankhurst <pankhurst@google.com>
diff --git a/.bazelversion b/.bazelversion
index ac14c3d..09b254e 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-5.1.1
+6.0.0
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 66f2ffc..2f37316 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,7 +1,6 @@
 ---
 name: Bug report
 about: Create a report to help us improve Tink
-assignees: 'thaidn, chuckx'
 
 ---
 
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 486da6f..100d12c 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,7 +1,6 @@
 ---
 name: Feature request
 about: Suggest an idea for Tink
-assignees: 'thaidn, chuckx'
 
 ---
 
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 49711e3..49269d9 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -32,7 +32,7 @@
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
       with:
         # We must fetch at least the immediate parents so that if this is
         # a pull request then we can checkout the head.
@@ -45,7 +45,7 @@
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      uses: github/codeql-action/init@v2
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
+      uses: github/codeql-action/autobuild@v2
 
     # â„šī¸ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -70,4 +70,4 @@
     #   make release
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
+      uses: github/codeql-action/analyze@v2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dff0033..2e4f78f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
-cmake_minimum_required(VERSION 3.5)
-project(Tink VERSION 1.7.0 LANGUAGES CXX)
+cmake_minimum_required(VERSION 3.13)
+project(Tink VERSION 2.0.0 LANGUAGES CXX)
 
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 
@@ -7,8 +7,6 @@
 option(TINK_USE_SYSTEM_OPENSSL "Build Tink linking to OpenSSL installed in the system" OFF)
 option(TINK_USE_INSTALLED_ABSEIL "Build Tink linking to Abseil installed in the system" OFF)
 option(TINK_USE_INSTALLED_GOOGLETEST "Build Tink linking to GTest installed in the system" OFF)
-option(TINK_USE_ABSL_STATUS "Compile Tink with absl::Status" OFF)
-option(TINK_USE_ABSL_STATUSOR "Compile Tink with absl::StatusOr" OFF)
 option(USE_ONLY_FIPS "Enables the FIPS only mode in Tink" OFF)
 
 set(CPACK_GENERATOR TGZ)
diff --git a/README.md b/README.md
index 74035b9..3ff7337 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,74 @@
 # Tink
 
-*A multi-language, cross-platform library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.*
+*A multi-language, cross-platform library that provides cryptographic APIs that
+are secure, easy to use correctly, and hard(er) to misuse. See also:
+https://developers.google.com/tink*.
 
-https://developers.google.com/tink
+> **NOTE**: **Tink is moving!**
+>
+> As part of our roadmap we are splitting Tink into
+> [multiple GitHub repositories][split_repo_roadmap_url] that will be hosted at
+> [github.com/tink-crypto](https://github.com/tink-crypto) and will be
+> independently versioned.
+>
+> Roughly, we are going to create one repository per language, library extension
+> such as KMS (except Tink Python), and tools.
+>
+> A few important highlights:
+>
+> -   The migration will be done gradually over the course of 2023 with a new
+>     release from each of the new repositories. Releases will be announced in
+>     our [mailing list][tink_mailing_list_url].
+> -   We will keep updating each implementation/tool in
+>     [github.com/google/tink](https://github.com/google/tink) for a specified
+>     amount of time; migrated implementations/tools will eventually stop being
+>     updated on [github.com/google/tink](https://github.com/google/tink). The
+>     support window depends on the specific implementation, as shown in the
+>     table below.
+> -   New issues and pull requests should be created in the new repos.
+>
+> Below is the list of resulting repositories, migration timeline and expected
+> end of support.
+>
+> Tink implementation/extension         | New repository                                                                            | Migration status               | End of support in google/tink
+> ------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------ | -----------------------------
+> Tink Java                             | [tink-crypto/tink-java](https://github.com/tink-crypto/tink-java)                         | Complete (Q1 2023)             | Q3 2023
+> Tink Java AWS KMS extension           | [tink-crypto/tink-java-awskms](https://github.com/tink-crypto/tink-java-awskms)           | Complete (Q1 2023)             | Q3 2023
+> Tink Java Google Cloud KMS extension  | [tink-crypto/tink-java-gcpkms](https://github.com/tink-crypto/tink-java-gcpkms)           | Complete (Q1 2023)             | Q3 2023
+> Tink Java apps extension              | [tink-crypto/tink-java-apps](https://github.com/tink-crypto/tink-java-apps)               | Complete (Q1 2023)             | Q3 2023
+> Tink C++                              | [tink-crypto/tink-cc](https://github.com/tink-crypto/tink-cc)                             | Complete (Q2 2023)             | Q4 2023
+> Tink C++ AWS KMS extension            | [tink-crypto/tink-cc-awskms](https://github.com/tink-crypto/tink-cc-awskms)               | Complete (Q2 2023)             | Q4 2023
+> Tink C++ Google Cloud KMS extension   | [tink-crypto/tink-cc-gcpkms](https://github.com/tink-crypto/tink-cc-gcpkms)               | Complete (Q2 2023)             | Q4 2023
+> Tink Python                           | [tink-crypto/tink-py](https://github.com/tink-crypto/tink-py)                             | Not started (expected Q3 2023) | TBA
+> Tink Go                               | [tink-crypto/tink-go](https://github.com/tink-crypto/tink-go)                             | In progress (expected Q2 2023) | TBA
+> Tink Go AWS KMS extension             | [tink-crypto/tink-go-awskms](https://github.com/tink-crypto/tink-go-awskms)               | In progress (expected Q2 2023) | TBA
+> Tink Go Google Cloud KMS extension    | [tink-crypto/tink-go-gcpkms](https://github.com/tink-crypto/tink-go-gcpkms)               | In progress (expected Q2 2023) | TBA
+> Tink Go HashiCorp Vault KMS extension | [tink-crypto/tink-go-hcvault](https://github.com/tink-crypto/tink-go-hcvault)             | In progress (expected Q2 2023) | TBA
+> Tink Obj-C                            | [tink-crypto/tink-objc](https://github.com/tink-crypto/tink-objc)                         | Not started (expected Q4 2023) | TBA
+> Tink Tinkey                           | [tink-crypto/tink-tinkey](https://github.com/tink-crypto/tink-tinkey)                     | Complete (Q2 2023)             | Q4 2023
+> Tink cross language tests             | [tink-crypto/tink-cross-lang-tests](https://github.com/tink-crypto/tink-cross-lang-tests) | Not started (expected Q4 2023) | TBA
 
-**`Ubuntu`**                        | **`macOS`**
------------------------------------ | ---------------------------------
-[![Kokoro Ubuntu][ubuntu_badge]](#) | [![Kokoro macOS][macos_badge]](#)
+> **NOTE**: **We are removing Tink for JavaScript/TypeScript**
+>
+> We are removing the Tink JavaScript/TypeScript library from our current Github
+> repository (master branch). As part of our effort to migrate Tink to
+> https://github.com/tink-crypto, we will not release an individual
+> JavaScript/Typescript repository. Furthermore, the JavaScript/TypeScript
+> [directory](https://github.com/google/tink/tree/master/javascript) in the
+> current release branch (v1.7.0) will no longer be actively supported.
+>
+> _We aim to remove the JS/TS directory from the current Tink Github repository
+> (master branch) on **June 22, 2023**. We will also deprecate the Tink npm
+> package on this date._
+>
+> See [this](https://github.com/google/tink/issues/689) tracking issue for more
+> details.
+>
+> Feel free to use our [mailing list][tink_mailing_list_url] to raise any
+> questions, issues or concerns.
 
-[ubuntu_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-ubuntu.png
-[macos_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-macos.png
+[split_repo_roadmap_url]: https://developers.google.com/tink/roadmap#splitting_tink_into_multiple_github_repositories
+[tink_mailing_list_url]: https://groups.google.com/forum/#!forum/tink-users
 
 ## Index
 
@@ -25,9 +84,10 @@
 Using crypto in your application [shouldn't have to][devs_are_users_too_slides]
 feel like juggling chainsaws in the dark. Tink is a crypto library written by a
 group of cryptographers and security engineers at Google. It was born out of our
-extensive experience working with Google's product teams, [fixing weaknesses in
-implementations](https://github.com/google/wycheproof), and providing simple
-APIs that can be used safely without needing a crypto background.
+extensive experience working with Google's product teams,
+[fixing weaknesses in implementations](https://github.com/google/wycheproof),
+and providing simple APIs that can be used safely without needing a crypto
+background.
 
 Tink provides secure APIs that are easy to use correctly and hard(er) to misuse.
 It reduces common crypto pitfalls with user-centered design, careful
@@ -52,6 +112,15 @@
 released on 2022-08-09.
 
 Javascript/Typescript is in an alpha state and should only be used for testing.
+Please see the intent to remove statement
+[here](https://github.com/google/tink/issues/689).
+
+**`Ubuntu`**                        | **`macOS`**
+----------------------------------- | ---------------------------------
+[![Kokoro Ubuntu][ubuntu_badge]](#) | [![Kokoro macOS][macos_badge]](#)
+
+[ubuntu_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-ubuntu.png
+[macos_badge]: https://storage.googleapis.com/tink-kokoro-build-badges/tink-macos.png
 
 ## Getting started
 
@@ -130,12 +199,11 @@
 
 ## Contact and mailing list
 
-If you want to contribute, please read [CONTRIBUTING](docs/CONTRIBUTING.md)
-and send us pull requests. You can also report bugs or file feature requests.
+If you want to contribute, please read [CONTRIBUTING](docs/CONTRIBUTING.md) and
+send us pull requests. You can also report bugs or file feature requests.
 
 If you'd like to talk to the developers or get notified about major product
-updates, you may want to subscribe to our
-[mailing list](https://groups.google.com/forum/#!forum/tink-users).
+updates, you may want to subscribe to our [mailing list][tink_mailing_list_url].
 
 ## Maintainers
 
@@ -167,3 +235,5 @@
 -   Enzo Puig
 -   Veronika Slívová
 -   Paula Vidas
+-   Cathie Yun
+-   Federico Zalcberg
diff --git a/WORKSPACE b/WORKSPACE
deleted file mode 100644
index 5a9185b..0000000
--- a/WORKSPACE
+++ /dev/null
@@ -1,9 +0,0 @@
-workspace(name = "tink_base")
-
-load("@tink_base//:tink_base_deps.bzl", "tink_base_deps")
-
-tink_base_deps()
-
-load("@tink_base//:tink_base_deps_init.bzl", "tink_base_deps_init")
-
-tink_base_deps_init()
diff --git a/apps/.bazelversion b/apps/.bazelversion
deleted file mode 100644
index ac14c3d..0000000
--- a/apps/.bazelversion
+++ /dev/null
@@ -1 +0,0 @@
-5.1.1
diff --git a/apps/BUILD.bazel b/apps/BUILD.bazel
deleted file mode 100644
index 1a1b3a3..0000000
--- a/apps/BUILD.bazel
+++ /dev/null
@@ -1,3 +0,0 @@
-package(default_visibility = ["//:__subpackages__"])
-
-licenses(["notice"])
diff --git a/apps/README.md b/apps/README.md
deleted file mode 100644
index 70b329f..0000000
--- a/apps/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Tink Java Apps
-
-It contains extensions and applications of Tink Java.
diff --git a/apps/WORKSPACE b/apps/WORKSPACE
deleted file mode 100644
index 27aaa48..0000000
--- a/apps/WORKSPACE
+++ /dev/null
@@ -1,22 +0,0 @@
-workspace(name = "tink_apps")
-
-local_repository(
-    name = "tink_java",
-    path = "../java_src",
-)
-
-load("@tink_java//:tink_java_deps.bzl", "tink_java_deps", "TINK_MAVEN_ARTIFACTS")
-tink_java_deps()
-
-load("@tink_java//:tink_java_deps_init.bzl", "tink_java_deps_init")
-tink_java_deps_init()
-
-load("@rules_jvm_external//:defs.bzl", "maven_install")
-
-maven_install(
-    artifacts = TINK_MAVEN_ARTIFACTS,
-    repositories = [
-        "https://maven.google.com",
-        "https://repo1.maven.org/maven2",
-    ],
-)
diff --git a/apps/paymentmethodtoken/BUILD.bazel b/apps/paymentmethodtoken/BUILD.bazel
deleted file mode 100644
index f601945..0000000
--- a/apps/paymentmethodtoken/BUILD.bazel
+++ /dev/null
@@ -1,26 +0,0 @@
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-gen_maven_jar_rules(
-    name = "maven",
-    doctitle = "Tink Cryptography API for Google Payment Method Token",
-    manifest_lines = [
-        "Automatic-Module-Name: com.google.crypto.tink.apps.paymentmethodtoken",
-    ],
-    root_packages = ["com.google.crypto.tink.apps.paymentmethodtoken"],
-    deps = [
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:google_payments_public_keys_manager",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_constants",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_decrypt",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_encrypt",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_kem",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_key_gen",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_sender",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_util",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:sender_intermediate_cert_factory",
-    ],
-)
diff --git a/apps/paymentmethodtoken/README.md b/apps/paymentmethodtoken/README.md
deleted file mode 100644
index 936026c..0000000
--- a/apps/paymentmethodtoken/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# An implementation of [Google Payment Method Token](https://developers.google.com/pay/api/payment-data-cryptography).
-
-## Latest release
-
-The most recent release is
-[1.7.0](https://github.com/google/tink/releases/tag/v1.7.0), released
-2022-08-09. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-paymentmethodtoken/1.7.0).
-
-The Maven group ID is `com.google.crypto.tink`, and the artifact ID is
-`apps-paymentmethodtoken`.
-
-To add a dependency using Maven:
-
-```xml
-<dependency>
-  <groupId>com.google.crypto.tink</groupId>
-  <artifactId>apps-paymentmethodtoken</artifactId>
-  <version>1.7.0</version>
-</dependency>
-```
-
-## Snapshots
-
-Snapshots of this app built from the master branch are available through Maven
-using version `HEAD-SNAPSHOT`. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-paymentmethodtoken/HEAD-SNAPSHOT).
-
-To add a dependency using Maven:
-
-```xml
-<repositories>
-<repository>
-  <id>sonatype-snapshots</id>
-  <name>sonatype-snapshots</name>
-  <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
-  <snapshots>
-    <enabled>true</enabled>
-    <updatePolicy>always</updatePolicy>
-  </snapshots>
-  <releases>
-    <updatePolicy>always</updatePolicy>
-  </releases>
-</repository>
-</repositories>
-
-<dependency>
-  <groupId>com.google.crypto.tink</groupId>
-  <artifactId>apps-paymentmethodtoken</artifactId>
-  <version>HEAD-SNAPSHOT</version>
-</dependency>
-```
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel
deleted file mode 100644
index 434db56..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel
+++ /dev/null
@@ -1,125 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-java_library(
-    name = "google_payments_public_keys_manager",
-    srcs = ["GooglePaymentsPublicKeysManager.java"],
-    deps = [
-        "@maven//:com_google_http_client_google_http_client",
-        "@tink_java//src/main/java/com/google/crypto/tink/util:keys_downloader",
-    ],
-)
-
-java_library(
-    name = "payment_method_token_hybrid_decrypt",
-    srcs = ["PaymentMethodTokenHybridDecrypt.java"],
-    deps = [
-        ":payment_method_token_constants",
-        ":payment_method_token_recipient_kem",
-        ":payment_method_token_util",
-        "@maven//:com_google_code_gson_gson",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:bytes",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:hkdf",
-    ],
-)
-
-java_library(
-    name = "payment_method_token_sender",
-    srcs = ["PaymentMethodTokenSender.java"],
-    deps = [
-        ":payment_method_token_constants",
-        ":payment_method_token_hybrid_encrypt",
-        ":payment_method_token_util",
-        "@maven//:com_google_code_gson_gson",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink:public_key_sign",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-    ],
-)
-
-java_library(
-    name = "payment_method_token_recipient_key_gen",
-    srcs = ["PaymentMethodTokenRecipientKeyGen.java"],
-    deps = [
-        ":payment_method_token_constants",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-    ],
-)
-
-java_library(
-    name = "payment_method_token_constants",
-    srcs = ["PaymentMethodTokenConstants.java"],
-    deps = [
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:enums",
-    ],
-)
-
-java_library(
-    name = "payment_method_token_recipient_kem",
-    srcs = ["PaymentMethodTokenRecipientKem.java"],
-)
-
-java_library(
-    name = "payment_method_token_hybrid_encrypt",
-    srcs = ["PaymentMethodTokenHybridEncrypt.java"],
-    deps = [
-        ":payment_method_token_constants",
-        ":payment_method_token_util",
-        "@maven//:com_google_code_gson_gson",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecies_hkdf_sender_kem",
-    ],
-)
-
-java_library(
-    name = "payment_method_token_recipient",
-    srcs = ["PaymentMethodTokenRecipient.java"],
-    deps = [
-        ":google_payments_public_keys_manager",
-        ":payment_method_token_constants",
-        ":payment_method_token_hybrid_decrypt",
-        ":payment_method_token_recipient_kem",
-        ":payment_method_token_util",
-        "@maven//:com_google_code_gson_gson",
-        "@maven//:joda_time_joda_time",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_verify_jce",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-    ],
-)
-
-java_library(
-    name = "sender_intermediate_cert_factory",
-    srcs = ["SenderIntermediateCertFactory.java"],
-    deps = [
-        ":payment_method_token_constants",
-        ":payment_method_token_util",
-        "@maven//:com_google_code_gson_gson",
-        "@tink_java//src/main/java/com/google/crypto/tink:public_key_sign",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-    ],
-)
-
-java_library(
-    name = "payment_method_token_util",
-    srcs = ["PaymentMethodTokenUtil.java"],
-    deps = [
-        ":payment_method_token_constants",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:bytes",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
-    ],
-)
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeysManager.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeysManager.java
deleted file mode 100644
index 0da08d2..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeysManager.java
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.api.client.http.HttpTransport;
-import com.google.api.client.http.javanet.NetHttpTransport;
-import com.google.crypto.tink.util.KeysDownloader;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * Thread-safe Google Payments public key manager.
- *
- * <p>For best performance, use the {@link GooglePaymentsPublicKeysManager#INSTANCE_PRODUCTION} for
- * production environment or {@link GooglePaymentsPublicKeysManager#INSTANCE_TEST} for test
- * environment.
- *
- * <p>If you need extra customizations for your use, we recommend you to use {@link
- * GooglePaymentsPublicKeysManager.Builder} to construct an instance and keep it as a singleton in a
- * static final variable across requests.
- *
- * <p>When initializing your server, we also recommend that you call {@link #refreshInBackground()}
- * to proactively fetch the keys.
- *
- * @since 1.0.0
- */
-public class GooglePaymentsPublicKeysManager {
-  /** Default HTTP transport used by this class. */
-  public static final NetHttpTransport DEFAULT_HTTP_TRANSPORT =
-      new NetHttpTransport.Builder().build();
-  /** URL to fetch keys for environment production. */
-  public static final String KEYS_URL_PRODUCTION =
-      "https://payments.developers.google.com/paymentmethodtoken/keys.json";
-  /** URL to fetch keys for environment test. */
-  public static final String KEYS_URL_TEST =
-      "https://payments.developers.google.com/paymentmethodtoken/test/keys.json";
-
-  private static final Executor DEFAULT_BACKGROUND_EXECUTOR = Executors.newCachedThreadPool();
-
-  private final KeysDownloader downloader;
-
-  /**
-   * Instance configured to talk to fetch keys from production environment (from {@link
-   * GooglePaymentsPublicKeysManager#KEYS_URL_PRODUCTION}).
-   */
-  public static final GooglePaymentsPublicKeysManager INSTANCE_PRODUCTION =
-      new GooglePaymentsPublicKeysManager(
-          DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_PRODUCTION);
-  /**
-   * Instance configured to talk to fetch keys from test environment (from {@link
-   * GooglePaymentsPublicKeysManager#KEYS_URL_TEST}).
-   */
-  public static final GooglePaymentsPublicKeysManager INSTANCE_TEST =
-      new GooglePaymentsPublicKeysManager(
-          DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, KEYS_URL_TEST);
-
-  GooglePaymentsPublicKeysManager(
-      Executor backgroundExecutor, HttpTransport httpTransport, String keysUrl) {
-    this.downloader =
-        new KeysDownloader.Builder()
-            .setUrl(keysUrl)
-            .setExecutor(backgroundExecutor)
-            .setHttpTransport(httpTransport)
-            .build();
-  }
-
-  HttpTransport getHttpTransport() {
-    return downloader.getHttpTransport();
-  }
-
-  String getUrl() {
-    return downloader.getUrl();
-  }
-
-  /**
-   * Returns a string containing a JSON with the Google public signing keys.
-   *
-   * <p>Meant to be called by {@link PaymentMethodTokenRecipient}.
-   */
-  String getTrustedSigningKeysJson() throws IOException {
-    return this.downloader.download();
-  }
-
-  /** Fetches keys in the background. */
-  public void refreshInBackground() {
-    downloader.refreshInBackground();
-  }
-
-  /**
-   * Builder for {@link GooglePaymentsPublicKeysManager}.
-   *
-   * @since 1.0.0
-   */
-  public static class Builder {
-    private HttpTransport httpTransport = DEFAULT_HTTP_TRANSPORT;
-    private String keysUrl = KEYS_URL_PRODUCTION;
-
-    public Builder setKeysUrl(String keysUrl) {
-      this.keysUrl = keysUrl;
-      return this;
-    }
-
-    /**
-     * Sets the HTTP transport.
-     *
-     * <p>You generally should not need to set a custom transport as the default transport {@link
-     * GooglePaymentsPublicKeysManager#DEFAULT_HTTP_TRANSPORT} should be suited for most use cases.
-     */
-    public Builder setHttpTransport(HttpTransport httpTransport) {
-      this.httpTransport = httpTransport;
-      return this;
-    }
-
-    public GooglePaymentsPublicKeysManager build() {
-      // If all parameters are equal to the existing singleton instances, returning them instead.
-      // This is more a safe guard if users of this class construct a new class and forget to
-      // save in a singleton.
-      for (GooglePaymentsPublicKeysManager instance :
-          Arrays.asList(INSTANCE_PRODUCTION, INSTANCE_TEST)) {
-        if (instance.getHttpTransport() == httpTransport && instance.getUrl().equals(keysUrl)) {
-          return instance;
-        }
-      }
-      return new GooglePaymentsPublicKeysManager(
-          DEFAULT_BACKGROUND_EXECUTOR, httpTransport, keysUrl);
-    }
-  }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java
deleted file mode 100644
index 416a426..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenConstants.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Enums.HashType;
-import java.nio.charset.StandardCharsets;
-
-/** Various constants. */
-final class PaymentMethodTokenConstants {
-  public static final String GOOGLE_SENDER_ID = "Google";
-  public static final String HMAC_SHA256_ALGO = "HmacSha256";
-  public static final byte[] HKDF_EMPTY_SALT = new byte[0];
-  public static final byte[] GOOGLE_CONTEXT_INFO_ECV1 = "Google".getBytes(StandardCharsets.UTF_8);
-  public static final String AES_CTR_ALGO = "AES/CTR/NoPadding";
-  // Zero IV is fine here because each encryption uses a unique key.
-  public static final byte[] AES_CTR_ZERO_IV = new byte[16];
-  public static final EllipticCurves.CurveType P256_CURVE_TYPE = EllipticCurves.CurveType.NIST_P256;
-  public static final EllipticCurves.PointFormatType UNCOMPRESSED_POINT_FORMAT =
-      EllipticCurves.PointFormatType.UNCOMPRESSED;
-  public static final String PROTOCOL_VERSION_EC_V1 = "ECv1";
-  public static final String PROTOCOL_VERSION_EC_V2 = "ECv2";
-  public static final String PROTOCOL_VERSION_EC_V2_SIGNING_ONLY = "ECv2SigningOnly";
-  public static final HashType ECDSA_HASH_SHA256 = HashType.SHA256;
-
-  public static final String JSON_ENCRYPTED_MESSAGE_KEY = "encryptedMessage";
-  public static final String JSON_EPHEMERAL_PUBLIC_KEY = "ephemeralPublicKey";
-  public static final String JSON_INTERMEDIATE_SIGNING_KEY = "intermediateSigningKey";
-  public static final String JSON_KEY_EXPIRATION_KEY = "keyExpiration";
-  public static final String JSON_KEY_VALUE_KEY = "keyValue";
-  public static final String JSON_MESSAGE_EXPIRATION_KEY = "messageExpiration";
-  public static final String JSON_PROTOCOL_VERSION_KEY = "protocolVersion";
-  public static final String JSON_SIGNATURES_KEY = "signatures";
-  public static final String JSON_SIGNATURE_KEY = "signature";
-  public static final String JSON_SIGNED_KEY_KEY = "signedKey";
-  public static final String JSON_SIGNED_MESSAGE_KEY = "signedMessage";
-  public static final String JSON_TAG_KEY = "tag";
-
-  /** Represents configuration regarding each protocol version. */
-  enum ProtocolVersionConfig {
-    EC_V1(
-        /* protocolVersion= */ PROTOCOL_VERSION_EC_V1,
-        /* aesCtrKeySize= */ 128 / 8,
-        /* hmacSha256KeySize= */ 128 / 8,
-        /* isEncryptionRequired= */ true,
-        /* supportsIntermediateSigningKeys= */ false),
-    EC_V2(
-        /* protocolVersion= */ PROTOCOL_VERSION_EC_V2,
-        /* aesCtrKeySize= */ 256 / 8,
-        /* hmacSha256KeySize= */ 256 / 8,
-        /* isEncryptionRequired= */ true,
-        /* supportsIntermediateSigningKeys= */ true),
-    EC_V2_SIGNING_ONLY(
-        /* protocolVersion= */ PROTOCOL_VERSION_EC_V2_SIGNING_ONLY,
-        /* aesCtrKeySize= */ 256 / 8,
-        /* hmacSha256KeySize= */ 256 / 8,
-        /* isEncryptionRequired= */ false,
-        /* supportsIntermediateSigningKeys= */ true);
-
-    public final String protocolVersion;
-    public final int aesCtrKeySize;
-    public final int hmacSha256KeySize;
-    public final boolean isEncryptionRequired;
-    public final boolean supportsIntermediateSigningKeys;
-
-    ProtocolVersionConfig(
-        String protocolVersion,
-        int aesCtrKeySize,
-        int hmacSha256KeySize,
-        boolean isEncryptionRequired,
-        boolean supportsIntermediateSigningKeys) {
-      this.protocolVersion = protocolVersion;
-      this.aesCtrKeySize = aesCtrKeySize;
-      this.hmacSha256KeySize = hmacSha256KeySize;
-      this.isEncryptionRequired = isEncryptionRequired;
-      this.supportsIntermediateSigningKeys = supportsIntermediateSigningKeys;
-    }
-
-    public static ProtocolVersionConfig forProtocolVersion(String protocolVersion) {
-      for (ProtocolVersionConfig protocolVersionConfig : ProtocolVersionConfig.values()) {
-        if (protocolVersionConfig.protocolVersion.equals(protocolVersion)) {
-          return protocolVersionConfig;
-        }
-      }
-      throw new IllegalArgumentException("Unknown protocol version: " + protocolVersion);
-    }
-  }
-
-  private PaymentMethodTokenConstants() {}
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java
deleted file mode 100644
index 6d2637a..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecrypt.java
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.Bytes;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Hkdf;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.Arrays;
-
-/**
- * A {@link HybridDecrypt} implementation for the hybrid encryption used in <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- */
-class PaymentMethodTokenHybridDecrypt implements HybridDecrypt {
-  private final PaymentMethodTokenRecipientKem recipientKem;
-  private final ProtocolVersionConfig protocolVersionConfig;
-
-  PaymentMethodTokenHybridDecrypt(
-      final ECPrivateKey recipientPrivateKey, ProtocolVersionConfig protocolVersionConfig)
-      throws GeneralSecurityException {
-    this(
-        new PaymentMethodTokenRecipientKem() {
-          @Override
-          public byte[] computeSharedSecret(final byte[] ephemeralPublicKey)
-              throws GeneralSecurityException {
-            ECPublicKey publicKey =
-                EllipticCurves.getEcPublicKey(
-                    recipientPrivateKey.getParams(),
-                    PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
-                    ephemeralPublicKey);
-            return EllipticCurves.computeSharedSecret(recipientPrivateKey, publicKey);
-          }
-        },
-        protocolVersionConfig);
-  }
-
-  PaymentMethodTokenHybridDecrypt(
-      final PaymentMethodTokenRecipientKem recipientKem,
-      ProtocolVersionConfig protocolVersionConfig) {
-    this.recipientKem = recipientKem;
-    this.protocolVersionConfig = protocolVersionConfig;
-  }
-
-  @Override
-  public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo)
-      throws GeneralSecurityException {
-    try {
-      JsonObject json = JsonParser.parseString(new String(ciphertext, UTF_8)).getAsJsonObject();
-      validate(json);
-      byte[] demKey = kem(json, contextInfo);
-      return dem(json, demKey);
-    } catch (JsonParseException | IllegalStateException e) {
-      throw new GeneralSecurityException("cannot decrypt; failed to parse JSON", e);
-    }
-  }
-
-  private byte[] kem(JsonObject json, final byte[] contextInfo) throws GeneralSecurityException {
-    int demKeySize = protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize;
-    byte[] ephemeralPublicKey =
-        Base64.decode(
-            json.get(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY).getAsString());
-    byte[] sharedSecret = recipientKem.computeSharedSecret(ephemeralPublicKey);
-    return Hkdf.computeEciesHkdfSymmetricKey(
-        ephemeralPublicKey,
-        sharedSecret,
-        PaymentMethodTokenConstants.HMAC_SHA256_ALGO,
-        PaymentMethodTokenConstants.HKDF_EMPTY_SALT,
-        contextInfo,
-        demKeySize);
-  }
-
-  private byte[] dem(JsonObject json, final byte[] demKey) throws GeneralSecurityException {
-    byte[] hmacSha256Key =
-        Arrays.copyOfRange(demKey, protocolVersionConfig.aesCtrKeySize, demKey.length);
-    byte[] encryptedMessage =
-        Base64.decode(
-            json.get(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY).getAsString());
-    byte[] computedTag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, encryptedMessage);
-    byte[] expectedTag =
-        Base64.decode(json.get(PaymentMethodTokenConstants.JSON_TAG_KEY).getAsString());
-    if (!Bytes.equal(expectedTag, computedTag)) {
-      throw new GeneralSecurityException("cannot decrypt; invalid MAC");
-    }
-    byte[] aesCtrKey = Arrays.copyOf(demKey, protocolVersionConfig.aesCtrKeySize);
-    return PaymentMethodTokenUtil.aesCtr(aesCtrKey, encryptedMessage);
-  }
-
-  private void validate(JsonObject payload) throws GeneralSecurityException {
-    if (!payload.has(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY)
-        || !payload.has(PaymentMethodTokenConstants.JSON_TAG_KEY)
-        || !payload.has(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY)
-        || payload.size() != 3) {
-      throw new GeneralSecurityException(
-          "The payload must contain exactly encryptedMessage, tag and ephemeralPublicKey");
-    }
-  }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java
deleted file mode 100644
index ff3a418..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncrypt.java
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EciesHkdfSenderKem;
-import com.google.gson.JsonObject;
-import com.google.gson.internal.Streams;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPublicKey;
-import java.util.Arrays;
-
-/**
- * A {@link HybridEncrypt} implementation for the hybrid encryption used in <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- */
-class PaymentMethodTokenHybridEncrypt implements HybridEncrypt {
-  private final EciesHkdfSenderKem senderKem;
-  private final ProtocolVersionConfig protocolVersionConfig;
-
-  public PaymentMethodTokenHybridEncrypt(
-      final ECPublicKey recipientPublicKey, final ProtocolVersionConfig protocolVersionConfig) {
-    this.senderKem = new EciesHkdfSenderKem(recipientPublicKey);
-    this.protocolVersionConfig = protocolVersionConfig;
-  }
-
-  static String jsonEncodeCiphertext(byte[] ciphertext, byte[] tag, byte[] ephemeralPublicKey)
-      throws GeneralSecurityException {
-    JsonObject result = new JsonObject();
-    result.addProperty(
-        PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY, Base64.encode(ciphertext));
-    result.addProperty(
-        PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY, Base64.encode(ephemeralPublicKey));
-    result.addProperty(PaymentMethodTokenConstants.JSON_TAG_KEY, Base64.encode(tag));
-    StringWriter stringWriter = new StringWriter();
-    JsonWriter jsonWriter = new JsonWriter(stringWriter);
-    jsonWriter.setHtmlSafe(true);
-    try {
-      Streams.write(result, jsonWriter);
-      return stringWriter.toString();
-    } catch (IOException e) {
-      throw new GeneralSecurityException("cannot encrypt; JSON error", e);
-    }
-  }
-
-  @Override
-  public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo)
-      throws GeneralSecurityException {
-    int symmetricKeySize =
-        protocolVersionConfig.aesCtrKeySize + protocolVersionConfig.hmacSha256KeySize;
-    EciesHkdfSenderKem.KemKey kemKey =
-        senderKem.generateKey(
-            PaymentMethodTokenConstants.HMAC_SHA256_ALGO,
-            PaymentMethodTokenConstants.HKDF_EMPTY_SALT,
-            contextInfo,
-            symmetricKeySize,
-            PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT);
-    byte[] aesCtrKey = Arrays.copyOf(kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize);
-    byte[] ciphertext = PaymentMethodTokenUtil.aesCtr(aesCtrKey, plaintext);
-    byte[] hmacSha256Key =
-        Arrays.copyOfRange(
-            kemKey.getSymmetricKey(), protocolVersionConfig.aesCtrKeySize, symmetricKeySize);
-    byte[] tag = PaymentMethodTokenUtil.hmacSha256(hmacSha256Key, ciphertext);
-    byte[] ephemeralPublicKey = kemKey.getKemBytes();
-
-    String jsonEncodedCiphertext = jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey);
-    return jsonEncodedCiphertext.getBytes(UTF_8);
-  }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipient.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipient.java
deleted file mode 100644
index 5f6cfe7..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipient.java
+++ /dev/null
@@ -1,629 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaVerifyJce;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.joda.time.Instant;
-
-/**
- * An implementation of the recipient side of <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- *
- * <h3>Warning</h3>
- *
- * <p>This implementation only supports versions {@code ECv1}, {@code ECv2} and {@code
- * ECv2SigningOnly}.
- *
- * <h3>Typical usage</h3>
- *
- * <pre>{@code
- * PaymentMethodTokenRecipient recipient = new PaymentMethodTokenRecipient.Builder()
- *     .fetchSenderVerifyingKeysWith(
- *         GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
- *     .recipientId(recipientId)
- *     // Multiple recipient private keys can be added to support graceful key rotations
- *     .addRecipientPrivateKey(recipientPrivateKey1)
- *     .addRecipientPrivateKey(recipientPrivateKey2)
- *     .build();
- * String ciphertext = ...;
- * String plaintext = recipient.unseal(ciphertext);
- * }</pre>
- *
- * <h3>Custom decryption</h3>
- *
- * <p>Recipients that store private keys in HSM can provide implementations of {@link
- * PaymentMethodTokenRecipientKem} and configure Tink to use their custom code with {@link
- * PaymentMethodTokenRecipient.Builder#addRecipientKem}.
- *
- * <pre>{@code
- * PaymentMethodTokenRecipient recipient = new PaymentMethodTokenRecipient.Builder()
- *     .fetchSenderVerifyingKeysWith(
- *         GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
- *     .recipientId(recipientId)
- *     .addRecipientKem(new MyPaymentMethodTokenRecipientKem())
- *     .build();
- * String ciphertext = ...;
- * String plaintext = recipient.unseal(ciphertext);
- * }</pre>
- *
- * @see <a href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment
- *     Method Token standard</a>
- * @since 1.0.0
- */
-public final class PaymentMethodTokenRecipient {
-  private final String protocolVersion;
-  private final List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders;
-  private final List<HybridDecrypt> hybridDecrypters = new ArrayList<>();
-  private final String senderId;
-  private final String recipientId;
-
-  PaymentMethodTokenRecipient(
-      String protocolVersion,
-      List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders,
-      String senderId,
-      List<ECPrivateKey> recipientPrivateKeys,
-      List<PaymentMethodTokenRecipientKem> recipientKems,
-      String recipientId)
-      throws GeneralSecurityException {
-    if (!protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
-        && !protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-        && !protocolVersion.equals(
-            PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)) {
-      throw new IllegalArgumentException("invalid version: " + protocolVersion);
-    }
-    this.protocolVersion = protocolVersion;
-    if (senderVerifyingKeysProviders == null || senderVerifyingKeysProviders.isEmpty()) {
-      throw new IllegalArgumentException(
-          "must set at least one way to get sender's verifying key using"
-              + " Builder.fetchSenderVerifyingKeysWith or Builder.senderVerifyingKeys");
-    }
-    this.senderVerifyingKeysProviders = senderVerifyingKeysProviders;
-    this.senderId = senderId;
-
-    ProtocolVersionConfig protocolVersionConfig =
-        ProtocolVersionConfig.forProtocolVersion(protocolVersion);
-    if (protocolVersionConfig.isEncryptionRequired) {
-      if (recipientPrivateKeys.isEmpty() && recipientKems.isEmpty()) {
-        throw new IllegalArgumentException(
-            "must add at least one recipient's decrypting key using Builder.addRecipientPrivateKey "
-                + "or Builder.addRecipientKem");
-      }
-      for (ECPrivateKey privateKey : recipientPrivateKeys) {
-        hybridDecrypters.add(
-            new PaymentMethodTokenHybridDecrypt(privateKey, protocolVersionConfig));
-      }
-      for (PaymentMethodTokenRecipientKem kem : recipientKems) {
-        hybridDecrypters.add(new PaymentMethodTokenHybridDecrypt(kem, protocolVersionConfig));
-      }
-    } else {
-      if (!recipientPrivateKeys.isEmpty() || !recipientKems.isEmpty()) {
-        throw new IllegalArgumentException(
-            "must not set private decrypting key using Builder.addRecipientPrivateKey "
-                + "or Builder.addRecipientDecrypter");
-      }
-    }
-
-    if (recipientId == null) {
-      throw new IllegalArgumentException("must set recipient Id using Builder.recipientId");
-    }
-    this.recipientId = recipientId;
-  }
-
-  private PaymentMethodTokenRecipient(Builder builder) throws GeneralSecurityException {
-    this(
-        builder.protocolVersion,
-        builder.senderVerifyingKeysProviders,
-        builder.senderId,
-        builder.recipientPrivateKeys,
-        builder.recipientKems,
-        builder.recipientId);
-  }
-
-  /**
-   * Builder for {@link PaymentMethodTokenRecipient}.
-   *
-   * @since 1.0.0
-   */
-  public static class Builder {
-    private String protocolVersion = PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1;
-    private String senderId = PaymentMethodTokenConstants.GOOGLE_SENDER_ID;
-    private String recipientId = null;
-    private final List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders =
-        new ArrayList<SenderVerifyingKeysProvider>();
-    private final List<ECPrivateKey> recipientPrivateKeys = new ArrayList<ECPrivateKey>();
-    private final List<PaymentMethodTokenRecipientKem> recipientKems = new ArrayList<>();
-
-    public Builder() {}
-
-    /** Sets the protocolVersion. */
-    public Builder protocolVersion(String val) {
-      protocolVersion = val;
-      return this;
-    }
-
-    /** Sets the sender Id. */
-    public Builder senderId(String val) {
-      senderId = val;
-      return this;
-    }
-
-    /** Sets the recipient Id. */
-    public Builder recipientId(String val) {
-      recipientId = val;
-      return this;
-    }
-
-    /**
-     * Fetches verifying public keys of the sender using {@link GooglePaymentsPublicKeysManager}.
-     *
-     * <p>This is the preferred method of specifying the verifying public keys of the sender.
-     */
-    public Builder fetchSenderVerifyingKeysWith(
-        final GooglePaymentsPublicKeysManager googlePaymentsPublicKeysManager)
-        throws GeneralSecurityException {
-      this.senderVerifyingKeysProviders.add(
-          new SenderVerifyingKeysProvider() {
-            @Override
-            public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
-              try {
-                return parseTrustedSigningKeysJson(
-                    protocolVersion, googlePaymentsPublicKeysManager.getTrustedSigningKeysJson());
-              } catch (IOException e) {
-                throw new GeneralSecurityException("Failed to fetch keys!", e);
-              }
-            }
-          });
-      return this;
-    }
-
-    /**
-     * Sets the trusted verifying public keys of the sender.
-     *
-     * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
-     * sender, prefer calling {@link #fetchSenderVerifyingKeysWith} passing it an instance of {@link
-     * GooglePaymentsPublicKeysManager}. It will take care of fetching fresh keys and caching in
-     * memory. Only use this method if you can't use {@link #fetchSenderVerifyingKeysWith} and be
-     * aware you will need to handle Google key rotations yourself.
-     *
-     * <p>The given string is a JSON object formatted like the following:
-     *
-     * <pre>
-     * {
-     *   "keys": [
-     *     {
-     *       "keyValue": "encoded public key",
-     *       "protocolVersion": "ECv1"
-     *     },
-     *     {
-     *       "keyValue": "encoded public key",
-     *       "protocolVersion": "ECv1"
-     *     },
-     *   ],
-     * }
-     * </pre>
-     *
-     * <p>Each public key will be a base64 (no wrapping, padded) version of the key encoded in ASN.1
-     * type SubjectPublicKeyInfo defined in the X.509 standard.
-     */
-    public Builder senderVerifyingKeys(final String trustedSigningKeysJson)
-        throws GeneralSecurityException {
-      this.senderVerifyingKeysProviders.add(
-          new SenderVerifyingKeysProvider() {
-            @Override
-            public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
-              return parseTrustedSigningKeysJson(protocolVersion, trustedSigningKeysJson);
-            }
-          });
-      return this;
-    }
-
-    /**
-     * Adds a verifying public key of the sender.
-     *
-     * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
-     * sender, prefer calling {@link #fetchSenderVerifyingKeysWith} passing it an instance of {@link
-     * GooglePaymentsPublicKeysManager}. It will take care of fetching fresh keys and caching in
-     * memory. Only use this method if you can't use {@link #fetchSenderVerifyingKeysWith} and be
-     * aware you will need to handle Google key rotations yourself.
-     *
-     * <p>The public key is a base64 (no wrapping, padded) version of the key encoded in ASN.1 type
-     * SubjectPublicKeyInfo defined in the X.509 standard.
-     *
-     * <p>Multiple keys may be added. This utility will then verify any message signed with any of
-     * the private keys corresponding to the public keys added. Adding multiple keys is useful for
-     * handling key rotation.
-     */
-    public Builder addSenderVerifyingKey(final String val) throws GeneralSecurityException {
-      this.senderVerifyingKeysProviders.add(
-          new SenderVerifyingKeysProvider() {
-            @Override
-            public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
-              return Collections.singletonList(PaymentMethodTokenUtil.x509EcPublicKey(val));
-            }
-          });
-      return this;
-    }
-
-    /**
-     * Adds a verifying public key of the sender.
-     *
-     * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
-     * sender, prefer calling {@link #fetchSenderVerifyingKeysWith} passing it an instance of {@link
-     * GooglePaymentsPublicKeysManager}. It will take care of fetching fresh keys and caching in
-     * memory. Only use this method if you can't use {@link #fetchSenderVerifyingKeysWith} and be
-     * aware you will need to handle Google key rotations yourself.
-     */
-    public Builder addSenderVerifyingKey(final ECPublicKey val) throws GeneralSecurityException {
-      this.senderVerifyingKeysProviders.add(
-          new SenderVerifyingKeysProvider() {
-            @Override
-            public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
-              return Collections.singletonList(val);
-            }
-          });
-      return this;
-    }
-
-    /**
-     * Adds the decryption private key of the recipient.
-     *
-     * <p>It must be base64 encoded PKCS8 private key.
-     */
-    public Builder addRecipientPrivateKey(String val) throws GeneralSecurityException {
-      return addRecipientPrivateKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val));
-    }
-
-    /** Adds the decryption private key of the recipient. */
-    public Builder addRecipientPrivateKey(ECPrivateKey val) throws GeneralSecurityException {
-      recipientPrivateKeys.add(val);
-      return this;
-    }
-
-    /**
-     * Adds a custom {@link PaymentMethodTokenRecipientKem}.
-     *
-     * <p>This is useful for clients that store keys in an HSM and need a more control on how the
-     * key is used. If you are not using an HSM, you probably should just use {@link
-     * #addRecipientPrivateKey}.
-     *
-     * @since 1.1.0
-     */
-    public Builder addRecipientKem(PaymentMethodTokenRecipientKem kem) {
-      recipientKems.add(kem);
-      return this;
-    }
-
-    public PaymentMethodTokenRecipient build() throws GeneralSecurityException {
-      return new PaymentMethodTokenRecipient(this);
-    }
-  }
-
-  /**
-   * Unseal the given {@code sealedMessage} by performing the necessary signature verification and
-   * decryption (if required) steps based on the protocolVersion.
-   */
-  public String unseal(final String sealedMessage) throws GeneralSecurityException {
-    try {
-      if (protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)) {
-        return unsealECV1(sealedMessage);
-      } else if (protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)) {
-        return unsealECV2(sealedMessage);
-      } else if (protocolVersion.equals(
-          PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)) {
-        return unsealECV2SigningOnly(sealedMessage);
-      }
-      throw new IllegalArgumentException("unsupported version: " + protocolVersion);
-    } catch (JsonParseException | IllegalStateException e) {
-      throw new GeneralSecurityException("cannot unseal; invalid JSON message", e);
-    }
-  }
-
-  private String unsealECV1(String sealedMessage) throws GeneralSecurityException {
-    JsonObject jsonMsg = JsonParser.parseString(sealedMessage).getAsJsonObject();
-    validateECV1(jsonMsg);
-    String signedMessage = verifyECV1(jsonMsg);
-    String decryptedMessage = decrypt(signedMessage);
-    validateMessage(decryptedMessage);
-    return decryptedMessage;
-  }
-
-  private String unsealECV2(String sealedMessage) throws GeneralSecurityException {
-    JsonObject jsonMsg = JsonParser.parseString(sealedMessage).getAsJsonObject();
-    validateECV2(jsonMsg);
-    String signedMessage = verifyECV2(jsonMsg);
-    String decryptedMessage = decrypt(signedMessage);
-    validateMessage(decryptedMessage);
-    return decryptedMessage;
-  }
-
-  private String unsealECV2SigningOnly(String sealedMessage) throws GeneralSecurityException {
-    JsonObject jsonMsg = JsonParser.parseString(sealedMessage).getAsJsonObject();
-    validateECV2(jsonMsg);
-    String message = verifyECV2(jsonMsg);
-    validateMessage(message);
-    return message;
-  }
-
-  private String verifyECV1(final JsonObject jsonMsg) throws GeneralSecurityException {
-    byte[] signature =
-        Base64.decode(jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY).getAsString());
-    String signedMessage =
-        jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString();
-    byte[] signedBytes = getSignedBytes(protocolVersion, signedMessage);
-    verify(
-        protocolVersion,
-        senderVerifyingKeysProviders,
-        Collections.singletonList(signature),
-        signedBytes);
-    return signedMessage;
-  }
-
-  private String verifyECV2(final JsonObject jsonMsg) throws GeneralSecurityException {
-    byte[] signature =
-        Base64.decode(jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY).getAsString());
-    String signedMessage =
-        jsonMsg.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString();
-    byte[] signedBytes = getSignedBytes(protocolVersion, signedMessage);
-    verify(
-        protocolVersion,
-        verifyIntermediateSigningKey(jsonMsg),
-        Collections.singletonList(signature),
-        signedBytes);
-    return signedMessage;
-  }
-
-  private byte[] getSignedBytes(String protocolVersion, String signedMessage)
-      throws GeneralSecurityException {
-    return PaymentMethodTokenUtil.toLengthValue(
-        // The order of the parameters matters.
-        senderId, recipientId, protocolVersion, signedMessage);
-  }
-
-  private void validateMessage(String decryptedMessage) throws GeneralSecurityException {
-    JsonObject decodedMessage;
-
-    try {
-      decodedMessage = JsonParser.parseString(decryptedMessage).getAsJsonObject();
-    } catch (JsonParseException | IllegalStateException e) {
-      // Message wasn't a valid JSON, so nothing to validate.
-      return;
-    }
-
-    // If message expiration is present, checking it.
-    if (decodedMessage.has(PaymentMethodTokenConstants.JSON_MESSAGE_EXPIRATION_KEY)) {
-      long expirationInMillis =
-          Long.parseLong(
-              decodedMessage
-                  .get(PaymentMethodTokenConstants.JSON_MESSAGE_EXPIRATION_KEY)
-                  .getAsString());
-      if (expirationInMillis <= Instant.now().getMillis()) {
-        throw new GeneralSecurityException("expired payload");
-      }
-    }
-  }
-
-  private static void verify(
-      final String protocolVersion,
-      final List<SenderVerifyingKeysProvider> senderVerifyingKeysProviders,
-      final List<byte[]> signatures,
-      final byte[] signedBytes)
-      throws GeneralSecurityException {
-    boolean verified = false;
-    for (SenderVerifyingKeysProvider verifyingKeysProvider : senderVerifyingKeysProviders) {
-      for (ECPublicKey publicKey : verifyingKeysProvider.get(protocolVersion)) {
-        EcdsaVerifyJce verifier =
-            new EcdsaVerifyJce(
-                publicKey, PaymentMethodTokenConstants.ECDSA_HASH_SHA256, EcdsaEncoding.DER);
-        for (byte[] signature : signatures) {
-          try {
-            verifier.verify(signature, signedBytes);
-            // No exception means the signature is valid.
-            verified = true;
-          } catch (GeneralSecurityException e) {
-            // ignored, try again
-          }
-        }
-      }
-    }
-    if (!verified) {
-      throw new GeneralSecurityException("cannot verify signature");
-    }
-  }
-
-  private String decrypt(String ciphertext) throws GeneralSecurityException {
-    for (HybridDecrypt hybridDecrypter : hybridDecrypters) {
-      try {
-        byte[] cleartext =
-            hybridDecrypter.decrypt(
-                ciphertext.getBytes(UTF_8), PaymentMethodTokenConstants.GOOGLE_CONTEXT_INFO_ECV1);
-        return new String(cleartext, UTF_8);
-      } catch (GeneralSecurityException e) {
-        // ignored, try again
-      }
-    }
-    throw new GeneralSecurityException("cannot decrypt");
-  }
-
-  private void validateECV1(final JsonObject jsonMsg) throws GeneralSecurityException {
-    if (!jsonMsg.has(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY)
-        || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY)
-        || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY)
-        || jsonMsg.size() != 3) {
-      throw new GeneralSecurityException(
-          "ECv1 message must contain exactly protocolVersion, signature and signedMessage");
-    }
-    String version =
-        jsonMsg.get(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY).getAsString();
-    if (!version.equals(protocolVersion)) {
-      throw new GeneralSecurityException("invalid version: " + version);
-    }
-  }
-
-  private void validateECV2(final JsonObject jsonMsg) throws GeneralSecurityException {
-    if (!jsonMsg.has(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY)
-        || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY)
-        || !jsonMsg.has(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY)
-        || !jsonMsg.has(PaymentMethodTokenConstants.JSON_INTERMEDIATE_SIGNING_KEY)
-        || jsonMsg.size() != 4) {
-      throw new GeneralSecurityException(
-          protocolVersion
-              + " message must contain exactly protocolVersion, intermediateSigningKey, "
-              + "signature and signedMessage");
-    }
-    String version =
-        jsonMsg.get(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY).getAsString();
-    if (!version.equals(protocolVersion)) {
-      throw new GeneralSecurityException("invalid version: " + version);
-    }
-  }
-
-  /**
-   * Verifies the intermediate key and returns a singleton list containing of {@code
-   * SenderVerifyingKeysProvider} that provides the verified key.
-   */
-  private List<SenderVerifyingKeysProvider> verifyIntermediateSigningKey(JsonObject jsonMsg)
-      throws GeneralSecurityException {
-    JsonObject intermediateSigningKey =
-        jsonMsg.get(PaymentMethodTokenConstants.JSON_INTERMEDIATE_SIGNING_KEY).getAsJsonObject();
-    validateIntermediateSigningKey(intermediateSigningKey);
-    ArrayList<byte[]> signatures = new ArrayList<>();
-    JsonArray signaturesJson =
-        intermediateSigningKey
-            .get(PaymentMethodTokenConstants.JSON_SIGNATURES_KEY)
-            .getAsJsonArray();
-    for (int i = 0; i < signaturesJson.size(); i++) {
-      signatures.add(Base64.decode(signaturesJson.get(i).getAsString()));
-    }
-    String signedKeyAsString =
-        intermediateSigningKey.get(PaymentMethodTokenConstants.JSON_SIGNED_KEY_KEY).getAsString();
-    byte[] signedBytes =
-        PaymentMethodTokenUtil.toLengthValue(
-            // The order of the parameters matters.
-            senderId, protocolVersion, signedKeyAsString);
-    verify(protocolVersion, senderVerifyingKeysProviders, signatures, signedBytes);
-    JsonObject signedKey = JsonParser.parseString(signedKeyAsString).getAsJsonObject();
-    validateSignedKey(signedKey);
-    final String key = signedKey.get(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY).getAsString();
-    SenderVerifyingKeysProvider provider =
-        new SenderVerifyingKeysProvider() {
-          @Override
-          public List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException {
-            if (PaymentMethodTokenRecipient.this.protocolVersion.equals(protocolVersion)) {
-              return Collections.singletonList(PaymentMethodTokenUtil.x509EcPublicKey(key));
-            } else {
-              return Collections.emptyList();
-            }
-          }
-        };
-    return Collections.singletonList(provider);
-  }
-
-  private JsonObject validateIntermediateSigningKey(final JsonObject intermediateSigningKey)
-      throws GeneralSecurityException {
-    if (!intermediateSigningKey.has(PaymentMethodTokenConstants.JSON_SIGNATURES_KEY)
-        || !intermediateSigningKey.has(PaymentMethodTokenConstants.JSON_SIGNED_KEY_KEY)
-        || intermediateSigningKey.size() != 2) {
-      throw new GeneralSecurityException(
-          "intermediateSigningKey must contain exactly signedKey and signatures");
-    }
-    return intermediateSigningKey;
-  }
-
-  private void validateSignedKey(final JsonObject signedKey) throws GeneralSecurityException {
-    // Note: allowing further keys to be added so we can extend the protocol if needed in the future
-    if (!signedKey.has(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY)
-        || !signedKey.has(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY)) {
-      throw new GeneralSecurityException(
-          "intermediateSigningKey.signedKey must contain keyValue and keyExpiration");
-    }
-
-    // If message expiration is present, checking it.
-    long expirationInMillis =
-        Long.parseLong(
-            signedKey.get(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY).getAsString());
-    if (expirationInMillis <= Instant.now().getMillis()) {
-      throw new GeneralSecurityException("expired intermediateSigningKey");
-    }
-  }
-
-  private static List<ECPublicKey> parseTrustedSigningKeysJson(
-      String protocolVersion, String trustedSigningKeysJson) throws GeneralSecurityException {
-    List<ECPublicKey> senderVerifyingKeys = new ArrayList<>();
-    try {
-      JsonArray keys =
-          JsonParser.parseString(trustedSigningKeysJson)
-              .getAsJsonObject()
-              .get("keys")
-              .getAsJsonArray();
-      for (int i = 0; i < keys.size(); i++) {
-        JsonObject key = keys.get(i).getAsJsonObject();
-        if (protocolVersion.equals(
-            key.get(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY).getAsString())) {
-
-          if (key.has(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY)) {
-            // If message expiration is present, checking it.
-            long expirationInMillis =
-                Long.parseLong(
-                    key.get(PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY).getAsString());
-            if (expirationInMillis <= Instant.now().getMillis()) {
-              // Ignore expired keys
-              continue;
-            }
-          } else if (!protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)) {
-            // keyExpiration is required in all versions except ECv1, so if it is missing we should
-            // skip using this key.
-            // In ECv1 the expiration is optional because it is assumed that the caller is
-            // respecting the HTTP cache headers and not using the trustedSigningKeysJson that are
-            // expired according to the headers.
-            continue;
-          }
-
-          senderVerifyingKeys.add(
-              PaymentMethodTokenUtil.x509EcPublicKey(key.get("keyValue").getAsString()));
-        }
-      }
-    } catch (JsonParseException | IllegalStateException e) {
-      throw new GeneralSecurityException("failed to extract trusted signing public keys", e);
-    }
-    if (senderVerifyingKeys.isEmpty()) {
-      throw new GeneralSecurityException("no trusted keys are available for this protocol version");
-    }
-    return senderVerifyingKeys;
-  }
-
-  private interface SenderVerifyingKeysProvider {
-    List<ECPublicKey> get(String protocolVersion) throws GeneralSecurityException;
-  }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java
deleted file mode 100644
index 47d322b..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKem.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import java.security.GeneralSecurityException;
-
-/**
- * Interface for recipient's key encapsulation mechanism (KEM).
- *
- * <p>Google Pay's tokens are encrypted using ECIES which is a hybrid encryption mode consisting of
- * two steps: key encapsulation mechanisam (KEM) using Elliptic Curve Diffie Hellman (ECDH) and HKDF
- * and data encapsulation mechanism (DEM) using AES-CTR-HMAC.
- *
- * <p>During encryption, the KEM step takes the recipient's public key and produces a DEM key and an
- * ephemeral public key. The DEM key is then used to encrypt the credit card data, and the ephemeral
- * public key is sent as the <b>ephemeralPublicKey</b> field of the payload.
- *
- * <p>To decrypt, the recipient must use their private key to compute an ECDH shared secret from the
- * ephemeral public key, and from that derive the DEM key using HKDF. If the recipient keeps the
- * private key in a HSM, they cannot load the private key in Tink, but they can implement this
- * interface and configure Tink to use their custom KEM implementation with {@link
- * PaymentMethodTokenRecipient.Builder#addRecipientKem}.
- *
- * @see <a href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment
- *     Method Token standard</a>
- * @since 1.1.0
- */
-public interface PaymentMethodTokenRecipientKem {
-  /**
-   * Computes a shared secret from the {@code ephemeralPublicKey}, using ECDH.
-   *
-   * <p>{@code ephemeralPublicKey} is a point on the elliptic curve defined in the <a
-   * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
-   * Token standard</a>, encoded in uncompressed point format. In version ECv1 and ECv2 of the
-   * standard, the elliptic curve is NIST P-256.
-   *
-   * <p>Note that you only needs to compute the shared secret, but you don't have to derive the DEM
-   * key with HKDF -- that process is handled by Tink.
-   */
-  byte[] computeSharedSecret(final byte[] ephemeralPublicKey) throws GeneralSecurityException;
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java
deleted file mode 100644
index 2713854..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientKeyGen.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPublicKey;
-
-/**
- * A util that generates key pairs for the recipient side of <a
- * href="https://developers.google.com/android-pay/integration/payment-token-cryptography">Google
- * Payment Method Token</a>.
- *
- * <h3>Usage</h3>
- *
- * <pre>
- * bazel build apps/paymentmethodtoken/...
- * ./bazel-bin/apps/paymentmethodtoken/recipientkeygen
- * </pre>
- *
- * <p>Running that command will generate a fresh key pair. The private/public key can be found in
- * private_key.bin/public_key.bin. The content of private_key.bin can be passed to {@link
- * PaymentMethodTokenRecipient.Builder#addRecipientPrivateKey} and the content of public_key.bin can
- * be passed to {@link PaymentMethodTokenSender.Builder#rawUncompressedRecipientPublicKey}.
- */
-public final class PaymentMethodTokenRecipientKeyGen {
-  private static final String PRIVATE_KEY_FILE = "private_key.bin";
-
-  private static final String PUBLIC_KEY_FILE = "public_key.bin";
-
-  private static void generateKey() throws GeneralSecurityException, IOException {
-    KeyPair keyPair = EllipticCurves.generateKeyPair(PaymentMethodTokenConstants.P256_CURVE_TYPE);
-    writeBase64(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded());
-
-    ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
-    writeBase64(
-        PUBLIC_KEY_FILE,
-        EllipticCurves.pointEncode(
-            PaymentMethodTokenConstants.P256_CURVE_TYPE,
-            PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
-            publicKey.getW()));
-  }
-
-  private static void writeBase64(String pathname, byte[] content) throws IOException {
-    File out = new File(pathname);
-    if (out.exists()) {
-      System.out.println("Please make sure that " + pathname + " does not exist.");
-      System.exit(-1);
-    }
-    FileOutputStream stream = new FileOutputStream(out);
-    stream.write(Base64.encode(content, Base64.DEFAULT | Base64.NO_WRAP));
-    stream.close();
-  }
-
-  public static void main(String[] args) throws GeneralSecurityException, IOException {
-    System.out.println("Generating key....");
-    generateKey();
-    System.out.println("done.");
-  }
-
-  private PaymentMethodTokenRecipientKeyGen() {}
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSender.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSender.java
deleted file mode 100644
index 25d0fe2..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSender.java
+++ /dev/null
@@ -1,335 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.PublicKeySign;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaSignJce;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import com.google.gson.internal.Streams;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-
-/**
- * An implementation of the sender side of <a
- * href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment Method
- * Token</a>.
- *
- * <h3>Warning</h3>
- *
- * <p>This implementation supports only versions {@code ECv1} and {@code ECv2}.
- *
- * <h3>Usage</h3>
- *
- * <pre>{@code
- * PaymentMethodTokenSender sender = new PaymentMethodTokenSender.Builder()
- *    .senderId(senderId)
- *    .senderSigningKey(senderPrivateKey)
- *    .recipientId(recipientId)
- *    .recipientPublicKey(recipientPublicKey)
- *    .build();
- * String plaintext = "blah";
- * String ciphertext = sender.seal(plaintext);
- * }</pre>
- *
- * @see <a href="https://developers.google.com/pay/api/payment-data-cryptography">Google Payment
- *     Method Token standard</a>
- * @since 1.0.0
- */
-public final class PaymentMethodTokenSender {
-  private final String protocolVersion;
-  private final ProtocolVersionConfig protocolVersionConfig;
-  private final PublicKeySign signer;
-  private final String senderIntermediateCert;
-  private final String senderId;
-  private final String recipientId;
-
-  private HybridEncrypt hybridEncrypter;
-
-  PaymentMethodTokenSender(Builder builder) throws GeneralSecurityException {
-    switch (builder.protocolVersion) {
-      case PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1:
-        validateV1(builder);
-        break;
-      case PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2:
-        validateV2(builder);
-        break;
-      case PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY:
-        validateV2SigningOnly(builder);
-        break;
-      default:
-        throw new IllegalArgumentException("invalid version: " + builder.protocolVersion);
-    }
-
-    this.protocolVersion = builder.protocolVersion;
-    this.protocolVersionConfig = ProtocolVersionConfig.forProtocolVersion(protocolVersion);
-    this.signer =
-        new EcdsaSignJce(
-            builder.senderIntermediateSigningKey != null
-                ? builder.senderIntermediateSigningKey
-                : builder.senderSigningKey,
-            PaymentMethodTokenConstants.ECDSA_HASH_SHA256,
-            EcdsaEncoding.DER);
-    this.senderId = builder.senderId;
-    if (protocolVersionConfig.isEncryptionRequired) {
-      this.hybridEncrypter =
-          new PaymentMethodTokenHybridEncrypt(builder.recipientPublicKey, protocolVersionConfig);
-    }
-    if (builder.recipientId == null) {
-      throw new IllegalArgumentException("must set recipient Id using Builder.recipientId");
-    }
-    this.recipientId = builder.recipientId;
-    this.senderIntermediateCert = builder.senderIntermediateCert;
-  }
-
-  /**
-   * Builder for {@link PaymentMethodTokenSender}.
-   *
-   * @since 1.0.0
-   */
-  public static class Builder {
-    private String protocolVersion = PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1;
-    private String senderId = PaymentMethodTokenConstants.GOOGLE_SENDER_ID;
-    private String recipientId = null;
-    private ECPrivateKey senderSigningKey = null;
-    private ECPrivateKey senderIntermediateSigningKey = null;
-    private String senderIntermediateCert = null;
-    private ECPublicKey recipientPublicKey = null;
-
-    public Builder() {}
-
-    /** Sets the protocolVersion. */
-    public Builder protocolVersion(String val) {
-      protocolVersion = val;
-      return this;
-    }
-
-    /** Sets the sender Id. */
-    public Builder senderId(String val) {
-      senderId = val;
-      return this;
-    }
-
-    /** Sets the recipient Id. */
-    public Builder recipientId(String val) {
-      recipientId = val;
-      return this;
-    }
-
-    /**
-     * Sets the signing key of the sender.
-     *
-     * <p>It must be base64 encoded PKCS8 private key.
-     */
-    public Builder senderSigningKey(String val) throws GeneralSecurityException {
-      senderSigningKey = PaymentMethodTokenUtil.pkcs8EcPrivateKey(val);
-      return this;
-    }
-
-    public Builder senderSigningKey(ECPrivateKey val) throws GeneralSecurityException {
-      senderSigningKey = val;
-      return this;
-    }
-
-    /**
-     * Sets the intermediate signing key of the sender.
-     *
-     * <p>It must be base64 encoded PKCS8 private key.
-     *
-     * @since 1.1.0
-     */
-    public Builder senderIntermediateSigningKey(String val) throws GeneralSecurityException {
-      return senderIntermediateSigningKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val));
-    }
-
-    /**
-     * Sets the intermediate signing key of the sender.
-     *
-     * @since 1.1.0
-     */
-    public Builder senderIntermediateSigningKey(ECPrivateKey val) throws GeneralSecurityException {
-      senderIntermediateSigningKey = val;
-      return this;
-    }
-
-    /**
-     * JSON containing sender intermediate signing key and a signature of it by the sender signing
-     * key.
-     *
-     * <p>This can be generated by {@link SenderIntermediateCertFactory}.
-     *
-     * @since 1.1.0
-     */
-    public Builder senderIntermediateCert(String val) throws GeneralSecurityException {
-      this.senderIntermediateCert = val;
-      return this;
-    }
-
-    /**
-     * Sets the encryption public key of the recipient.
-     *
-     * <p>The public key is a base64 (no wrapping, padded) version of the key encoded in ASN.1 type
-     * SubjectPublicKeyInfo defined in the X.509 standard.
-     */
-    public Builder recipientPublicKey(String val) throws GeneralSecurityException {
-      recipientPublicKey = PaymentMethodTokenUtil.x509EcPublicKey(val);
-      return this;
-    }
-
-    public Builder recipientPublicKey(ECPublicKey val) throws GeneralSecurityException {
-      recipientPublicKey = val;
-      return this;
-    }
-
-    /**
-     * Sets the encryption public key of the recipient.
-     *
-     * <p>The public key must be formatted as base64 encoded uncompressed point format. This format
-     * is described in more detail in "Public Key Cryptography For The Financial Services Industry:
-     * The Elliptic Curve Digital Signature Algorithm (ECDSA)", ANSI X9.62, 1998
-     */
-    public Builder rawUncompressedRecipientPublicKey(String val) throws GeneralSecurityException {
-      recipientPublicKey = PaymentMethodTokenUtil.rawUncompressedEcPublicKey(val);
-      return this;
-    }
-
-    public PaymentMethodTokenSender build() throws GeneralSecurityException {
-      return new PaymentMethodTokenSender(this);
-    }
-  }
-
-  /** Seals the input message according to the Payment Method Token specification. */
-  public String seal(final String message) throws GeneralSecurityException {
-    if (protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
-        || protocolVersion.equals(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-        || protocolVersion.equals(
-            PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)) {
-      return sealV1OrV2(message);
-    }
-    throw new GeneralSecurityException("Unsupported version: " + protocolVersion);
-  }
-
-  private String sealV1OrV2(final String message) throws GeneralSecurityException {
-    String signedMessage =
-        protocolVersionConfig.isEncryptionRequired
-            ? new String(
-                hybridEncrypter.encrypt(
-                    message.getBytes(UTF_8), PaymentMethodTokenConstants.GOOGLE_CONTEXT_INFO_ECV1),
-                UTF_8)
-            : message;
-    return signV1OrV2(signedMessage);
-  }
-
-  static String jsonEncodeSignedMessage(
-      String message, String protocolVersion, byte[] signature, String senderIntermediateCert)
-      throws GeneralSecurityException {
-    try {
-      JsonObject result = new JsonObject();
-      result.addProperty(PaymentMethodTokenConstants.JSON_SIGNATURE_KEY, Base64.encode(signature));
-      if (senderIntermediateCert != null) {
-        result.add(
-            PaymentMethodTokenConstants.JSON_INTERMEDIATE_SIGNING_KEY,
-            JsonParser.parseString(senderIntermediateCert).getAsJsonObject());
-      }
-      result.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, protocolVersion);
-      result.addProperty(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY, message);
-      StringWriter stringWriter = new StringWriter();
-      JsonWriter jsonWriter = new JsonWriter(stringWriter);
-      jsonWriter.setHtmlSafe(true);
-      Streams.write(result, jsonWriter);
-      return stringWriter.toString();
-    } catch (JsonParseException | IllegalStateException | IOException e) {
-      throw new GeneralSecurityException("cannot seal; JSON error", e);
-    }
-  }
-
-  private String signV1OrV2(String message) throws GeneralSecurityException {
-    byte[] toSignBytes =
-        PaymentMethodTokenUtil.toLengthValue(
-            // The order of the parameters matters.
-            senderId, recipientId, protocolVersion, message);
-    byte[] signature = signer.sign(toSignBytes);
-    return jsonEncodeSignedMessage(message, protocolVersion, signature, senderIntermediateCert);
-  }
-
-  private static void validateV1(Builder builder) {
-    // ECv1 signed payloads directly.
-    if (builder.senderSigningKey == null) {
-      throw new IllegalArgumentException(
-          "must set sender's signing key using Builder.senderSigningKey");
-    }
-    if (builder.senderIntermediateSigningKey != null) {
-      throw new IllegalArgumentException(
-          "must not set sender's intermediate signing key using "
-              + "Builder.senderIntermediateSigningKey");
-    }
-    if (builder.senderIntermediateCert != null) {
-      throw new IllegalArgumentException(
-          "must not set signed sender's intermediate signing key using "
-              + "Builder.senderIntermediateCert");
-    }
-    if (builder.recipientPublicKey == null) {
-      throw new IllegalArgumentException(
-          "must set recipient's public key using Builder.recipientPublicKey");
-    }
-  }
-
-  private static void validateV2(Builder builder) {
-    validateIntermediateSigningKeys(builder);
-    if (builder.recipientPublicKey == null) {
-      throw new IllegalArgumentException(
-          "must set recipient's public key using Builder.recipientPublicKey");
-    }
-  }
-
-  private static void validateV2SigningOnly(Builder builder) {
-    validateIntermediateSigningKeys(builder);
-    if (builder.recipientPublicKey != null) {
-      throw new IllegalArgumentException(
-          "must not set recipient's public key using Builder.recipientPublicKey");
-    }
-  }
-
-  private static void validateIntermediateSigningKeys(Builder builder) {
-    // ECv2 and newer protocols use an intermediate signing key.
-    if (builder.senderSigningKey != null) {
-      throw new IllegalArgumentException(
-          "must not set sender's signing key using Builder.senderSigningKey");
-    }
-    if (builder.senderIntermediateSigningKey == null) {
-      throw new IllegalArgumentException(
-          "must set sender's intermediate signing key using "
-              + "Builder.senderIntermediateSigningKey");
-    }
-    if (builder.senderIntermediateCert == null) {
-      throw new IllegalArgumentException(
-          "must set signed sender's intermediate signing key using "
-              + "Builder.senderIntermediateCert");
-    }
-  }
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java
deleted file mode 100644
index 185038d..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenUtil.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.Bytes;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EngineFactory;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/** Various helpers. */
-final class PaymentMethodTokenUtil {
-  static ECPublicKey rawUncompressedEcPublicKey(String rawUncompressedPublicKey)
-      throws GeneralSecurityException {
-    return EllipticCurves.getEcPublicKey(
-        PaymentMethodTokenConstants.P256_CURVE_TYPE,
-        PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
-        Base64.decode(rawUncompressedPublicKey));
-  }
-
-  static ECPublicKey x509EcPublicKey(String x509PublicKey) throws GeneralSecurityException {
-    return EllipticCurves.getEcPublicKey(Base64.decode(x509PublicKey));
-  }
-
-  static ECPrivateKey pkcs8EcPrivateKey(String pkcs8PrivateKey) throws GeneralSecurityException {
-    return EllipticCurves.getEcPrivateKey(Base64.decode(pkcs8PrivateKey));
-  }
-
-  static byte[] toLengthValue(String... chunks) throws GeneralSecurityException {
-    byte[] out = new byte[0];
-    for (String chunk : chunks) {
-      byte[] bytes = chunk.getBytes(StandardCharsets.UTF_8);
-      out = Bytes.concat(out, Bytes.intToByteArray(4, bytes.length));
-      out = Bytes.concat(out, bytes);
-    }
-    return out;
-  }
-
-  static byte[] aesCtr(final byte[] encryptionKey, final byte[] message)
-      throws GeneralSecurityException {
-    Cipher cipher = EngineFactory.CIPHER.getInstance(PaymentMethodTokenConstants.AES_CTR_ALGO);
-    cipher.init(
-        Cipher.ENCRYPT_MODE,
-        new SecretKeySpec(encryptionKey, "AES"),
-        new IvParameterSpec(PaymentMethodTokenConstants.AES_CTR_ZERO_IV));
-    return cipher.doFinal(message);
-  }
-
-  static byte[] hmacSha256(final byte[] macKey, final byte[] encryptedMessage)
-      throws GeneralSecurityException {
-    SecretKeySpec key = new SecretKeySpec(macKey, PaymentMethodTokenConstants.HMAC_SHA256_ALGO);
-    Mac mac = EngineFactory.MAC.getInstance(PaymentMethodTokenConstants.HMAC_SHA256_ALGO);
-    mac.init(key);
-    return mac.doFinal(encryptedMessage);
-  }
-
-  private PaymentMethodTokenUtil() {}
-}
diff --git a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java
deleted file mode 100644
index 0e3bfc7..0000000
--- a/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactory.java
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import com.google.crypto.tink.PublicKeySign;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaSignJce;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.internal.Streams;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Creates a signed certificate with the intermediate signing keys used by the sender in certain
- * protocol versions.
- *
- * @since 1.1.0
- */
-public class SenderIntermediateCertFactory {
-  private final List<PublicKeySign> signers;
-  private final String intermediateSigningKey;
-  private final String protocolVersion;
-  private final String senderId;
-  private final long expiration;
-
-  private SenderIntermediateCertFactory(
-      String protocolVersion,
-      String senderId,
-      List<ECPrivateKey> senderSigningKeys,
-      String intermediateSigningKey,
-      long expiration)
-      throws GeneralSecurityException {
-    if (!ProtocolVersionConfig.forProtocolVersion(protocolVersion)
-        .supportsIntermediateSigningKeys) {
-      throw new IllegalArgumentException("invalid version: " + protocolVersion);
-    }
-    if (senderSigningKeys.isEmpty()) {
-      throw new IllegalArgumentException(
-          "must add at least one sender's signing key using Builder.addSenderSigningKey");
-    }
-    if (expiration == 0) {
-      throw new IllegalArgumentException("must set expiration using Builder.expiration");
-    }
-    if (expiration < 0) {
-      throw new IllegalArgumentException("invalid negative expiration");
-    }
-    this.protocolVersion = protocolVersion;
-    this.senderId = senderId;
-    this.signers = new ArrayList<>();
-    for (ECPrivateKey senderSigningKey : senderSigningKeys) {
-      this.signers.add(
-          new EcdsaSignJce(
-              senderSigningKey, PaymentMethodTokenConstants.ECDSA_HASH_SHA256, EcdsaEncoding.DER));
-    }
-    this.intermediateSigningKey = intermediateSigningKey;
-    this.expiration = expiration;
-  }
-
-  /**
-   * Builder for {@link SenderIntermediateCertFactory}.
-   *
-   * @since 1.1.0
-   */
-  public static class Builder {
-    private final List<ECPrivateKey> senderSigningKeys = new ArrayList<>();
-    private String intermediateSigningKey;
-    private String protocolVersion = PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2;
-    private String senderId = PaymentMethodTokenConstants.GOOGLE_SENDER_ID;
-    private long expiration;
-
-    public Builder() {}
-
-    /** Sets the protocolVersion. */
-    public Builder protocolVersion(String val) {
-      protocolVersion = val;
-      return this;
-    }
-
-    /** Sets the sender Id. */
-    public Builder senderId(String val) {
-      senderId = val;
-      return this;
-    }
-
-    /** Sets the expiration in millis since epoch. */
-    public Builder expiration(long millisSinceEpoch) {
-      expiration = millisSinceEpoch;
-      return this;
-    }
-
-    /**
-     * Adds a signing key of the sender.
-     *
-     * <p>It must be base64 encoded PKCS8 private key.
-     */
-    public Builder addSenderSigningKey(String val) throws GeneralSecurityException {
-      return addSenderSigningKey(PaymentMethodTokenUtil.pkcs8EcPrivateKey(val));
-    }
-
-    /**
-     * Adds a signing key of the sender.
-     *
-     * <p>It must be base64 encoded PKCS8 private key.
-     */
-    public Builder addSenderSigningKey(ECPrivateKey val) throws GeneralSecurityException {
-      this.senderSigningKeys.add(val);
-      return this;
-    }
-
-    /**
-     * Sets the intermediate signing key being signed.
-     *
-     * <p>The public key specified here is a base64 (no wrapping, padded) version of the key encoded
-     * in ASN.1 type SubjectPublicKeyInfo defined in the X.509 standard.
-     */
-    public Builder senderIntermediateSigningKey(String val) throws GeneralSecurityException {
-      // Parsing to validate the format
-      PaymentMethodTokenUtil.x509EcPublicKey(val);
-      intermediateSigningKey = val;
-      return this;
-    }
-
-    public SenderIntermediateCertFactory build() throws GeneralSecurityException {
-      return new SenderIntermediateCertFactory(
-          protocolVersion, senderId, senderSigningKeys, intermediateSigningKey, expiration);
-    }
-  }
-
-  static String jsonEncodeSignedKey(String intermediateSigningKey, long expiration) {
-    try {
-      JsonObject jsonObj = new JsonObject();
-      jsonObj.addProperty(PaymentMethodTokenConstants.JSON_KEY_VALUE_KEY, intermediateSigningKey);
-      jsonObj.addProperty(
-          PaymentMethodTokenConstants.JSON_KEY_EXPIRATION_KEY, Long.toString(expiration));
-      StringWriter stringWriter = new StringWriter();
-      JsonWriter jsonWriter = new JsonWriter(stringWriter);
-      jsonWriter.setHtmlSafe(true);
-      Streams.write(jsonObj, jsonWriter);
-      return stringWriter.toString();
-    } catch (JsonParseException | IllegalStateException | IOException e) {
-      throw new AssertionError("Failed to perform JSON encoding", e);
-    }
-  }
-
-  static String jsonEncodeCertificate(String signedKey, ArrayList<String> signatures) {
-    try {
-      JsonArray jsonSignatures = new JsonArray();
-      for (String signature : signatures) {
-        jsonSignatures.add(signature);
-      }
-      JsonObject result = new JsonObject();
-      result.addProperty(PaymentMethodTokenConstants.JSON_SIGNED_KEY_KEY, signedKey);
-      result.add(PaymentMethodTokenConstants.JSON_SIGNATURES_KEY, jsonSignatures);
-      StringWriter stringWriter = new StringWriter();
-      JsonWriter jsonWriter = new JsonWriter(stringWriter);
-      jsonWriter.setHtmlSafe(true);
-      Streams.write(result, jsonWriter);
-      return stringWriter.toString();
-    } catch (JsonParseException | IllegalStateException | IOException e) {
-      throw new AssertionError("Failed to perform JSON encoding", e);
-    }
-  }
-
-  /**
-   * Creates the certificate.
-   *
-   * <p>This will return a serialized JSONObject in the following format:
-   *
-   * <pre>
-   *   {
-   *     // {
-   *     //   // A string that identifies this cert
-   *     //   "keyValue": "ZXBoZW1lcmFsUHVibGljS2V5"
-   *     //   // string (UTC milliseconds since epoch)
-   *     //   "expiration": "1520836260646",
-   *     // }
-   *     "signedKey": "... serialized JSON shown in comment above ...",
-   *     "signatures": ["signature1", "signature2", ...],
-   *   }
-   * </pre>
-   */
-  public String create() throws GeneralSecurityException {
-    String signedKey = jsonEncodeSignedKey(intermediateSigningKey, expiration);
-    byte[] toSignBytes =
-        PaymentMethodTokenUtil.toLengthValue(
-            // The order of the parameters matters.
-            senderId, protocolVersion, signedKey);
-    ArrayList<String> signatures = new ArrayList<>();
-    for (PublicKeySign signer : signers) {
-      byte[] signature = signer.sign(toSignBytes);
-      signatures.add(Base64.encode(signature));
-    }
-    return jsonEncodeCertificate(signedKey, signatures);
-  }
-}
diff --git a/apps/paymentmethodtoken/src/test/BUILD.bazel b/apps/paymentmethodtoken/src/test/BUILD.bazel
deleted file mode 100644
index c6f52d3..0000000
--- a/apps/paymentmethodtoken/src/test/BUILD.bazel
+++ /dev/null
@@ -1,43 +0,0 @@
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# Tests
-
-java_library(
-    name = "generator_test",
-    testonly = 1,
-    srcs = glob([
-        "**/*.java",
-    ]),
-    deps = [
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:google_payments_public_keys_manager",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_constants",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_decrypt",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_hybrid_encrypt",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_recipient_kem",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_sender",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:payment_method_token_util",
-        "//paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken:sender_intermediate_cert_factory",
-        "@maven//:com_google_code_gson_gson",
-        "@maven//:com_google_http_client_google_http_client",
-        "@maven//:joda_time_joda_time",
-        "@maven//:junit_junit",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:random",
-    ],
-)
-
-gen_java_test_rules(
-    test_files = glob(["**/*Test.java"],
-    ),
-    deps = [
-        ":generator_test",
-    ],
-)
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java
deleted file mode 100644
index 59cf9ad..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/GooglePaymentsPublicKeyManagerTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for {@link GooglePaymentsPublicKeysManager}. */
-@RunWith(JUnit4.class)
-public class GooglePaymentsPublicKeyManagerTest {
-  @Test
-  public void builderShouldReturnSingletonsWhenMatching() {
-    assertSame(
-        GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION,
-        new GooglePaymentsPublicKeysManager.Builder().build());
-    assertSame(
-        GooglePaymentsPublicKeysManager.INSTANCE_TEST,
-        new GooglePaymentsPublicKeysManager.Builder()
-            .setKeysUrl(GooglePaymentsPublicKeysManager.KEYS_URL_TEST)
-            .build());
-  }
-
-  @Test
-  public void builderShouldReturnDifferentInstanceWhenNotMatchingSingletons() {
-    assertNotSame(
-        GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION,
-        new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build());
-    assertNotSame(
-        GooglePaymentsPublicKeysManager.INSTANCE_TEST,
-        new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("https://abc").build());
-  }
-
-  @Test
-  public void builderShouldThrowIllegalArgumentExceptionWhenUrlIsNotHttps() {
-    try {
-      new GooglePaymentsPublicKeysManager.Builder().setKeysUrl("http://abc").build();
-      fail("Expected IllegalArgumentException");
-    } catch (IllegalArgumentException ex) {
-      // expected.
-    }
-  }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java
deleted file mode 100644
index 70b8ed4..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodJsonEncodingTest.java
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertEquals;
-
-import java.util.ArrayList;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for the exact Json-Encoding produced.
- *
- * These tests test implementation details. Do not depend on the this. For example, the particular
- * ordering of the elements or the particular character escaping used may change in the future.
- * */
-@RunWith(JUnit4.class)
-public final class PaymentMethodJsonEncodingTest {
-
-  @Test
-  public void testExactOutputOfJsonEncodeCiphertext() throws Exception {
-    byte[] ciphertext = "CiPhErTeXt".getBytes(UTF_8);
-    byte[] tag = "taaag".getBytes(UTF_8);
-    byte[] ephemeralPublicKey = "ephemeral Public Key".getBytes(UTF_8);
-
-    String jsonEncodedCiphertext =
-        PaymentMethodTokenHybridEncrypt.jsonEncodeCiphertext(ciphertext, tag, ephemeralPublicKey);
-
-    // JSONObject uses a HashMap, where the ordering is not defined. The ordering is however
-    // deterministic. And for jsonEncodeCiphertext, the order happens to be first "encryptedMessage"
-    // then "ephemeralPublicKey", and finally "tag". Also, JSONObject uses HTML-safe encoding.
-    assertEquals(
-        "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":"
-            + "\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}",
-        jsonEncodedCiphertext);
-  }
-
-  @Test
-  public void testExactOutputOfJsonEncodeSignedMessage() throws Exception {
-    String senderIntermediateCert =
-        "{\"signedKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\""
-            + ":\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]}";
-    String version = "ECv1";
-    String message =
-        "{\"encryptedMessage\":\"Q2lQaEVyVGVYdA\\u003d\\u003d\",\"ephemeralPublicKey\":\"ZXBoZW1l"
-            + "cmFsIFB1YmxpYyBLZXk\\u003d\",\"tag\":\"dGFhYWc\\u003d\"}";
-    byte[] signature = "the signature".getBytes(UTF_8);
-
-    String jsonEncodedSignedMessage =
-        PaymentMethodTokenSender.jsonEncodeSignedMessage(
-            message, version, signature, senderIntermediateCert);
-
-    String expected =
-        "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"intermediateSigningKey\":{\"signe"
-            + "dKey\":\"{\\\"keyValue\\\":\\\"abcde\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":"
-            + "\\\"1615299372858\\\"}\",\"signatures\":[\"fghijkl\\u003d\"]},\"protocolVersion\""
-            + ":\"ECv1\",\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u00"
-            + "3d\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u00"
-            + "3d\\\",\\\"tag\\\":\\\"dGFhYWc\\\\u003d\\\"}\"}";
-    assertEquals(expected, jsonEncodedSignedMessage);
-
-    String expected2 =
-        "{\"signature\":\"dGhlIHNpZ25hdHVyZQ\\u003d\\u003d\",\"protocolVersion\":\"ECv1\",\"sign"
-            + "edMessage\":\"{\\\"encryptedMessage\\\":\\\"Q2lQaEVyVGVYdA\\\\u003d\\\\u003d\\\","
-            + "\\\"ephemeralPublicKey\\\":\\\"ZXBoZW1lcmFsIFB1YmxpYyBLZXk\\\\u003d\\\",\\\"tag\\"
-            + "\":\\\"dGFhYWc\\\\u003d\\\"}\"}";
-
-    String jsonEncodedSignedMessage2 =
-        PaymentMethodTokenSender.jsonEncodeSignedMessage(message, version, signature, null);
-    assertEquals(expected2, jsonEncodedSignedMessage2);
-  }
-
-  @Test
-  public void testExactOutputOfJsonEncodedSignedKey() throws Exception {
-    String intermediateSigningKey =
-        "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA"
-            + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-    long expiration = 1520836260646L;
-    assertEquals(
-        "{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1"
-            + "TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\",\"keyExpiration\""
-            + ":\"1520836260646\"}",
-        SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration));
-  }
-
-  @Test
-  public void testExactOutputOfJsonEncodeCertificate() throws Exception {
-    String intermediateSigningKey =
-        "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSA"
-            + "M43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-    long expiration = 1520836260646L;
-    String signedKey =
-        SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration);
-    ArrayList<String> signatures = new ArrayList<>();
-    signatures.add("iTzFvzFRxyCw==");
-    signatures.add("abcde090/+==");
-    signatures.add("xyz");
-    String expected =
-        "{\"signedKey\":\"{\\\"keyValue\\\":\\\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE"
-            + "/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRx"
-            + "yCw\\\\u003d\\\\u003d\\\",\\\"keyExpiration\\\":\\\"1520836260646\\\"}\",\"signatur"
-            + "es\":[\"iTzFvzFRxyCw\\u003d\\u003d\",\"abcde090/+\\u003d\\u003d\",\"xyz\"]}";
-    assertEquals(
-        expected, SenderIntermediateCertFactory.jsonEncodeCertificate(signedKey, signatures));
-  }
-
-  @Test
-  public void testExactOutputOfWeirdJsonEncodeCertificate() throws Exception {
-    String intermediateSigningKey =
-        "\"\\==";
-    long expiration = -123;
-    String signedKey =
-        SenderIntermediateCertFactory.jsonEncodeSignedKey(intermediateSigningKey, expiration);
-    ArrayList<String> signatures = new ArrayList<>();
-    signatures.add("");
-    signatures.add("\\\"/+==");
-    String expected =
-        "{\"signedKey\":\"{\\\"keyValue\\\":\\\"\\\\\\\"\\\\\\\\\\\\u003d\\\\u003d"
-            + "\\\",\\\"keyExpiration\\\":\\\"-123\\\"}\",\"signatures\":[\"\",\"\\\\\\\"/+\\u003d"
-            + "\\u003d\"]}";
-    assertEquals(
-        expected, SenderIntermediateCertFactory.jsonEncodeCertificate(signedKey, signatures));
-  }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecryptTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecryptTest.java
deleted file mode 100644
index 387c3aa..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridDecryptTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Random;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECParameterSpec;
-import java.util.Arrays;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenHybridDecrypt}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenHybridDecryptTest {
-  @Test
-  public void testModifyDecrypt() throws Exception {
-    ECParameterSpec spec = EllipticCurves.getNistP256Params();
-    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
-    keyGen.initialize(spec);
-    KeyPair recipientKey = keyGen.generateKeyPair();
-    ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
-    ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
-
-    HybridEncrypt hybridEncrypt =
-        new PaymentMethodTokenHybridEncrypt(recipientPublicKey, ProtocolVersionConfig.EC_V1);
-    HybridDecrypt hybridDecrypt =
-        new PaymentMethodTokenHybridDecrypt(recipientPrivateKey, ProtocolVersionConfig.EC_V1);
-    testModifyDecrypt(hybridEncrypt, hybridDecrypt);
-  }
-
-  public void testModifyDecrypt(HybridEncrypt hybridEncrypt, HybridDecrypt hybridDecrypt)
-      throws Exception {
-    byte[] plaintext = Random.randBytes(111);
-    byte[] context = "context info".getBytes(UTF_8);
-
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
-    byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context);
-    assertArrayEquals(plaintext, decrypted);
-
-    JsonObject json = JsonParser.parseString(new String(ciphertext, UTF_8)).getAsJsonObject();
-
-    // Modify public key.
-    byte[] kem =
-        Base64.decode(
-            json.get(PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY).getAsString());
-    for (int bytes = 0; bytes < kem.length; bytes++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modifiedPublicKey = Arrays.copyOf(kem, kem.length);
-        modifiedPublicKey[bytes] ^= (byte) (1 << bit);
-        json.addProperty(
-            PaymentMethodTokenConstants.JSON_EPHEMERAL_PUBLIC_KEY,
-            Base64.encode(modifiedPublicKey));
-        try {
-          hybridDecrypt.decrypt(json.toString().getBytes(UTF_8), context);
-          fail("Invalid ciphertext, should have thrown exception");
-        } catch (GeneralSecurityException expected) {
-          // Expected
-        }
-      }
-    }
-
-    // Modify payload.
-    byte[] payload =
-        Base64.decode(
-            json.get(PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY).getAsString());
-    for (int bytes = 0; bytes < payload.length; bytes++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modifiedPayload = Arrays.copyOf(payload, payload.length);
-        modifiedPayload[bytes] ^= (byte) (1 << bit);
-        json.addProperty(
-            PaymentMethodTokenConstants.JSON_ENCRYPTED_MESSAGE_KEY, Base64.encode(modifiedPayload));
-        try {
-          hybridDecrypt.decrypt(json.toString().getBytes(UTF_8), context);
-          fail("Invalid ciphertext, should have thrown exception");
-        } catch (GeneralSecurityException expected) {
-          // Expected
-        }
-      }
-    }
-
-    // Modify context.
-    try {
-      hybridDecrypt.decrypt(ciphertext, Arrays.copyOf(context, context.length - 1));
-      fail("Invalid context, should have thrown exception");
-    } catch (GeneralSecurityException expected) {
-      // Expected
-    }
-  }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncryptTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncryptTest.java
deleted file mode 100644
index 3fe9459..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenHybridEncryptTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenConstants.ProtocolVersionConfig;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Random;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECParameterSpec;
-import java.util.Set;
-import java.util.TreeSet;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenHybridEncrypt}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenHybridEncryptTest {
-  @Test
-  public void testBasicMultipleEncrypts() throws Exception {
-    ECParameterSpec spec = EllipticCurves.getNistP256Params();
-    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
-    keyGen.initialize(spec);
-    KeyPair recipientKey = keyGen.generateKeyPair();
-    ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
-    ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
-
-    HybridEncrypt hybridEncrypt =
-        new PaymentMethodTokenHybridEncrypt(recipientPublicKey, ProtocolVersionConfig.EC_V1);
-    HybridDecrypt hybridDecrypt =
-        new PaymentMethodTokenHybridDecrypt(recipientPrivateKey, ProtocolVersionConfig.EC_V1);
-    testBasicMultipleEncrypts(hybridEncrypt, hybridDecrypt);
-  }
-
-  public void testBasicMultipleEncrypts(HybridEncrypt hybridEncrypt, HybridDecrypt hybridDecrypt)
-      throws Exception {
-    byte[] plaintext = Random.randBytes(111);
-    byte[] context = "context info".getBytes(StandardCharsets.UTF_8);
-    // Makes sure that the encryption is randomized.
-    Set<String> ciphertexts = new TreeSet<String>();
-    for (int j = 0; j < 100; j++) {
-      byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
-      if (ciphertexts.contains(new String(ciphertext, StandardCharsets.UTF_8))) {
-        throw new GeneralSecurityException("Encryption is not randomized");
-      }
-      ciphertexts.add(new String(ciphertext, StandardCharsets.UTF_8));
-      byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context);
-      assertArrayEquals(plaintext, decrypted);
-    }
-    assertEquals(100, ciphertexts.size());
-  }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientTest.java
deleted file mode 100644
index 4bce9f5..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenRecipientTest.java
+++ /dev/null
@@ -1,1403 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.api.client.testing.http.MockHttpTransport;
-import com.google.api.client.testing.http.MockLowLevelHttpResponse;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenRecipient}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenRecipientTest {
-
-  /**
-   * Sample merchant public key.
-   *
-   * <p>Corresponds to public key of {@link #MERCHANT_PRIVATE_KEY_PKCS8_BASE64}
-   *
-   * <p>Created with:
-   *
-   * <pre>
-   * openssl ec -in merchant-key.pem -pubout -text -noout 2> /dev/null | grep "pub:" -A5 \
-   *     | xxd -r -p | base64
-   * </pre>
-   */
-  private static final String MERCHANT_PUBLIC_KEY_BASE64 =
-      "BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=";
-
-  /**
-   * Sample merchant private key.
-   *
-   * <p>Corresponds to the private key of {@link #MERCHANT_PUBLIC_KEY_BASE64}
-   *
-   * <pre>
-   * openssl pkcs8 -topk8 -inform PEM -outform PEM -in merchant-key.pem -nocrypt
-   * </pre>
-   */
-  private static final String MERCHANT_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCPSuFr4iSIaQprjj"
-          + "chHPyDu2NXFe0vDBoTpPkYaK9dehRANCAATnaFz/vQKuO90pxsINyVNWojabHfbx"
-          + "9qIJ6uD7Q7ZSxmtyo/Ez3/o2kDT8g0pIdyVIYktCsq65VoQIDWSh2Bdm";
-
-  /** An alternative merchant private key used during the tests. */
-  private static final String ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgOUIzccyJ3rTx6SVm"
-          + "XrWdtwUP0NU26nvc8KIYw2GmYZKhRANCAAR5AjmTNAE93hQEQE+PryLlgr6Q7FXyN"
-          + "XoZRk+1Fikhq61mFhQ9s14MOwGBxd5O6Jwn/sdUrWxkYk3idtNEN1Rz";
-
-  /** Sample Google provided JSON with its public signing keys. */
-  private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON =
-      "{\n"
-          + "  \"keys\": [\n"
-          + "    {\n"
-          + "      \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQtlxizmLFynw"
-          + "HcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\",\n"
-          + "      \"protocolVersion\": \"ECv1\"\n"
-          + "    },\n"
-          + "    {\n"
-          + "      \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM"
-          + "43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==\",\n"
-          + "      \"keyExpiration\": \""
-          + Instant.now().plus(Duration.standardDays(1)).getMillis()
-          + "\",\n"
-          + "      \"protocolVersion\": \"ECv2\"\n"
-          + "    },\n"
-          + "    {\n"
-          + "      \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENXvYqxD5WayKYhuXQevdGdLA8i"
-          + "fV4LsRS2uKvFo8wwyiwgQHB9DiKzG6T/P1Fu9Bl7zWy/se5Dy4wk1mJoPuxg==\",\n"
-          + "      \"keyExpiration\": \""
-          + Instant.now().plus(Duration.standardDays(1)).getMillis()
-          + "\",\n"
-          + "      \"protocolVersion\": \"ECv2SigningOnly\"\n"
-          + "    }\n"
-          + "  ]\n"
-          + "}";
-
-  /** Index within {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} of the ECv1 Google signing key. */
-  private static final int INDEX_OF_GOOGLE_SIGNING_EC_V1 = 0;
-
-  /** Index within {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} of the ECv2 Google signing key. */
-  private static final int INDEX_OF_GOOGLE_SIGNING_EC_V2 = 1;
-
-  /**
-   * Index within {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} of the ECv2SigningOnly Google signing
-   * key.
-   */
-  private static final int INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY = 2;
-
-  /**
-   * Sample Google private signing key for the ECv1 protocolVersion.
-   *
-   * <p>Corresponds to the ECv1 private key of the key in {@link
-   * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZj/Dldxz8fvKVF5O"
-          + "TeAtK6tY3G1McmvhMppe6ayW6GahRANCAAQ9icfBLy56BYB7BC2XGLOYsXKfAdzF"
-          + "FPU8rTtwMDr8LixetUjVLNkJTHxTxLQuMytPqKt39Vbtt7czPq3+yu1F";
-
-  /**
-   * Sample Google private signing key for the ECv2 protocolVersion.
-   *
-   * <p>Corresponds to ECv2 private key of the key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
-          + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
-          + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
-  /**
-   * Sample Google intermediate public signing key for the ECv2 protocolVersion.
-   *
-   * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
-   * X.509 standard.
-   *
-   * <p>The intermediate public key will be signed by {@link
-   * #GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
-      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
-          + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-
-  /**
-   * Sample Google intermediate private signing key for the ECv2 protocolVersion.
-   *
-   * <p>Corresponds to private key of the key in {@link
-   * #GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
-          + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
-          + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
-  /**
-   * Sample Google private signing key for the ECv2SigningOnly protocolVersion.
-   *
-   * <p>Corresponds to ECv2SigningOnly private key of the key in {@link
-   * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRi9hSdY+knJ08odnY"
-          + "tZFMRi7ZYeMoasAijLhD4GiQ1yhRANCAAQ1e9irEPlZrIpiG5dB690Z0sDy"
-          + "J9XguxFLa4q8WjzDDKLCBAcH0OIrMbpP8/UW70GXvNbL+x7kPLjCTWYmg+7G";
-
-  /**
-   * Sample Google intermediate public signing key for the ECv2SigningOnly protocolVersion.
-   *
-   * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
-   * X.509 standard.
-   *
-   * <p>The intermediate public key will be signed by {@link
-   * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64}.
-   */
-  private static final String
-      GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
-          "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8OaurwvbyYm8JWDgFPRTIDg0/"
-              + "kcQTFAQ4txi5IP0AyM1QiagwRhDUfjpqZkpw8xt/DXwyWYM0DdHqoeV"
-              + "TKqmYQ==";
-
-  /**
-   * Sample Google intermediate private signing key for the ECv2SigningOnly protocolVersion.
-   *
-   * <p>Corresponds to private key of the key in {@link
-   * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
-   */
-  private static final String
-      GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
-          "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+Jvpkq26tpZ0s"
-              + "TTZVh4teEI41SnJdmkBzM8VZ5ZirE2hRANCAATw5q6vC9vJibwlYOAU"
-              + "9FMgODT+RxBMUBDi3GLkg/QDIzVCJqDBGENR+OmpmSnDzG38NfDJZgz"
-              + "QN0eqh5VMqqZh";
-
-  private static final String RECIPIENT_ID = "someRecipient";
-
-  private static final String PLAINTEXT = "plaintext";
-
-  /**
-   * The result of {@link #PLAINTEXT} encrypted with {@link #MERCHANT_PRIVATE_KEY_PKCS8_BASE64} and
-   * signed with the only key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON} using the ECv1
-   * protocolVersion.
-   */
-  private static final String CIPHERTEXT_EC_V1 =
-      "{"
-          + "\"protocolVersion\":\"ECv1\","
-          + "\"signedMessage\":"
-          + ("\"{"
-              + "\\\"tag\\\":\\\"ZVwlJt7dU8Plk0+r8rPF8DmPTvDiOA1UAoNjDV+SqDE\\\\u003d\\\","
-              + "\\\"ephemeralPublicKey\\\":\\\"BPhVspn70Zj2Kkgu9t8+ApEuUWsI/zos5whGCQBlgOkuYagOis7"
-              + "qsrcbQrcprjvTZO3XOU+Qbcc28FSgsRtcgQE\\\\u003d\\\","
-              + "\\\"encryptedMessage\\\":\\\"12jUObueVTdy\\\"}\",")
-          + "\"signature\":\"MEQCIDxBoUCoFRGReLdZ/cABlSSRIKoOEFoU3e27c14vMZtfAiBtX3pGMEpnw6mSAbnagC"
-          + "CgHlCk3NcFwWYEyxIE6KGZVA\\u003d\\u003d\"}";
-
-  private static final String ALTERNATE_PUBLIC_SIGNING_KEY =
-      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU8E6JppGKFG40r5dDU1idHRN52NuwsemFzXZh1oUqh3bGUPgPioH+RoW"
-          + "nmVSUQz1WfM2426w9f0GADuXzpUkcw==";
-
-  private static final class MyPaymentMethodTokenRecipientKem
-      implements PaymentMethodTokenRecipientKem {
-    private final ECPrivateKey privateKey;
-
-    public MyPaymentMethodTokenRecipientKem(String recipientPrivateKey)
-        throws GeneralSecurityException {
-      privateKey = PaymentMethodTokenUtil.pkcs8EcPrivateKey(recipientPrivateKey);
-    }
-
-    @Override
-    public byte[] computeSharedSecret(final byte[] ephemeralPublicKey)
-        throws GeneralSecurityException {
-      ECPublicKey publicKey =
-          EllipticCurves.getEcPublicKey(
-              privateKey.getParams(),
-              PaymentMethodTokenConstants.UNCOMPRESSED_POINT_FORMAT,
-              ephemeralPublicKey);
-      return EllipticCurves.computeSharedSecret(privateKey, publicKey);
-    }
-  }
-
-  @Test
-  public void testShouldDecryptECV1() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
-  }
-
-  @Test
-  public void testShouldDecryptECV1WithNonStrictJsonEncoding() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-  String ciphertextEcV1WithNonStrictJsonEncoding =
-      "{"
-          + "# comment \n"   // python-style comment terminated with new line
-          + "protocolVersion:'ECv1',"   // protocolVersion has no quotes, ECv1 has single quotes
-          + "/* a comment */"   // c-style comment
-          + "\"signedMessage\"="  // use = instead of :
-          + "// another comment \n"   // c-style comment terminated with new line
-          + ("\"{"
-              + "\\\"tag\\\":\\\"ZVwlJt7dU8Plk0+r8rPF8DmPTvDiOA1UAoNjDV+SqDE\\\\u003d\\\","
-              + "\\\"ephemeralPublicKey\\\":\\\"BPhVspn70Zj2Kkgu9t8+ApEuUWsI/zos5whGCQBlgOkuYagOis7"
-              + "qsrcbQrcprjvTZO3XOU+Qbcc28FSgsRtcgQE\\\\u003d\\\","
-              + "\\\"encryptedMessage\\\":\\\"12jUObueVTdy\\\"}\";")  // ; instead of ,
-          + "\"signature\":\"MEQCIDxBoUCoFRGReLdZ/cABlSSRIKoOEFoU3e27c14vMZtfAiBtX3pGMEpnw6mSAbnagC"
-          + "CgHlCk3NcFwWYEyxIE6KGZVA\\u003d\\u003d\"}";
-
-    assertEquals(PLAINTEXT, recipient.unseal(ciphertextEcV1WithNonStrictJsonEncoding));
-  }
-
-  @Test
-  public void testShouldDecryptECV1WhenUsingCustomKem() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientKem(
-                new MyPaymentMethodTokenRecipientKem(MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
-  }
-
-  @Test
-  public void testShouldDecryptECV1WhenFetchingSenderVerifyingKeys() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .fetchSenderVerifyingKeysWith(
-                new GooglePaymentsPublicKeysManager.Builder()
-                    .setHttpTransport(
-                        new MockHttpTransport.Builder()
-                            .setLowLevelHttpResponse(
-                                new MockLowLevelHttpResponse()
-                                    .setContent(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON))
-                            .build())
-                    .build())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
-  }
-
-  @Test
-  public void testShouldTryAllKeysToDecryptECV1() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
-  }
-
-  @Test
-  public void testShouldTryAllCustomKemsToDecryptECV1() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientKem(
-                new MyPaymentMethodTokenRecipientKem(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
-            .addRecipientKem(
-                new MyPaymentMethodTokenRecipientKem(MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
-  }
-
-  @Test
-  public void testShouldFailIfDecryptingWithDifferentKeyECV1() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(CIPHERTEXT_EC_V1);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot decrypt", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfDecryptingWithDifferentCustomKemECV1() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientKem(
-                new MyPaymentMethodTokenRecipientKem(ALTERNATE_MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
-            .build();
-
-    try {
-      recipient.unseal(CIPHERTEXT_EC_V1);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot decrypt", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfVerifyingWithDifferentKeyECV1() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    trustedKeysJson
-        .get("keys")
-        .getAsJsonArray()
-        .get(INDEX_OF_GOOGLE_SIGNING_EC_V1)
-        .getAsJsonObject()
-        .addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(CIPHERTEXT_EC_V1);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldTryAllKeysToVerifySignatureECV1() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject correctKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-    JsonObject wrongKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject().deepCopy();
-    wrongKey.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(wrongKey);
-    newKeys.add(correctKey);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
-  }
-
-  @Test
-  public void testShouldFailIfSignedECV1WithKeyForWrongProtocolVersion() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject correctKeyButWrongProtocol =
-        keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-    correctKeyButWrongProtocol.addProperty(
-        PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, "ECv2");
-    JsonObject wrongKeyButRightProtocol = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-    wrongKeyButRightProtocol.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-    wrongKeyButRightProtocol.addProperty(
-        PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY,
-        PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1);
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(correctKeyButWrongProtocol);
-    newKeys.add(wrongKeyButRightProtocol);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(CIPHERTEXT_EC_V1);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfNoSigningKeysForProtocolVersion() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-    key1.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, "ECv2");
-    JsonObject key2 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-
-    key2.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-    key2.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, "ECv3");
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key1);
-    newKeys.add(key2);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(CIPHERTEXT_EC_V1);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("no trusted keys are available for this protocol version", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfSignedMessageWasChangedInECV1() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
-    payload.addProperty(
-        PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY,
-        payload.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString() + " ");
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfWrongRecipientInECV1() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId("not " + RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    try {
-      recipient.unseal(CIPHERTEXT_EC_V1);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfECV1SetsWrongProtocolVersion() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
-    String invalidVersion = "ECv2";
-    payload.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, invalidVersion);
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("invalid version: " + invalidVersion, e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfProtocolSetToAnInt() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
-    payload.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, 1);
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      // expected
-    }
-  }
-
-  @Test
-  public void testShouldFailIfProtocolSetToAnFloat() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    JsonObject payload = JsonParser.parseString(CIPHERTEXT_EC_V1).getAsJsonObject();
-    payload.addProperty(PaymentMethodTokenConstants.JSON_PROTOCOL_VERSION_KEY, 1.1);
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      // expected
-    }
-  }
-
-  @Test
-  public void testShouldSucceedIfMessageIsNotExpired() throws Exception {
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
-            .recipientId(RECIPIENT_ID)
-            .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
-            .build();
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    JsonObject plaintext = new JsonObject();
-    plaintext.addProperty(
-        "messageExpiration",
-        // One day in the future
-        String.valueOf(Instant.now().plus(Duration.standardDays(1)).getMillis()));
-    plaintext.addProperty("someKey", "someValue");
-    String ciphertext = sender.seal(plaintext.toString());
-    JsonObject decrypted = JsonParser.parseString(recipient.unseal(ciphertext)).getAsJsonObject();
-
-    assertEquals("someValue", decrypted.get("someKey").getAsString());
-  }
-
-  @Test
-  public void testShouldFailIfMessageIsExpired() throws Exception {
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
-            .recipientId(RECIPIENT_ID)
-            .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
-            .build();
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    JsonObject expired = new JsonObject();
-    expired.addProperty(
-        "messageExpiration",
-        // One day in the past
-        String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-
-    String ciphertext = sender.seal(expired.toString());
-    try {
-      recipient.unseal(ciphertext);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("expired payload", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfTrustedKeyIsExpiredInECV1() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-    key1.addProperty(
-        "keyExpiration", // One day in the past
-        String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key1);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(CIPHERTEXT_EC_V1);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("no trusted keys are available for this protocol version", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldSucceedIfKeyExpirationIsMissingInTrustedKeyIsExpiredForECV1()
-      throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V1).getAsJsonObject();
-    key1.remove("keyExpiration");
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key1);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(CIPHERTEXT_EC_V1));
-  }
-
-  @Test
-  public void testUnsealECV2() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
-  }
-
-  @Test
-  public void testUnsealECV2WithCustomKem() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientKem(
-                new MyPaymentMethodTokenRecipientKem(MERCHANT_PRIVATE_KEY_PKCS8_BASE64))
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
-  }
-
-  @Test
-  public void testShouldFailIfSignedMessageWasChangedInECV2() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
-    payload.addProperty(
-        PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY,
-        payload.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString() + " ");
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfECV2UseWrongSenderId() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .senderId("not-" + PaymentMethodTokenConstants.GOOGLE_SENDER_ID)
-            .build();
-
-    try {
-      recipient.unseal(sealECV2(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfVerifyingWithDifferentKeyECV2() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
-    key1.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key1);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(sealECV2(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfTrustedKeyIsExpiredInECV2() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
-    key1.addProperty(
-        "keyExpiration", // One day in the past
-        String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key1);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(sealECV2(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("no trusted keys are available for this protocol version", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfKeyExpirationIsMissingInTrustedKeyECV2() throws Exception {
-    // Key expiration is required for V2
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key1 = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
-    key1.remove("keyExpiration");
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key1);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(sealECV2(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("no trusted keys are available for this protocol version", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldTryAllKeysToVerifySignatureECV2() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject correctKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject();
-    JsonObject wrongKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2).getAsJsonObject().deepCopy();
-    wrongKey.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(wrongKey);
-    newKeys.add(correctKey);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
-  }
-
-  @Test
-  public void testShouldFailIfSignedKeyWasChangedInECV2() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
-    JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
-    intermediateSigningKey.addProperty(
-        "signedKey", intermediateSigningKey.get("signedKey").getAsString() + " ");
-    payload.add("intermediateSigningKey", intermediateSigningKey);
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSignatureForSignedKeyIsIncorrectInECV2() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
-    JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
-    JsonArray signatures = intermediateSigningKey.get("signatures").getAsJsonArray();
-    String correctSignature = signatures.get(0).getAsString();
-    byte[] wrongSignatureBytes = Base64.decode(correctSignature);
-    wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
-    JsonArray newSignatures = new JsonArray();
-    newSignatures.add(Base64.encode(wrongSignatureBytes));
-    intermediateSigningKey.add("signatures", newSignatures);
-    payload.add("intermediateSigningKey", intermediateSigningKey);
-
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldTryVerifyingAllSignaturesForSignedKeyInECV2() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    JsonObject payload = JsonParser.parseString(sealECV2(PLAINTEXT)).getAsJsonObject();
-    JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
-    JsonArray signatures = intermediateSigningKey.get("signatures").getAsJsonArray();
-    String correctSignature = signatures.get(0).getAsString();
-    byte[] wrongSignatureBytes = Base64.decode(correctSignature);
-    wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
-    JsonArray newSignatures = new JsonArray();
-    newSignatures.add(Base64.encode(wrongSignatureBytes));
-    newSignatures.add(correctSignature);
-    intermediateSigningKey.add("signatures", newSignatures);
-    payload.add("intermediateSigningKey", intermediateSigningKey);
-
-    assertEquals(PLAINTEXT, recipient.unseal(sealECV2(PLAINTEXT)));
-  }
-
-  @Test
-  public void testShouldThrowIfECV2UseWrongRecipientId() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId("not" + RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(sealECV2(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldAcceptNonExpiredECV2Message() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    JsonObject payload = new JsonObject();
-    payload.addProperty(
-        "messageExpiration",
-        // One day in the future
-        String.valueOf(Instant.now().plus(Duration.standardDays(1)).getMillis()));
-    String plaintext = payload.toString();
-    assertEquals(plaintext, recipient.unseal(sealECV2(plaintext)));
-  }
-
-  @Test
-  public void testShouldFailIfECV2MessageIsExpired() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    JsonObject payload = new JsonObject();
-    payload.addProperty(
-        "messageExpiration",
-        // One day in the past
-        String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-
-    String ciphertext = sealECV2(payload.toString());
-    try {
-      recipient.unseal(ciphertext);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("expired payload", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfIntermediateSigningKeyIsExpiredInECV2() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderIntermediateSigningKey(
-                GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-            .senderIntermediateCert(
-                new SenderIntermediateCertFactory.Builder()
-                    .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-                    .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-                    .senderIntermediateSigningKey(
-                        GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-                    // Expiration date in the past.
-                    .expiration(Instant.now().minus(Duration.standardDays(1)).getMillis())
-                    .build()
-                    .create())
-            .recipientId(RECIPIENT_ID)
-            .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
-            .build();
-
-    try {
-      recipient.unseal(sender.seal(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("expired intermediateSigningKey", e.getMessage());
-    }
-  }
-
-  private static String sealECV2(String plaintext) throws GeneralSecurityException {
-    return new PaymentMethodTokenSender.Builder()
-        .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-        .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-        .senderIntermediateCert(
-            new SenderIntermediateCertFactory.Builder()
-                .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-                .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-                .senderIntermediateSigningKey(
-                    GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-                .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
-                .build()
-                .create())
-        .recipientId(RECIPIENT_ID)
-        .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
-        .build()
-        .seal(plaintext);
-  }
-
-  @Test
-  public void testVerifyECV2SigningOnly() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(signECV2SigningOnly(PLAINTEXT)));
-  }
-
-  @Test
-  public void testShouldFailIfSignedMessageWasChangedInECV2SigningOnly() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-    JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
-    payload.addProperty(
-        PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY,
-        payload.get(PaymentMethodTokenConstants.JSON_SIGNED_MESSAGE_KEY).getAsString() + " ");
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfECV2SigningOnlyUseWrongSenderId() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .senderId("not-" + PaymentMethodTokenConstants.GOOGLE_SENDER_ID)
-            .build();
-
-    try {
-      recipient.unseal(signECV2SigningOnly(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfVerifyingWithDifferentKeyECV2SigningOnly() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
-    key.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    try {
-      recipient.unseal(signECV2SigningOnly(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfTrustedKeyIsExpiredInECV2SigningOnly() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
-    key.addProperty(
-        "keyExpiration", // One day in the past
-        String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    try {
-      recipient.unseal(signECV2SigningOnly(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("no trusted keys are available for this protocol version", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfKeyExpirationIsMissingInTrustedKeyECV2SigningOnly() throws Exception {
-    // Key expiration is required for ECv2SigningOnly
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject key = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
-    key.remove("keyExpiration");
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(key);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    try {
-      recipient.unseal(signECV2SigningOnly(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("no trusted keys are available for this protocol version", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldTryAllKeysToVerifySignatureECV2SigningOnly() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    JsonArray keys = trustedKeysJson.get("keys").getAsJsonArray();
-    JsonObject correctKey = keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject();
-    JsonObject wrongKey =
-        keys.get(INDEX_OF_GOOGLE_SIGNING_EC_V2_SIGNING_ONLY).getAsJsonObject().deepCopy();
-    wrongKey.addProperty("keyValue", ALTERNATE_PUBLIC_SIGNING_KEY);
-    JsonArray newKeys = new JsonArray();
-    newKeys.add(wrongKey);
-    newKeys.add(correctKey);
-    trustedKeysJson.add("keys", newKeys);
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(trustedKeysJson.toString())
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    assertEquals(PLAINTEXT, recipient.unseal(signECV2SigningOnly(PLAINTEXT)));
-  }
-
-  @Test
-  public void testShouldFailIfSignedKeyWasChangedInECV2SigningOnly() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
-    JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
-    intermediateSigningKey.addProperty(
-        "signedKey", intermediateSigningKey.get("signedKey").getAsString() + " ");
-    payload.add("intermediateSigningKey", intermediateSigningKey);
-
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSignatureForSignedKeyIsIncorrectInECV2SigningOnly()
-      throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-    JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
-    JsonArray signatures =
-        payload.get("intermediateSigningKey").getAsJsonObject().get("signatures").getAsJsonArray();
-    String correctSignature = signatures.get(0).getAsString();
-    byte[] wrongSignatureBytes = Base64.decode(correctSignature);
-    wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
-    JsonArray newSignatures = new JsonArray();
-    newSignatures.add(Base64.encode(wrongSignatureBytes));
-    JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
-    intermediateSigningKey.add("signatures", newSignatures);
-    payload.add("intermediateSigningKey", intermediateSigningKey);
-
-    try {
-      recipient.unseal(payload.toString());
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldTryVerifyingAllSignaturesForSignedKeyInECV2SigningOnly() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-    JsonObject payload = JsonParser.parseString(signECV2SigningOnly(PLAINTEXT)).getAsJsonObject();
-    JsonArray signatures =
-        payload.get("intermediateSigningKey").getAsJsonObject().get("signatures").getAsJsonArray();
-    String correctSignature = signatures.get(0).getAsString();
-    byte[] wrongSignatureBytes = Base64.decode(correctSignature);
-    wrongSignatureBytes[0] = (byte) ~wrongSignatureBytes[0];
-    JsonArray newSignatures = new JsonArray();
-    newSignatures.add(Base64.encode(wrongSignatureBytes));
-    newSignatures.add(correctSignature);
-    JsonObject intermediateSigningKey = payload.get("intermediateSigningKey").getAsJsonObject();
-    intermediateSigningKey.add("signatures", newSignatures);
-    payload.add("intermediateSigningKey", intermediateSigningKey);
-
-    assertEquals(PLAINTEXT, recipient.unseal(signECV2SigningOnly(PLAINTEXT)));
-  }
-
-  @Test
-  public void testShouldThrowIfECV2SigningOnlyUseWrongRecipientId() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId("not" + RECIPIENT_ID)
-            .build();
-
-    try {
-      recipient.unseal(signECV2SigningOnly(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("cannot verify signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldAcceptNonExpiredECV2SigningOnlyMessage() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    JsonObject payload = new JsonObject();
-    payload.addProperty(
-        "messageExpiration",
-        // One day in the future
-        String.valueOf(Instant.now().plus(Duration.standardDays(1)).getMillis()));
-    String plaintext = payload.toString();
-    assertEquals(plaintext, recipient.unseal(signECV2SigningOnly(plaintext)));
-  }
-
-  @Test
-  public void testShouldFailIfECV2SigningOnlyMessageIsExpired() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    JsonObject payload = new JsonObject();
-    payload.addProperty(
-        "messageExpiration",
-        // One day in the past
-        String.valueOf(Instant.now().minus(Duration.standardDays(1)).getMillis()));
-    String ciphertext = signECV2SigningOnly(payload.toString());
-    try {
-      recipient.unseal(ciphertext);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("expired payload", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfIntermediateSigningKeyIsExpiredInECV2SigningOnly() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderIntermediateSigningKey(
-                GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-            .senderIntermediateCert(
-                new SenderIntermediateCertFactory.Builder()
-                    .protocolVersion(
-                        PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-                    .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
-                    .senderIntermediateSigningKey(
-                        GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-                    // Expiration date in the past.
-                    .expiration(Instant.now().minus(Duration.standardDays(1)).getMillis())
-                    .build()
-                    .create())
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    try {
-      recipient.unseal(sender.seal(PLAINTEXT));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("expired intermediateSigningKey", e.getMessage());
-    }
-  }
-
-  private static String signECV2SigningOnly(String plaintext) throws GeneralSecurityException {
-    return new PaymentMethodTokenSender.Builder()
-        .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-        .senderIntermediateSigningKey(
-            GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-        .senderIntermediateCert(
-            new SenderIntermediateCertFactory.Builder()
-                .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-                .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
-                .senderIntermediateSigningKey(
-                    GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-                .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
-                .build()
-                .create())
-        .recipientId(RECIPIENT_ID)
-        .build()
-        .seal(plaintext);
-  }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSenderTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSenderTest.java
deleted file mode 100644
index 5ca98a0..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/PaymentMethodTokenSenderTest.java
+++ /dev/null
@@ -1,577 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.subtle.EllipticCurves;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECParameterSpec;
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code PaymentMethodTokenSender}. */
-@RunWith(JUnit4.class)
-public class PaymentMethodTokenSenderTest {
-  private static final String MERCHANT_PUBLIC_KEY_BASE64 =
-      "BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=";
-  /**
-   * Created with:
-   *
-   * <pre>
-   * openssl pkcs8 -topk8 -inform PEM -outform PEM -in merchant-key.pem -nocrypt
-   * </pre>
-   */
-  private static final String MERCHANT_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCPSuFr4iSIaQprjj"
-          + "chHPyDu2NXFe0vDBoTpPkYaK9dehRANCAATnaFz/vQKuO90pxsINyVNWojabHfbx"
-          + "9qIJ6uD7Q7ZSxmtyo/Ez3/o2kDT8g0pIdyVIYktCsq65VoQIDWSh2Bdm";
-
-  /** Sample Google provided JSON with its public signing keys. */
-  private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON =
-      "{\n"
-          + "  \"keys\": [\n"
-          + "    {\n"
-          + "      \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQtlxizmLFynw"
-          + "HcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\",\n"
-          + "      \"protocolVersion\": \"ECv1\"\n"
-          + "    },\n"
-          + "    {\n"
-          + "      \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM"
-          + "43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==\",\n"
-          + "      \"keyExpiration\": \""
-          + Instant.now().plus(Duration.standardDays(1)).getMillis()
-          + "\",\n"
-          + "      \"protocolVersion\": \"ECv2\"\n"
-          + "    },\n"
-          + "    {\n"
-          + "      \"keyValue\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENXvYqxD5WayKYhuXQevdGdLA8i"
-          + "fV4LsRS2uKvFo8wwyiwgQHB9DiKzG6T/P1Fu9Bl7zWy/se5Dy4wk1mJoPuxg==\",\n"
-          + "      \"keyExpiration\": \""
-          + Instant.now().plus(Duration.standardDays(1)).getMillis()
-          + "\",\n"
-          + "      \"protocolVersion\": \"ECv2SigningOnly\"\n"
-          + "    }\n"
-          + "  ]\n"
-          + "}";
-
-  /**
-   * Sample Google private signing key for the ECv1 protocolVersion.
-   *
-   * <p>Corresponds to the ECv1 private key of the key in {@link
-   * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZj/Dldxz8fvKVF5O"
-          + "TeAtK6tY3G1McmvhMppe6ayW6GahRANCAAQ9icfBLy56BYB7BC2XGLOYsXKfAdzF"
-          + "FPU8rTtwMDr8LixetUjVLNkJTHxTxLQuMytPqKt39Vbtt7czPq3+yu1F";
-
-  /**
-   * Sample Google private signing key for the ECv2 protocolVersion.
-   *
-   * <p>Corresponds to ECv2 private key of the key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
-          + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
-          + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
-  /**
-   * Sample Google intermediate public signing key for the ECv2 protocolVersion.
-   *
-   * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
-   * X.509 standard.
-   *
-   * <p>The intermediate public key will be signed by {@link
-   * #GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
-      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
-          + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-
-  /**
-   * Sample Google intermediate private signing key for the ECv2 protocolVersion.
-   *
-   * <p>Corresponds to private key of the key in {@link
-   * #GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
-          + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
-          + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
-  /**
-   * Sample Google private signing key for the ECV2SigningOnly protocolVersion.
-   *
-   * <p>Corresponds to ECV2SigningOnly private key of the key in {@link
-   * #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRi9hSdY+knJ08odnY"
-          + "tZFMRi7ZYeMoasAijLhD4GiQ1yhRANCAAQ1e9irEPlZrIpiG5dB690Z0sDy"
-          + "J9XguxFLa4q8WjzDDKLCBAcH0OIrMbpP8/UW70GXvNbL+x7kPLjCTWYmg+7G";
-
-  /**
-   * Sample Google intermediate public signing key for the ECV2SigningOnly protocolVersion.
-   *
-   * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
-   * X.509 standard.
-   *
-   * <p>The intermediate public key will be signed by {@link
-   * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64}.
-   */
-  private static final String
-      GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
-          "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8OaurwvbyYm8JWDgFPRTIDg0/"
-              + "kcQTFAQ4txi5IP0AyM1QiagwRhDUfjpqZkpw8xt/DXwyWYM0DdHqoeV"
-              + "TKqmYQ==";
-
-  /**
-   * Sample Google intermediate private signing key for the ECV2SigningOnly protocolVersion.
-   *
-   * <p>Corresponds to private key of the key in {@link
-   * #GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64}.
-   */
-  private static final String
-      GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64 =
-          "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+Jvpkq26tpZ0s"
-              + "TTZVh4teEI41SnJdmkBzM8VZ5ZirE2hRANCAATw5q6vC9vJibwlYOAU"
-              + "9FMgODT+RxBMUBDi3GLkg/QDIzVCJqDBGENR+OmpmSnDzG38NfDJZgz"
-              + "QN0eqh5VMqqZh";
-
-  private static final String RECIPIENT_ID = "someRecipient";
-
-  @Test
-  public void testECV1WithPrecomputedKeys() throws Exception {
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
-            .recipientId(RECIPIENT_ID)
-            .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
-            .build();
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    String plaintext = "blah";
-    assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
-  }
-
-  @Test
-  public void testECV1WithTestdataSeal() throws Exception {
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    // Seal where "=" has been escaped by \u003d.
-    // - tag is "bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8=", = gets escapced by \u003d, so
-    //   signedMessage contains "tag": "bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8\u003d"
-    // - this gets escaped in the 2nd encoding with \ -> \\ and " -> \". So the final seal
-    //   contains \"tag\":\"bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8\\u003d\"
-    // Note that to encode this string in Java, we have to escape \ -> \\ and " -> \" again.
-    String testdataSeal =
-        "{\"signedMessage\":\"{\\\"tag\\\":\\\"bkd9lJMV/8Na34/9ZtZmDmgrZcxJ71ALhT/KpraqzR8\\\\u003d"
-            + "\\\",\\\"ephemeralPublicKey\\\":\\\"BLyk1Iwcx+yRQbCSsZgAwb8s/ChNDTlcCovgL5/37qDdia3x"
-            + "PM5GUb+HmbW9bF/c12p0ySxtU/MDcNkJZ0nWCVs\\\\u003d\\\",\\\"encryptedMessage\\\":\\\"ue"
-            + "63Gg\\\\u003d\\\\u003d\\\"}\",\"protocolVersion\":\"ECv1\",\"signature\":\"MEQCIFFdW"
-            + "ve35+jh7CT9QC9W6Leqx32P41oNxG2NDm6PaY1fAiAKHvklWHDPiLKYPb0zyXRGIl6GYvfQ3LAl1z5sR3CbS"
-            + "w\\u003d\\u003d\"}";
-
-    // Seal where "=" has not been escaped, but " and \ have to be escaped as before.
-    String testdataSeal2 =
-        "{\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"Ih3e7g==\\\",\\\"tag\\\":\\\"GlZ332kL5u"
-            + "ZICWrCsSSQ6KrFFqQyKI84SH2Wh6UTv8c=\\\",\\\"ephemeralPublicKey\\\":\\\"BBb/VaSEYJphhs"
-            + "ma1X24QrjdFr/DHo7IDu8owi6NR0tW9p5F9jn6wxu5by5Rs/bYkVdr8HNT9+MEpBOnL7Si/dQ=\\\"}\",\""
-            + "protocolVersion\":\"ECv1\",\"signature\":\"MEYCIQD6rOV4l9pm/MDr2jPkqnU2GSHbbnUaLYQow"
-            + "/0Y+S1axAIhAKUUO7N9eoBNuXySDDWDYrdu7r0IHxeeFtkubDxhplYU\"}";
-
-    // Seal where "=" has only been escaped in the outer encoding:
-    // - tag is "Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE=", so
-    //   signedMessage contains "tag": "Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE="
-    // - this gets escaped in the 2nd encoding with \ -> \\, " -> \" and = -> \u003d. So the final
-    //   seal contains \"tag\":\"Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE\u003d\"
-    // Note that to encode this string in Java, we have to escape \ -> \\ and " -> \" again.
-    String testdataSeal3 =
-        "{\"signedMessage\":\"{\\\"encryptedMessage\\\":\\\"eTBsng\\u003d\\u003d\\\",\\\"tag\\\":\\"
-            + "\"Jv2KH2lCVyNgGcQMbWSMf+N8RQCgTfkRq3J3f9qrBKE\\u003d\\\",\\\"ephemeralPublicKey\\\":"
-            + "\\\"BGPS6h2ddaOA+H71e28xZ2OQZBJuVrCK3qUXc6G6ykHTr8ab9pmS7B87n9jg8qwYAWpFcRNgC2fxnrPL"
-            + "+p2Gk5U\\u003d\\\"}\",\"protocolVersion\":\"ECv1\",\"signature\":\"MEUCIQC+8NTo1xbem"
-            + "5lPhXuEwcYc0W03jdj3Q1YNI4XvoVAbuAIgT5WXy1wZ2GNXoaLauNGo0iHeOjsZT3wYUziZPHkSAlk\\u003"
-            + "d\"}";
-
-    // Weird token where for no reason the characters { -> \u007b and s -> \u0073 have been escaped.
-    String testdataSeal4 =
-        "{\"\\u0073ignedMe\\u0073\\u0073age\":\"\\u007b\\\"encryptedMe\\\\u0073\\\\u0073age\\\":\\"
-            + "\"tfAPcA\\\\u003d\\\\u003d\\\",\\\"tag\\\":\\\"bB72KLdziA4Oca77w7g7a4fbKpgFXVrtUr56W"
-            + "NpIL6M\\\\u003d\\\",\\\"ephemeralPublicKey\\\":\\\"BKYBrgWkOP7gqCcIqRjT1t0HjMdbEIMeP"
-            + "82VtfC7IaCNbHgL9vojnQ/Yxy8Kutn+MCvG3gRdTxGN+Pwl+1QjM6w\\\\u003d\\\"}\",\"protocolVer"
-            + "\\u0073ion\":\"ECv1\",\"\\u0073ignature\":\"MEUCIHDW7JS51iy1LeyiNri7tNqe0HYdPwmDoi/M"
-            + "j2RD+3f1AiEA9FBSpqDhQzEvn849IrxXWQlIkuJdUfLE1NxCBQ8mCj0\\u003d\"}";
-
-    String plaintext = "blah";
-    assertEquals(plaintext, recipient.unseal(testdataSeal));
-    assertEquals(plaintext, recipient.unseal(testdataSeal2));
-    assertEquals(plaintext, recipient.unseal(testdataSeal3));
-    assertEquals(plaintext, recipient.unseal(testdataSeal4));
-  }
-
-  @Test
-  public void testECV2WithPrecomputedKeys() throws Exception {
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderIntermediateSigningKey(
-                GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-            .senderIntermediateCert(
-                new SenderIntermediateCertFactory.Builder()
-                    .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-                    .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-                    .senderIntermediateSigningKey(
-                        GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-                    .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
-                    .build()
-                    .create())
-            .recipientId(RECIPIENT_ID)
-            .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
-            .build();
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .addRecipientPrivateKey(MERCHANT_PRIVATE_KEY_PKCS8_BASE64)
-            .build();
-
-    String plaintext = "blah";
-    assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
-  }
-
-  @Test
-  public void testECV2SigningOnlyWithPrecomputedKeys() throws Exception {
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderIntermediateSigningKey(
-                GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-            .senderIntermediateCert(
-                new SenderIntermediateCertFactory.Builder()
-                    .protocolVersion(
-                        PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-                    .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
-                    .senderIntermediateSigningKey(
-                        GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-                    .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
-                    .build()
-                    .create())
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-            .senderVerifyingKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .recipientId(RECIPIENT_ID)
-            .build();
-
-    String plaintext = "blah";
-    assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
-  }
-
-  @Test
-  public void testShouldThrowWithUnsupportedProtocolVersion() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder().protocolVersion("ECv99").build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals("invalid version: ECv99", expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSignedIntermediateSigningKeyIsSetForECV1() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
-          .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
-          .senderIntermediateCert(newSignedIntermediateSigningKey())
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must not set signed sender's intermediate signing key using "
-              + "Builder.senderIntermediateCert",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfIntermediateSigningKeyIsSetForECV1() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
-          .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
-          .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must not set sender's intermediate signing key using "
-              + "Builder.senderIntermediateSigningKey",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSenderSigningKeyIsSetForECV2() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-          .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must not set sender's signing key using Builder.senderSigningKey",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSignedIntermediateSigningKeyIsNotSetForECV2() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-          // no calls to senderIntermediateCert
-          .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must set signed sender's intermediate signing key using "
-              + "Builder.senderIntermediateCert",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfIntermediateSigningKeyIsNotSetForECV2() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-          // no calls to senderIntermediateSigningKey
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must set sender's intermediate signing key using Builder.senderIntermediateSigningKey",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSenderSigningKeyIsNotSetForECV1() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
-          // no calls to senderSigningKey
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must set sender's signing key using Builder.senderSigningKey", expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSenderSigningKeyIsSetForECV2SigningOnly() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-          .senderSigningKey(GOOGLE_SIGNING_EC_V1_PRIVATE_KEY_PKCS8_BASE64)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must not set sender's signing key using Builder.senderSigningKey",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfSignedIntermediateSigningKeyIsNotSetForECV2SigningOnly()
-      throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-          // no calls to senderIntermediateCert
-          .senderIntermediateSigningKey(
-              GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must set signed sender's intermediate signing key using "
-              + "Builder.senderIntermediateCert",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfRecipientPublicKeyIsSetForECV2SigningOnly() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-          // no calls to senderIntermediateCert
-          .senderIntermediateSigningKey(
-              GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PRIVATE_KEY_PKCS8_BASE64)
-          .rawUncompressedRecipientPublicKey(MERCHANT_PUBLIC_KEY_BASE64)
-          .senderIntermediateCert(
-              new SenderIntermediateCertFactory.Builder()
-                  .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-                  .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_PRIVATE_KEY_PKCS8_BASE64)
-                  .senderIntermediateSigningKey(
-                      GOOGLE_SIGNING_EC_V2_SIGNING_ONLY_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-                  .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
-                  .build()
-                  .create())
-          .recipientId(RECIPIENT_ID)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must not set recipient's public key using Builder.recipientPublicKey",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldThrowIfIntermediateSigningKeyIsNotSetForECV2SigningOnly() throws Exception {
-    try {
-      new PaymentMethodTokenSender.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-          // no calls to senderIntermediateSigningKey
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must set sender's intermediate signing key using Builder.senderIntermediateSigningKey",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void testSendReceive() throws Exception {
-    ECParameterSpec spec = EllipticCurves.getNistP256Params();
-    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
-    keyGen.initialize(spec);
-    String senderId = "foo";
-    String recipientId = "bar";
-
-    KeyPair senderKey = keyGen.generateKeyPair();
-    ECPublicKey senderPublicKey = (ECPublicKey) senderKey.getPublic();
-    ECPrivateKey senderPrivateKey = (ECPrivateKey) senderKey.getPrivate();
-
-    KeyPair recipientKey = keyGen.generateKeyPair();
-    ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
-    ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
-
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .senderId(senderId)
-            .senderSigningKey(senderPrivateKey)
-            .recipientId(recipientId)
-            .recipientPublicKey(recipientPublicKey)
-            .build();
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderId(senderId)
-            .addSenderVerifyingKey(senderPublicKey)
-            .recipientId(recipientId)
-            .addRecipientPrivateKey(recipientPrivateKey)
-            .build();
-
-    String plaintext = "blah";
-    assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
-  }
-
-  @Test
-  public void testWithKeysGeneratedByRecipientKeyGen() throws Exception {
-    ECParameterSpec spec = EllipticCurves.getNistP256Params();
-    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
-    keyGen.initialize(spec);
-    String senderId = "foo";
-    String recipientId = "bar";
-
-    KeyPair senderKey = keyGen.generateKeyPair();
-    ECPublicKey senderPublicKey = (ECPublicKey) senderKey.getPublic();
-    ECPrivateKey senderPrivateKey = (ECPrivateKey) senderKey.getPrivate();
-
-    // The keys here are generated by PaymentMethodTokenRecipientKeyGen.
-    String recipientPrivateKey =
-        "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAP5/1502pXYdMion22yiWK"
-            + "GoTBJN/wAAfdjBU6puyEMw==";
-    String recipientPublicKey =
-        "BJ995jnw2Ppn4BMP/ZKtlTOOIBQC+/L3PDcFRjowZuCkRqUZ/kGWE8c+zimZNHOZPzLB"
-            + "NVGJ3V8M/fM4g4o02Mc=";
-
-    PaymentMethodTokenSender sender =
-        new PaymentMethodTokenSender.Builder()
-            .senderId(senderId)
-            .senderSigningKey(senderPrivateKey)
-            .recipientId(recipientId)
-            .rawUncompressedRecipientPublicKey(recipientPublicKey)
-            .build();
-
-    PaymentMethodTokenRecipient recipient =
-        new PaymentMethodTokenRecipient.Builder()
-            .senderId(senderId)
-            .addSenderVerifyingKey(senderPublicKey)
-            .recipientId(recipientId)
-            .addRecipientPrivateKey(recipientPrivateKey)
-            .build();
-
-    String plaintext = "blah";
-    assertEquals(plaintext, recipient.unseal(sender.seal(plaintext)));
-  }
-
-  private String newSignedIntermediateSigningKey() throws GeneralSecurityException {
-    return new SenderIntermediateCertFactory.Builder()
-        .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-        .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-        .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-        .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
-        .build()
-        .create();
-  }
-}
diff --git a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactoryTest.java b/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactoryTest.java
deleted file mode 100644
index 566ebde..0000000
--- a/apps/paymentmethodtoken/src/test/java/com/google/crypto/tink/apps/paymentmethodtoken/SenderIntermediateCertFactoryTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.paymentmethodtoken;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import org.joda.time.Duration;
-import org.joda.time.Instant;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for {@link SenderIntermediateCertFactory}. */
-@RunWith(JUnit4.class)
-public class SenderIntermediateCertFactoryTest {
-
-  /**
-   * Sample Google private signing key for the ECv2 protocolVersion.
-   *
-   * <p>Base64 version of the private key encoded in PKCS8 format.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKvEdSS8f0mjTCNKev"
-          + "aKXIzfNC5b4A104gJWI9TsLIMqhRANCAAT/X7ccFVJt2/6Ps1oCt2AzKhIAz"
-          + "jfJHJ3Op2DVPGh1LMD3oOPgxzUSIqujHG6dq9Ui93Eacl4VWJPMW/MVHHIL";
-
-  /**
-   * Sample Google intermediate public signing key for the ECv2 protocolVersion.
-   *
-   * <p>Base64 version of the public key encoded in ASN.1 type SubjectPublicKeyInfo defined in the
-   * X.509 standard.
-   */
-  private static final String GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64 =
-      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yR"
-          + "ydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw==";
-
-  @Test
-  public void shouldProduceSenderIntermediateCertJson() throws Exception {
-    String encoded =
-        new SenderIntermediateCertFactory.Builder()
-            .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-            .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-            .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-            .expiration(123456)
-            .build()
-            .create();
-
-    JsonObject decodedSignedIntermediateSigningKey =
-        JsonParser.parseString(encoded).getAsJsonObject();
-    assertTrue(decodedSignedIntermediateSigningKey.get("signedKey").isJsonPrimitive());
-    assertTrue(decodedSignedIntermediateSigningKey.get("signatures").isJsonArray());
-    assertEquals(2, decodedSignedIntermediateSigningKey.size());
-
-    JsonObject signedKey =
-        JsonParser.parseString(decodedSignedIntermediateSigningKey.get("signedKey").getAsString())
-            .getAsJsonObject();
-    assertTrue(signedKey.get("keyValue").getAsJsonPrimitive().isString());
-    assertTrue(signedKey.get("keyExpiration").getAsJsonPrimitive().isString());
-    assertEquals(2, signedKey.size());
-    assertEquals(
-        GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64,
-        signedKey.get("keyValue").getAsString());
-    assertEquals("123456", signedKey.get("keyExpiration").getAsString());
-    assertEquals(1, decodedSignedIntermediateSigningKey.get("signatures").getAsJsonArray().size());
-    assertFalse(
-        decodedSignedIntermediateSigningKey
-            .get("signatures")
-            .getAsJsonArray()
-            .get(0)
-            .getAsString()
-            .isEmpty());
-  }
-
-  @Test
-  public void shouldThrowIfExpirationNotSet() throws Exception {
-    try {
-      new SenderIntermediateCertFactory.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-          .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-          .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-          // no expiration
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals("must set expiration using Builder.expiration", expected.getMessage());
-    }
-  }
-
-  @Test
-  public void shouldThrowIfExpirationIsNegative() throws Exception {
-    try {
-      new SenderIntermediateCertFactory.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-          .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-          .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-          .expiration(-1)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals("invalid negative expiration", expected.getMessage());
-    }
-  }
-
-  @Test
-  public void shouldThrowIfNoSenderSigningKeyAdded() throws Exception {
-    try {
-      new SenderIntermediateCertFactory.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2)
-          .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-          // no call to addSenderSigningKey
-          .expiration(Instant.now().plus(Duration.standardDays(1)).getMillis())
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals(
-          "must add at least one sender's signing key using Builder.addSenderSigningKey",
-          expected.getMessage());
-    }
-  }
-
-  @Test
-  public void shouldSupportECV2SigningOnly() throws Exception {
-    new SenderIntermediateCertFactory.Builder()
-        .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V2_SIGNING_ONLY)
-        .senderIntermediateSigningKey(GOOGLE_SIGNING_EC_V2_INTERMEDIATE_PUBLIC_KEY_X509_BASE64)
-        .addSenderSigningKey(GOOGLE_SIGNING_EC_V2_PRIVATE_KEY_PKCS8_BASE64)
-        .expiration(123456)
-        .build();
-  }
-
-  @Test
-  public void shouldThrowIfInvalidProtocolVersionSet() throws Exception {
-    try {
-      new SenderIntermediateCertFactory.Builder()
-          .protocolVersion(PaymentMethodTokenConstants.PROTOCOL_VERSION_EC_V1)
-          .build();
-      fail("Should have thrown!");
-    } catch (IllegalArgumentException expected) {
-      assertEquals("invalid version: ECv1", expected.getMessage());
-    }
-  }
-}
diff --git a/apps/rewardedads/BUILD.bazel b/apps/rewardedads/BUILD.bazel
deleted file mode 100644
index a8f1330..0000000
--- a/apps/rewardedads/BUILD.bazel
+++ /dev/null
@@ -1,15 +0,0 @@
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-gen_maven_jar_rules(
-    name = "maven",
-    doctitle = "Tink Cryptography API for Google Mobile Rewarded Video Ads SSV",
-    manifest_lines = [
-        "Automatic-Module-Name: com.google.crypto.tink.apps.rewardedads",
-    ],
-    root_packages = ["com.google.crypto.tink.apps.rewardedads"],
-    deps = ["//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier"],
-)
diff --git a/apps/rewardedads/README.md b/apps/rewardedads/README.md
deleted file mode 100644
index b92e374..0000000
--- a/apps/rewardedads/README.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# An implementation of Google AdMob Rewarded Ads
-
-This app implements the verifier side of Server-Side Verification of Google
-AdMob Rewarded Ads.
-
-## Latest Release
-
-The most recent release is
-[1.7.0](https://github.com/google/tink/releases/tag/v1.7.0), released
-2022-08-09. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-rewardedads/1.7.0).
-
-The Maven group ID is `com.google.crypto.tink`, and the artifact ID is
-`apps-rewardedads`.
-
-To add a dependency using Maven:
-
-```xml
-<dependency>
-  <groupId>com.google.crypto.tink</groupId>
-  <artifactId>apps-rewardedads</artifactId>
-  <version>1.7.0</version>
-</dependency>
-```
-
-## Snapshots
-
-Snapshots of this app built from the master branch are available through Maven
-using version `HEAD-SNAPSHOT`. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-rewardedads/HEAD-SNAPSHOT).
-
-To add a dependency using Maven:
-
-```xml
-<repositories>
-<repository>
-  <id>sonatype-snapshots</id>
-  <name>sonatype-snapshots</name>
-  <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
-  <snapshots>
-    <enabled>true</enabled>
-    <updatePolicy>always</updatePolicy>
-  </snapshots>
-  <releases>
-    <updatePolicy>always</updatePolicy>
-  </releases>
-</repository>
-</repositories>
-
-<dependency>
-  <groupId>com.google.crypto.tink</groupId>
-  <artifactId>apps-rewardedads</artifactId>
-  <version>HEAD-SNAPSHOT</version>
-</dependency>
-```
diff --git a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel b/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel
deleted file mode 100644
index a44132c..0000000
--- a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel
+++ /dev/null
@@ -1,17 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-java_library(
-    name = "rewarded_ads_verifier",
-    srcs = ["RewardedAdsVerifier.java"],
-    deps = [
-        "@maven//:com_google_code_gson_gson",
-        "@maven//:com_google_http_client_google_http_client",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_verify_jce",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:enums",
-        "@tink_java//src/main/java/com/google/crypto/tink/util:keys_downloader",
-    ],
-)
diff --git a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifier.java b/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifier.java
deleted file mode 100644
index 85d26df..0000000
--- a/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifier.java
+++ /dev/null
@@ -1,325 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.rewardedads;
-
-import com.google.api.client.http.javanet.NetHttpTransport;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaVerifyJce;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.crypto.tink.subtle.Enums.HashType;
-import com.google.crypto.tink.util.KeysDownloader;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonParser;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.Charset;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPublicKey;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * An implementation of the verifier side of Server-Side Verification of Google AdMob Rewarded Ads.
- *
- * <p>Typical usage:
- *
- * <pre>{@code
- * RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
- *     .fetchVerifyingPublicKeysWith(
- *         RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
- *     .build();
- * String rewardUrl = ...;
- * verifier.verify(rewardUrl);
- * }</pre>
- *
- * <p>This usage ensures that you always have the latest public keys, even when the keys were
- * recently rotated. It will fetch and cache the latest public keys from {#PUBLIC_KEYS_URL_PROD}.
- * When the cache expires, it will re-fetch the public keys. When initializing your server, we also
- * recommend that you call {@link KeysDownloader#refreshInBackground()} of {@link
- * RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD} to proactively fetch the public keys.
- *
- * <p>If you've already downloaded the public keys and have other means to manage key rotation, you
- * can use {@link RewardedAdsVerifier.Builder#setVerifyingPublicKeys} to set the public keys. The
- * Builder also allows you to customize other properties.
- */
-public final class RewardedAdsVerifier {
-  private static final Charset UTF_8 = Charset.forName("UTF-8");
-
-  /** Default HTTP transport used by this class. */
-  private static final NetHttpTransport DEFAULT_HTTP_TRANSPORT =
-      new NetHttpTransport.Builder().build();
-
-  private static final Executor DEFAULT_BACKGROUND_EXECUTOR = Executors.newCachedThreadPool();
-  private final List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders;
-
-  public static final String SIGNATURE_PARAM_NAME = "signature=";
-  public static final String KEY_ID_PARAM_NAME = "key_id=";
-
-  /** URL to fetch keys for environment production. */
-  public static final String PUBLIC_KEYS_URL_PROD =
-      "https://www.gstatic.com/admob/reward/verifier-keys.json";
-
-  /** URL to fetch keys for environment test. */
-  public static final String PUBLIC_KEYS_URL_TEST =
-      "https://www.gstatic.com/admob/reward/verifier-keys-test.json";
-
-  /**
-   * Instance configured to talk to fetch keys from production environment (from {@link
-   * #PUBLIC_KEYS_URL_PROD}).
-   */
-  public static final KeysDownloader KEYS_DOWNLOADER_INSTANCE_PROD =
-      new KeysDownloader(DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, PUBLIC_KEYS_URL_PROD);
-  /**
-   * Instance configured to talk to fetch keys from test environment (from {@link
-   * #PUBLIC_KEYS_URL_TEST}).
-   */
-  public static final KeysDownloader KEYS_DOWNLOADER_INSTANCE_TEST =
-      new KeysDownloader(DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, PUBLIC_KEYS_URL_TEST);
-
-  RewardedAdsVerifier(List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders)
-      throws GeneralSecurityException {
-    if (verifyingPublicKeysProviders == null || verifyingPublicKeysProviders.isEmpty()) {
-      throw new IllegalArgumentException(
-          "must set at least one way to get verifying key using"
-              + " Builder.fetchVerifyingPublicKeysWith or Builder.setVerifyingPublicKeys");
-    }
-    this.verifyingPublicKeysProviders = verifyingPublicKeysProviders;
-  }
-
-  private RewardedAdsVerifier(Builder builder) throws GeneralSecurityException {
-    this(builder.verifyingPublicKeysProviders);
-  }
-
-  /**
-   * Verifies that {@code rewardUrl} has a valid signature.
-   *
-   * <p>This method requires that the name of the last two query parameters of {@code rewardUrl} are
-   * {@link #SIGNATURE_PARAM_NAME} and {@link #KEY_ID_PARAM_NAME} in that order.
-   */
-  public void verify(String rewardUrl) throws GeneralSecurityException {
-    URI uri;
-    try {
-      uri = new URI(rewardUrl);
-    } catch (URISyntaxException ex) {
-      throw new GeneralSecurityException(ex);
-    }
-    String queryString = uri.getQuery();
-    int i = queryString.indexOf(SIGNATURE_PARAM_NAME);
-    if (i <= 0 || queryString.charAt(i - 1) != '&') {
-      throw new GeneralSecurityException(
-          "signature and key id must be the last two query parameters");
-    }
-    byte[] tbsData =
-        queryString.substring(0, i - 1 /* i - 1 instead of i because of & */).getBytes(UTF_8);
-
-    String sigAndKeyId = queryString.substring(i);
-    i = sigAndKeyId.indexOf(KEY_ID_PARAM_NAME);
-    if (i == -1 || sigAndKeyId.charAt(i - 1) != '&') {
-      throw new GeneralSecurityException(
-          "signature and key id must be the last two query parameters");
-    }
-    String sig =
-        sigAndKeyId.substring(
-            SIGNATURE_PARAM_NAME.length(), i - 1 /* i - 1 instead of i because of & */);
-
-    // We don't have to check that keyId is the last parameter, because the long conversion would
-    // fail anyway if there's any trailing data.
-    try {
-      long keyId = Long.parseLong(sigAndKeyId.substring(i + KEY_ID_PARAM_NAME.length()));
-      verify(tbsData, keyId, Base64.urlSafeDecode(sig));
-    } catch (NumberFormatException ex) {
-      throw new GeneralSecurityException("key_id must be a long");
-    }
-  }
-
-  private void verify(final byte[] tbs, long keyId, final byte[] signature)
-      throws GeneralSecurityException {
-    boolean foundKeyId = false;
-    for (VerifyingPublicKeysProvider provider : verifyingPublicKeysProviders) {
-      Map<Long, ECPublicKey> publicKeys = provider.get();
-      if (publicKeys.containsKey(keyId)) {
-        foundKeyId = true;
-        ECPublicKey publicKey = publicKeys.get(keyId);
-        EcdsaVerifyJce verifier = new EcdsaVerifyJce(publicKey, HashType.SHA256, EcdsaEncoding.DER);
-        verifier.verify(signature, tbs);
-      }
-    }
-    if (!foundKeyId) {
-      throw new GeneralSecurityException("cannot find verifying key with key id: " + keyId);
-    }
-  }
-
-  /** Builder for RewardedAdsVerifier. */
-  public static class Builder {
-    private final List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders =
-        new ArrayList<>();
-
-    public Builder() {}
-
-    /**
-     * Fetches verifying public keys of the sender using {@link KeysDownloader}.
-     *
-     * <p>This is the preferred method of specifying the verifying public keys.
-     */
-    public Builder fetchVerifyingPublicKeysWith(final KeysDownloader downloader)
-        throws GeneralSecurityException {
-      this.verifyingPublicKeysProviders.add(
-          new VerifyingPublicKeysProvider() {
-            @Override
-            public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
-              try {
-                return parsePublicKeysJson(downloader.download());
-              } catch (IOException e) {
-                throw new GeneralSecurityException("Failed to fetch keys!", e);
-              }
-            }
-          });
-      return this;
-    }
-
-    /**
-     * Sets the trusted verifying public keys of the sender.
-     *
-     * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
-     * sender, prefer calling {@link #fetchVerifyingPublicKeysWith} passing it an instance of {@link
-     * KeysDownloader}. It will take care of fetching fresh keys and caching in memory. Only use
-     * this method if you can't use {@link #fetchVerifyingPublicKeysWith} and be aware you will need
-     * to handle key rotations yourself.
-     *
-     * <p>The given string is a JSON object formatted like the following:
-     *
-     * <pre>
-     * {
-     *   "keys": [
-     *     {
-     *       keyId: 1916455855,
-     *       pem: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUaWMKcBHWdhUE+DncSIHhFCLLEln\nUs0LB9oanZ4K/FNICIM8ltS4nzc9yjmhgVQOlmSS6unqvN9t8sqajRTPcw==\n-----END PUBLIC KEY-----"
-     *       base64: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUaWMKcBHWdhUE+DncSIHhFCLLElnUs0LB9oanZ4K/FNICIM8ltS4nzc9yjmhgVQOlmSS6unqvN9t8sqajRTPcw=="
-     *     },
-     *     {
-     *       keyId: 3901585526,
-     *       pem: "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtxg2BsK/fllIeADtLspezS6YfHFWXZ8tiJncm8LDBa/NxEC84akdWbWDCUrMMGIV27/3/e7UuKSEonjGvaDUsw==\n-----END PUBLIC KEY-----"
-     *       base64: "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtxg2BsK/fllIeADtLspezS6YfHFWXZ8tiJncm8LDBa/NxEC84akdWbWDCUrMMGIV27/3/e7UuKSEonjGvaDUsw=="
-     *     },
-     *   ],
-     * }
-     * </pre>
-     *
-     * <p>Each public key will be a base64 (no wrapping, padded) version of the key encoded in ASN.1
-     * type SubjectPublicKeyInfo defined in the X.509 standard.
-     */
-    public Builder setVerifyingPublicKeys(final String publicKeysJson)
-        throws GeneralSecurityException {
-      this.verifyingPublicKeysProviders.add(
-          new VerifyingPublicKeysProvider() {
-            @Override
-            public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
-              return parsePublicKeysJson(publicKeysJson);
-            }
-          });
-      return this;
-    }
-
-    /**
-     * Adds a verifying public key of the sender.
-     *
-     * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
-     * sender, prefer calling {@link #fetchVerifyingPublicKeysWith} passing it an instance of {@link
-     * KeysDownloader}. It will take care of fetching fresh keys and caching in memory. Only use
-     * this method if you can't use {@link #fetchVerifyingPublicKeysWith} and be aware you will need
-     * to handle Google key rotations yourself.
-     *
-     * <p>The public key is a base64 (no wrapping, padded) version of the key encoded in ASN.1 type
-     * SubjectPublicKeyInfo defined in the X.509 standard.
-     *
-     * <p>Multiple keys may be added. This utility will then verify any message signed with any of
-     * the private keys corresponding to the public keys added. Adding multiple keys is useful for
-     * handling key rotation.
-     */
-    public Builder addVerifyingPublicKey(final long keyId, final String val)
-        throws GeneralSecurityException {
-      this.verifyingPublicKeysProviders.add(
-          new VerifyingPublicKeysProvider() {
-            @Override
-            public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
-              return Collections.singletonMap(
-                  keyId, EllipticCurves.getEcPublicKey(Base64.decode(val)));
-            }
-          });
-      return this;
-    }
-
-    /**
-     * Adds a verifying public key of the sender.
-     *
-     * <p><b>IMPORTANT</b>: Instead of using this method to set the verifying public keys of the
-     * sender, prefer calling {@link #fetchVerifyingPublicKeysWith} passing it an instance of {@link
-     * KeysDownloader}. It will take care of fetching fresh keys and caching in memory. Only use
-     * this method if you can't use {@link #fetchVerifyingPublicKeysWith} and be aware you will need
-     * to handle Google key rotations yourself.
-     */
-    public Builder addVerifyingPublicKey(final long keyId, final ECPublicKey val)
-        throws GeneralSecurityException {
-      this.verifyingPublicKeysProviders.add(
-          new VerifyingPublicKeysProvider() {
-            @Override
-            public Map<Long, ECPublicKey> get() throws GeneralSecurityException {
-              return Collections.singletonMap(keyId, val);
-            }
-          });
-      return this;
-    }
-
-    public RewardedAdsVerifier build() throws GeneralSecurityException {
-      return new RewardedAdsVerifier(this);
-    }
-  }
-
-  private static Map<Long, ECPublicKey> parsePublicKeysJson(String publicKeysJson)
-      throws GeneralSecurityException {
-    Map<Long, ECPublicKey> publicKeys = new HashMap<>();
-    try {
-      JsonArray keys =
-          JsonParser.parseString(publicKeysJson).getAsJsonObject().get("keys").getAsJsonArray();
-      for (int i = 0; i < keys.size(); i++) {
-        JsonObject key = keys.get(i).getAsJsonObject();
-        publicKeys.put(
-            key.get("keyId").getAsLong(),
-            EllipticCurves.getEcPublicKey(Base64.decode(key.get("base64").getAsString())));
-      }
-    } catch (JsonParseException | IllegalStateException e) {
-      throw new GeneralSecurityException("failed to extract trusted signing public keys", e);
-    }
-    if (publicKeys.isEmpty()) {
-      throw new GeneralSecurityException("no trusted keys are available for this protocol version");
-    }
-    return publicKeys;
-  }
-
-  private interface VerifyingPublicKeysProvider {
-    Map<Long, ECPublicKey> get() throws GeneralSecurityException;
-  }
-}
diff --git a/apps/rewardedads/src/test/BUILD.bazel b/apps/rewardedads/src/test/BUILD.bazel
deleted file mode 100644
index 244de24..0000000
--- a/apps/rewardedads/src/test/BUILD.bazel
+++ /dev/null
@@ -1,35 +0,0 @@
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# Tests
-
-java_library(
-    name = "generator_test",
-    testonly = 1,
-    srcs = glob([
-        "**/*.java",
-    ]),
-    deps = [
-        "//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier",
-        "@maven//:com_google_code_gson_gson",
-        "@maven//:com_google_http_client_google_http_client",
-        "@maven//:junit_junit",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:enums",
-        "@tink_java//src/main/java/com/google/crypto/tink/util:keys_downloader",
-    ],
-)
-
-gen_java_test_rules(
-    test_files = glob([
-        "**/*Test.java",
-    ]),
-    deps = [
-        ":generator_test",
-    ],
-)
diff --git a/apps/rewardedads/src/test/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifierTest.java b/apps/rewardedads/src/test/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifierTest.java
deleted file mode 100644
index f0e636e..0000000
--- a/apps/rewardedads/src/test/java/com/google/crypto/tink/apps/rewardedads/RewardedAdsVerifierTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.rewardedads;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.google.api.client.testing.http.MockHttpTransport;
-import com.google.api.client.testing.http.MockLowLevelHttpResponse;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EcdsaSignJce;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
-import com.google.crypto.tink.subtle.Enums.HashType;
-import com.google.crypto.tink.util.KeysDownloader;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import java.net.URI;
-import java.nio.charset.Charset;
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code RewardedAdsVerifier}. */
-@RunWith(JUnit4.class)
-public class RewardedAdsVerifierTest {
-  private static final Charset UTF_8 = Charset.forName("UTF-8");
-
-  /** Sample Google provided JSON with its public signing keys. */
-  private static final String GOOGLE_VERIFYING_PUBLIC_KEYS_JSON =
-      "{\"keys\":[{"
-          + "\"keyId\":1234,"
-          + "\"base64\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPYnHwS8uegWAewQt"
-          + "lxizmLFynwHcxRT1PK07cDA6/C4sXrVI1SzZCUx8U8S0LjMrT6ird/VW7be3Mz6t/srtRQ==\""
-          + "}]}";
-
-  /**
-   * Sample Google private signing key.
-   *
-   * <p>Corresponds to private key of the key in {@link #GOOGLE_VERIFYING_PUBLIC_KEYS_JSON}.
-   */
-  private static final String GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64 =
-      "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZj/Dldxz8fvKVF5O"
-          + "TeAtK6tY3G1McmvhMppe6ayW6GahRANCAAQ9icfBLy56BYB7BC2XGLOYsXKfAdzF"
-          + "FPU8rTtwMDr8LixetUjVLNkJTHxTxLQuMytPqKt39Vbtt7czPq3+yu1F";
-
-  // must match the value in GOOGLE_VERIFYING_PUBLIC_KEYS_JSON
-  private static final long KEY_ID = 1234;
-  private static final String REWARD_HOST_AND_PATH = "https://publisher.com/blah?";
-  private static final String REWARD_QUERY = "foo1=bar1&foo2=bar2";
-  private static final String REWARD_URL = REWARD_HOST_AND_PATH + REWARD_QUERY;
-
-  private static final String ALTERNATE_PUBLIC_SIGNING_KEY =
-      "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU8E6JppGKFG40r5dDU1idHRN52NuwsemFzXZh1oUqh3bGUPgPioH+RoW"
-          + "nmVSUQz1WfM2426w9f0GADuXzpUkcw==";
-
-  private static String signUrl(String rewardUrl, String privateKey, long keyId) throws Exception {
-    EcdsaSignJce signer =
-        new EcdsaSignJce(
-            EllipticCurves.getEcPrivateKey(Base64.decode(privateKey)),
-            HashType.SHA256,
-            EcdsaEncoding.DER);
-    String queryString = new URI(rewardUrl).getQuery();
-    return buildUrl(rewardUrl, signer.sign(queryString.getBytes(UTF_8)), keyId);
-  }
-
-  private static String buildUrl(String rewardUrl, byte[] sig, long keyId) {
-    return new StringBuilder(rewardUrl)
-        .append("&")
-        .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
-        .append(Base64.urlSafeEncode(sig))
-        .append("&")
-        .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
-        .append(keyId)
-        .toString();
-  }
-
-  @Test
-  public void testShouldVerify() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID));
-  }
-
-  @Test
-  public void testShouldVerifyIfKeyIdIsLargerThanMaxInt() throws Exception {
-    long keyId = Integer.MAX_VALUE + 1;
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-
-    trustedKeysJson.getAsJsonArray("keys").get(0).getAsJsonObject().addProperty("keyId", keyId);
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(trustedKeysJson.toString())
-            .build();
-    verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, keyId));
-  }
-
-  @Test
-  public void testShouldVerifyWithEncodedUrl() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-
-    String rewardUrl = "https://publisher.com/path?foo=hello%20world&bar=user%40gmail.com";
-    String decodedQueryString = "foo=hello world&bar=user@gmail.com";
-    EcdsaSignJce signer =
-        new EcdsaSignJce(
-            EllipticCurves.getEcPrivateKey(Base64.decode(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64)),
-            HashType.SHA256,
-            EcdsaEncoding.DER);
-    byte[] signature = signer.sign(decodedQueryString.getBytes(UTF_8));
-    String signedUrl = buildUrl(rewardUrl, signature, KEY_ID);
-    verifier.verify(signedUrl);
-  }
-
-  @Test
-  public void testShouldDecryptV1WhenFetchingSenderVerifyingKeys() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .fetchVerifyingPublicKeysWith(
-                new KeysDownloader.Builder()
-                    .setHttpTransport(
-                        new MockHttpTransport.Builder()
-                            .setLowLevelHttpResponse(
-                                new MockLowLevelHttpResponse()
-                                    .setContent(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON))
-                            .build())
-                    .setUrl("https://someUrl" /* unused */)
-                    .build())
-            .build();
-
-    verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID));
-  }
-
-  @Test
-  public void testShouldFailIfVerifyingWithDifferentKey() throws Exception {
-    JsonObject trustedKeysJson =
-        JsonParser.parseString(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON).getAsJsonObject();
-    trustedKeysJson
-        .getAsJsonArray("keys")
-        .get(0)
-        .getAsJsonObject()
-        .addProperty("base64", ALTERNATE_PUBLIC_SIGNING_KEY);
-
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(trustedKeysJson.toString())
-            .build();
-
-    try {
-      verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertEquals("Invalid signature", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailIfKeyIdNotFound() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-
-    try {
-      verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID + 1));
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertTrue(e.getMessage().contains("cannot find verifying key with key id"));
-    }
-  }
-
-  @Test
-  public void testShouldFailIfSignedMessageWasChanged() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    byte[] validSignedUrl =
-        signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID).getBytes(UTF_8);
-    for (int i = REWARD_HOST_AND_PATH.length(); i < REWARD_URL.length(); i++) {
-      byte[] modifiedUrl = Arrays.copyOf(validSignedUrl, validSignedUrl.length);
-      modifiedUrl[i] = (byte) (modifiedUrl[i] ^ 0xff);
-      try {
-        verifier.verify(new String(modifiedUrl, UTF_8));
-        fail("Expected GeneralSecurityException");
-      } catch (GeneralSecurityException e) {
-        assertEquals("Invalid signature", e.getMessage());
-      }
-    }
-  }
-
-  @Test
-  public void testShouldFailIfSignatureWasChanged() throws Exception {
-    EcdsaSignJce signer =
-        new EcdsaSignJce(
-            EllipticCurves.getEcPrivateKey(Base64.decode(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64)),
-            HashType.SHA256,
-            EcdsaEncoding.DER);
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-
-    byte[] validSig = signer.sign(REWARD_URL.getBytes(UTF_8));
-    for (int i = 0; i < validSig.length; i++) {
-      byte[] modifiedSig = Arrays.copyOf(validSig, validSig.length);
-      modifiedSig[i] = (byte) (modifiedSig[i] ^ 0xff);
-      String modifiedUrl = buildUrl(REWARD_URL, modifiedSig, KEY_ID);
-      try {
-        verifier.verify(modifiedUrl);
-        fail("Expected GeneralSecurityException");
-      } catch (GeneralSecurityException e) {
-        // Expected.
-        System.out.println(e);
-      }
-    }
-  }
-
-  @Test
-  public void testShouldFailWithoutSignature() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    try {
-      verifier.verify(REWARD_URL);
-    } catch (GeneralSecurityException e) {
-      assertEquals("signature and key id must be the last two query parameters", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailWithoutKeyId() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    try {
-      verifier.verify(
-          new StringBuilder(REWARD_URL)
-              .append("&")
-              .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
-              .append("foo")
-              .toString());
-    } catch (GeneralSecurityException e) {
-      assertEquals("signature and key id must be the last two query parameters", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    try {
-      verifier.verify(
-          new StringBuilder(REWARD_HOST_AND_PATH)
-              .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
-              .toString());
-    } catch (GeneralSecurityException e) {
-      assertEquals("signature and key id must be the last two query parameters", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters2() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    try {
-      verifier.verify(
-          new StringBuilder(REWARD_HOST_AND_PATH)
-              .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
-              .append("foo")
-              .append("&")
-              .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
-              .append("123")
-              .append("bar=baz")
-              .toString());
-    } catch (GeneralSecurityException e) {
-      assertEquals("signature and key id must be the last two query parameters", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters3() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    try {
-      verifier.verify(
-          new StringBuilder(REWARD_URL)
-              .append("&")
-              .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
-              .append("foo")
-              .append("&")
-              .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
-              .append("123")
-              .append("&bar=baz") // this would be interpreted as part of the key ID
-              .toString());
-    } catch (GeneralSecurityException e) {
-      assertEquals("key_id must be a long", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailWithSignatureAndKeyIdNoAmpersand() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    try {
-      verifier.verify(
-          new StringBuilder(REWARD_URL)
-              .append("&")
-              .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
-              .append("foo")
-              .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
-              .append("123")
-              .toString());
-    } catch (GeneralSecurityException e) {
-      assertEquals("signature and key id must be the last two query parameters", e.getMessage());
-    }
-  }
-
-  @Test
-  public void testShouldFailWithKeyIdNotLong() throws Exception {
-    RewardedAdsVerifier verifier =
-        new RewardedAdsVerifier.Builder()
-            .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)
-            .build();
-    try {
-      verifier.verify(
-          new StringBuilder(REWARD_URL)
-              .append("&")
-              .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME)
-              .append("foo")
-              .append("&")
-              .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME)
-              .append("not_long")
-              .toString());
-    } catch (GeneralSecurityException e) {
-      assertEquals("key_id must be a long", e.getMessage());
-    }
-  }
-}
diff --git a/apps/webpush/BUILD.bazel b/apps/webpush/BUILD.bazel
deleted file mode 100644
index 7260048..0000000
--- a/apps/webpush/BUILD.bazel
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-gen_maven_jar_rules(
-    name = "maven",
-    doctitle = "Tink Cryptography API for Message Encryption for Web Push (RFC 8291)",
-    manifest_lines = [
-        "Automatic-Module-Name: com.google.crypto.tink.apps.webpush",
-    ],
-    root_packages = ["com.google.crypto.tink.apps.webpush"],
-    deps = [
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_constants",
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_decrypt",
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_encrypt",
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_util",
-    ],
-)
diff --git a/apps/webpush/README.md b/apps/webpush/README.md
deleted file mode 100644
index 1018f4d..0000000
--- a/apps/webpush/README.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# Message Encryption for Web Push
-
-This Tink app is an implementation of [RFC 8291 - Message Encryption for Web
-Push](https://tools.ietf.org/html/rfc8291).
-
-The most recent release is
-[1.7.0](https://github.com/google/tink/releases/tag/v1.7.0), released
-2022-08-09. API docs can be found
-[here](https://google.github.io/tink/javadoc/apps-webpush/1.7.0).
-
-## Installation
-
-To add a dependency using Maven:
-
-```xml
-<dependency>
-  <groupId>com.google.crypto.tink</groupId>
-  <artifactId>apps-webpush</artifactId>
-  <version>1.7.0</version>
-</dependency>
-```
-
-To add a dependency using Gradle:
-
-```
-dependencies {
-  implementation 'com.google.crypto.tink:apps-webpush:1.7.0'
-}
-```
-
-## Encryption
-
-```java
-import com.google.crypto.tink.HybridEncrypt;
-import java.security.interfaces.ECPublicKey;
-
-ECPublicKey reicipientPublicKey = ...;
-byte[] authSecret = ...;
-HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder()
-     .withAuthSecret(authSecret)
-     .withRecipientPublicKey(recipientPublicKey)
-     .build();
-byte[] plaintext = ...;
-byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo, must be null */);
-```
-
-## Decryption
-
-```java
-import com.google.crypto.tink.HybridDecrypt;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-
-ECPrivateKey recipientPrivateKey = ...;
-ECPublicKey  recipientPublicKey = ...;
-HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder()
-     .withAuthSecret(authSecret)
-     .withRecipientPublicKey(recipientPublicKey)
-     .withRecipientPrivateKey(recipientPrivateKey)
-     .build();
-byte[] ciphertext = ...;
-byte[] plaintext = hybridDecrypt.decrypt(ciphertext, /* contextInfo, must be null */);
-```
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel
deleted file mode 100644
index 5d2afbb..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel
+++ /dev/null
@@ -1,44 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-java_library(
-    name = "web_push_hybrid_decrypt",
-    srcs = ["WebPushHybridDecrypt.java"],
-    deps = [
-        ":web_push_constants",
-        ":web_push_util",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
-    ],
-)
-
-java_library(
-    name = "web_push_util",
-    srcs = ["WebPushUtil.java"],
-    deps = [
-        ":web_push_constants",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:bytes",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:hkdf",
-    ],
-)
-
-java_library(
-    name = "web_push_constants",
-    srcs = ["WebPushConstants.java"],
-    deps = ["@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves"],
-)
-
-java_library(
-    name = "web_push_hybrid_encrypt",
-    srcs = ["WebPushHybridEncrypt.java"],
-    deps = [
-        ":web_push_constants",
-        ":web_push_util",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:random",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
-    ],
-)
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushConstants.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushConstants.java
deleted file mode 100644
index c864021..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushConstants.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.subtle.EllipticCurves;
-import java.nio.charset.Charset;
-
-/** Various constants. */
-final class WebPushConstants {
-  static final Charset UTF_8 = Charset.forName("UTF-8");
-  static final int AUTH_SECRET_SIZE = 16;
-  static final int IKM_SIZE = 32;
-  static final int CEK_KEY_SIZE = 16;
-  static final int NONCE_SIZE = 12;
-  static final byte[] IKM_INFO =
-      new byte[] {'W', 'e', 'b', 'P', 'u', 's', 'h', ':', ' ', 'i', 'n', 'f', 'o', (byte) 0};
-  static final byte[] CEK_INFO =
-      new byte[] {
-        'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ',
-        'a', 'e', 's', '1', '2', '8', 'g', 'c', 'm', (byte) 0
-      };
-  static final byte[] NONCE_INFO =
-      new byte[] {
-        'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'E', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ',
-        'n', 'o', 'n', 'c', 'e', (byte) 0
-      };
-
-  static final int SALT_SIZE = 16;
-  static final int RECORD_SIZE_LEN = 4;
-  static final int PUBLIC_KEY_SIZE_LEN = 1;
-  static final int PUBLIC_KEY_SIZE = 65;
-  //   * salt:                    16
-  //   * record size:              4
-  //   * public key size:          1
-  //   * uncompressed public key: 65
-  //   * total                  : 86
-  static final int CONTENT_CODING_HEADER_SIZE =
-      SALT_SIZE + RECORD_SIZE_LEN + PUBLIC_KEY_SIZE_LEN + PUBLIC_KEY_SIZE;
-
-  // the byte 0x2 separating the payload and the padding
-  static final byte PADDING_DELIMITER_BYTE = (byte) 2;
-  static final int PADDING_DELIMETER_SIZE = 1;
-  static final int DEFAULT_PADDING_SIZE = 0;
-  static final int TAG_SIZE = 16;
-  //   * content coding header:   86
-  //   * padding delimeter:        1
-  //   * AES-GCM tag size:        16
-  //   * Total:                  103
-  static final int CIPHERTEXT_OVERHEAD =
-      CONTENT_CODING_HEADER_SIZE + PADDING_DELIMETER_SIZE + DEFAULT_PADDING_SIZE + TAG_SIZE;
-
-  static final int MAX_CIPHERTEXT_SIZE = 4096;
-
-  static final String HMAC_SHA256 = "HMACSHA256";
-  static final EllipticCurves.PointFormatType UNCOMPRESSED_POINT_FORMAT =
-      EllipticCurves.PointFormatType.UNCOMPRESSED;
-  static final EllipticCurves.CurveType NIST_P256_CURVE_TYPE = EllipticCurves.CurveType.NIST_P256;
-
-  private WebPushConstants() {}
-}
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecrypt.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecrypt.java
deleted file mode 100644
index 2062c54..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecrypt.java
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EngineFactory;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECPoint;
-import java.util.Arrays;
-import javax.crypto.Cipher;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * A {@link HybridDecrypt} implementation for the hybrid encryption used in <a
- * href="https://tools.ietf.org/html/rfc8291">RFC 8291 - Web Push Message Encryption</a>.
- *
- * <h3>Ciphertext format</h3>
- *
- * <p>When used with <a href="https://tools.ietf.org/html/rfc8291#section-4">AES128-GCM content
- * encoding</a>, which is the only content encoding supported in this implementation, the ciphertext
- * is formatted according to RFC 8188 section 2, and looks as follows
- *
- * <pre>
- * // NOLINTNEXTLINE
- * +-----------+----------------+------------------+---------------------------------------------------
- * | salt (16) | recordsize (4) | publickeylen (1) | publickey (publickeylen) | aes128-gcm-ciphertext |
- * +-----------+----------------+------------------+---------------------------------------------------
- * </pre>
- *
- * <p>RFC 8188 divides messages into records which are encrypted independently. Web Push messages
- * cannot be longer than 3993 bytes, and are always encrypted in a single record with default size
- * of 4096 bytes. {@code aes128-gcm-ciphertext} is the encryption of the message padded with a
- * single byte of value {@code 0x02} (which indicates that this is the last and only record).
- *
- * <h3>Usage</h3>
- *
- * <pre>{@code
- * import com.google.crypto.tink.HybridDecrypt;
- * import com.google.crypto.tink.HybridEncrypt;
- * import java.security.interfaces.ECPrivateKey;
- * import java.security.interfaces.ECPublicKey;
- *
- * // Encryption.
- * ECPublicKey reicipientPublicKey = ...;
- * byte[] authSecret = ...;
- * HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder()
- *      .withAuthSecret(authSecret)
- *      .withRecipientPublicKey(recipientPublicKey)
- *      .build();
- * byte[] plaintext = ...;
- * byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null);
- *
- * // Decryption.
- * ECPrivateKey recipientPrivateKey = ...;
- * HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder()
- *      .withAuthSecret(authSecret)
- *      .withRecipientPublicKey(recipientPublicKey)
- *      .withRecipientPrivateKey(recipientPrivateKey)
- *      .build();
- * byte[] plaintext = hybridDecrypt.decrypt(ciphertext, null);
- * }</pre>
- *
- * @since 1.1.0
- */
-public final class WebPushHybridDecrypt implements HybridDecrypt {
-  private final ECPrivateKey recipientPrivateKey;
-  private final byte[] recipientPublicKey;
-  private final byte[] authSecret;
-  private final int recordSize;
-
-  private WebPushHybridDecrypt(Builder builder) throws GeneralSecurityException {
-    if (builder.recipientPrivateKey == null) {
-      throw new IllegalArgumentException(
-          "must set recipient's private key with Builder.withRecipientPrivateKey");
-    }
-    this.recipientPrivateKey = builder.recipientPrivateKey;
-
-    if (builder.recipientPublicKey == null
-        || builder.recipientPublicKey.length != WebPushConstants.PUBLIC_KEY_SIZE) {
-      throw new IllegalArgumentException(
-          "recipient public key must have " + WebPushConstants.PUBLIC_KEY_SIZE + " bytes");
-    }
-    this.recipientPublicKey = builder.recipientPublicKey;
-
-    if (builder.authSecret == null) {
-      throw new IllegalArgumentException("must set auth secret with Builder.withAuthSecret");
-    }
-    if (builder.authSecret.length != WebPushConstants.AUTH_SECRET_SIZE) {
-      throw new IllegalArgumentException(
-          "auth secret must have " + WebPushConstants.AUTH_SECRET_SIZE + " bytes");
-    }
-    this.authSecret = builder.authSecret;
-
-    if (builder.recordSize < WebPushConstants.CIPHERTEXT_OVERHEAD
-        || builder.recordSize > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
-      throw new IllegalArgumentException(
-          String.format(
-              "invalid record size (%s); must be a number between [%s, %s]",
-              builder.recordSize,
-              WebPushConstants.CIPHERTEXT_OVERHEAD,
-              WebPushConstants.MAX_CIPHERTEXT_SIZE));
-    }
-    this.recordSize = builder.recordSize;
-  }
-
-  /**
-   * Builder for {@link WebPushHybridDecrypt}.
-   *
-   * @since 1.1.0
-   */
-  public static final class Builder {
-    private ECPrivateKey recipientPrivateKey = null;
-    private byte[] recipientPublicKey = null;
-    private byte[] authSecret = null;
-    private int recordSize = WebPushConstants.MAX_CIPHERTEXT_SIZE;
-
-    public Builder() {}
-
-    /**
-     * Sets the record size.
-     *
-     * <p>If set, this value must match the record size set with {@link
-     * WebPushHybridEncrypt.Builder#withRecordSize}.
-     *
-     * <p>If not set, a record size of 4096 bytes is used. This value should work for most users.
-     */
-    public Builder withRecordSize(int val) {
-      recordSize = val;
-      return this;
-    }
-
-    /** Sets the authentication secret. */
-    public Builder withAuthSecret(final byte[] val) {
-      authSecret = val.clone();
-      return this;
-    }
-
-    /** Sets the public key of the recipient. */
-    public Builder withRecipientPublicKey(ECPublicKey val) throws GeneralSecurityException {
-      recipientPublicKey =
-          EllipticCurves.pointEncode(
-              WebPushConstants.NIST_P256_CURVE_TYPE,
-              WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
-              val.getW());
-      return this;
-    }
-
-    /**
-     * Sets the public key of the recipient.
-     *
-     * <p>The public key must be formatted as an uncompressed point format, i.e., it has {@code 65}
-     * bytes and the first byte must be {@code 0x04}.
-     */
-    public Builder withRecipientPublicKey(final byte[] val) {
-      recipientPublicKey = val.clone();
-      return this;
-    }
-
-    /** Sets the private key of the recipient. */
-    public Builder withRecipientPrivateKey(ECPrivateKey val) throws GeneralSecurityException {
-      recipientPrivateKey = val;
-      return this;
-    }
-
-    /**
-     * Sets the private key of the recipient.
-     *
-     * <p>The private key is the serialized bytes of the BigInteger returned by
-     * {@link ECPrivateKey#getS()}.
-     */
-    public Builder withRecipientPrivateKey(final byte[] val) throws GeneralSecurityException {
-      recipientPrivateKey =
-          EllipticCurves.getEcPrivateKey(WebPushConstants.NIST_P256_CURVE_TYPE, val);
-      return this;
-    }
-
-    public WebPushHybridDecrypt build() throws GeneralSecurityException {
-      return new WebPushHybridDecrypt(this);
-    }
-  }
-
-  @Override
-  public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo /* unused */)
-      throws GeneralSecurityException {
-    if (contextInfo != null) {
-      throw new GeneralSecurityException("contextInfo must be null because it is unused");
-    }
-
-    if (ciphertext.length < WebPushConstants.CIPHERTEXT_OVERHEAD) {
-      throw new GeneralSecurityException("ciphertext too short");
-    }
-
-    // A push service is not required to support more than 4096 octets of
-    // payload body. See https://tools.ietf.org/html/rfc8291#section-4.0.
-    if (ciphertext.length > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
-      throw new GeneralSecurityException("ciphertext too long");
-    }
-
-    // Unpacking.
-    ByteBuffer record = ByteBuffer.wrap(ciphertext);
-    byte[] salt = new byte[WebPushConstants.SALT_SIZE];
-    record.get(salt);
-
-    int recordSize = record.getInt();
-    if (recordSize != this.recordSize
-        || recordSize < ciphertext.length
-        || recordSize > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
-      throw new GeneralSecurityException("invalid record size: " + recordSize);
-    }
-
-    int publicKeySize = (int) record.get();
-    if (publicKeySize != WebPushConstants.PUBLIC_KEY_SIZE) {
-      throw new GeneralSecurityException("invalid ephemeral public key size: " + publicKeySize);
-    }
-
-    byte[] asPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE];
-    record.get(asPublicKey);
-    ECPoint asPublicPoint =
-        EllipticCurves.pointDecode(
-            WebPushConstants.NIST_P256_CURVE_TYPE,
-            WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
-            asPublicKey);
-
-    byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE];
-    record.get(payload);
-
-    // See https://tools.ietf.org/html/rfc8291#section-3.4.
-    byte[] ecdhSecret = EllipticCurves.computeSharedSecret(recipientPrivateKey, asPublicPoint);
-    byte[] ikm = WebPushUtil.computeIkm(ecdhSecret, authSecret, recipientPublicKey, asPublicKey);
-    byte[] cek = WebPushUtil.computeCek(ikm, salt);
-    byte[] nonce = WebPushUtil.computeNonce(ikm, salt);
-
-    return decrypt(cek, nonce, payload);
-  }
-
-  private byte[] decrypt(final byte[] key, final byte[] nonce, final byte[] ciphertext)
-      throws GeneralSecurityException {
-    Cipher cipher = EngineFactory.CIPHER.getInstance("AES/GCM/NoPadding");
-    GCMParameterSpec params = new GCMParameterSpec(8 * WebPushConstants.TAG_SIZE, nonce);
-    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), params);
-    byte[] plaintext = cipher.doFinal(ciphertext);
-    if (plaintext.length == 0) {
-      throw new GeneralSecurityException("decryption failed");
-    }
-    // Remove zero paddings.
-    int index = plaintext.length - 1;
-    while (index > 0) {
-      if (plaintext[index] != 0) {
-        break;
-      }
-      index--;
-    }
-
-    if (plaintext[index] != WebPushConstants.PADDING_DELIMITER_BYTE) {
-      throw new GeneralSecurityException("decryption failed");
-    }
-    return Arrays.copyOf(plaintext, index);
-  }
-}
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncrypt.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncrypt.java
deleted file mode 100644
index dfb7555..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncrypt.java
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.EngineFactory;
-import com.google.crypto.tink.subtle.Random;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECPoint;
-import javax.crypto.Cipher;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * A {@link HybridEncrypt} implementation for the hybrid encryption used in <a
- * href="https://tools.ietf.org/html/rfc8291">RFC 8291 - Web Push Message Encryption</a>.
- *
- * <h3>Ciphertext format</h3>
- *
- * <p>When used with <a href="https://tools.ietf.org/html/rfc8291#section-4">AES128-GCM content
- * encoding</a>, which is the only content encoding supported in this implementation, the ciphertext
- * is formatted according to RFC 8188 section 2, and looks as follows
- *
- * <pre>
- * // NOLINTNEXTLINE
- * +-----------+----------------+------------------+---------------------------------------------------
- * | salt (16) | recordsize (4) | publickeylen (1) | publickey (publickeylen) | aes128-gcm-ciphertext |
- * +-----------+----------------+------------------+---------------------------------------------------
- * </pre>
- *
- * <p>RFC 8188 divides messages into records which are encrypted independently. Web Push messages
- * cannot be longer than 3993 bytes, and are always encrypted in a single record with default size
- * of 4096 bytes. {@code aes128-gcm-ciphertext} is the encryption of the message padded with a
- * single byte of value {@code 0x02} (which indicates that this is the last and only record).
- *
- * <h3>Usage</h3>
- *
- * <pre>{@code
- * import com.google.crypto.tink.HybridDecrypt;
- * import com.google.crypto.tink.HybridEncrypt;
- * import java.security.interfaces.ECPrivateKey;
- * import java.security.interfaces.ECPublicKey;
- *
- * // Encryption.
- * ECPublicKey reicipientPublicKey = ...;
- * byte[] authSecret = ...;
- * HybridEncrypt hybridEncrypt = new WebPushHybridEncrypt.Builder()
- *      .withAuthSecret(authSecret)
- *      .withRecipientPublicKey(recipientPublicKey)
- *      .build();
- * byte[] plaintext = ...;
- * byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null);
- *
- * // Decryption.
- * ECPrivateKey recipientPrivateKey = ...;
- * HybridDecrypt hybridDecrypt = new WebPushHybridDecrypt.Builder()
- *      .withAuthSecret(authSecret)
- *      .withRecipientPublicKey(recipientPublicKey)
- *      .withRecipientPrivateKey(recipientPrivateKey)
- *      .build();
- * byte[] plaintext = hybridDecrypt.decrypt(ciphertext, null);
- * }</pre>
- *
- * @since 1.1.0
- */
-public final class WebPushHybridEncrypt implements HybridEncrypt {
-  private final byte[] recipientPublicKey;
-  private final byte[] authSecret;
-  private final ECPoint recipientPublicPoint;
-  private final int recordSize;
-  private final int paddingSize;
-
-  private WebPushHybridEncrypt(Builder builder) throws GeneralSecurityException {
-    if (builder.recipientPublicKey == null || builder.recipientPublicPoint == null) {
-      throw new IllegalArgumentException(
-          "must set recipient's public key with Builder.withRecipientPublicKey");
-    }
-    this.recipientPublicKey = builder.recipientPublicKey;
-    this.recipientPublicPoint = builder.recipientPublicPoint;
-
-    if (builder.authSecret == null) {
-      throw new IllegalArgumentException("must set auth secret with Builder.withAuthSecret");
-    }
-    if (builder.authSecret.length != WebPushConstants.AUTH_SECRET_SIZE) {
-      throw new IllegalArgumentException(
-          "auth secret must have " + WebPushConstants.AUTH_SECRET_SIZE + " bytes");
-    }
-    this.authSecret = builder.authSecret;
-    this.recordSize = builder.recordSize;
-    this.paddingSize = builder.paddingSize;
-  }
-
-  /**
-   * Builder for {@link WebPushHybridEncrypt}.
-   *
-   * @since 1.1.0
-   */
-  public static final class Builder {
-    private byte[] recipientPublicKey = null;
-    private ECPoint recipientPublicPoint = null;
-    private byte[] authSecret = null;
-    private int recordSize = WebPushConstants.MAX_CIPHERTEXT_SIZE;
-    private int paddingSize = WebPushConstants.DEFAULT_PADDING_SIZE;
-
-    public Builder() {}
-
-    /**
-     * Sets the record size.
-     *
-     * <p>If set, this value must match the record size set with {@link
-     * WebPushHybridEncrypt.Builder#withRecordSize}.
-     *
-     * <p>If not set, a record size of 4096 bytes is used. This value should work for most users.
-     */
-    public Builder withRecordSize(int val) {
-      if (val < WebPushConstants.CIPHERTEXT_OVERHEAD
-          || val > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
-        throw new IllegalArgumentException(
-            String.format(
-                "invalid record size (%s); must be a number between [%s, %s]",
-                val, WebPushConstants.CIPHERTEXT_OVERHEAD, WebPushConstants.MAX_CIPHERTEXT_SIZE));
-      }
-
-      recordSize = val;
-      return this;
-    }
-
-    /**
-     * Sets the padding size which is default to 0.
-     *
-     * <p>The padding size cannot be larger than
-     */
-    public Builder withPaddingSize(int val) {
-      if (val < 0
-          || val > WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD) {
-        throw new IllegalArgumentException(
-            String.format(
-                "invalid padding size (%s); must be a number between [%s, %s]",
-                val,
-                0,
-                WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD));
-      }
-
-      paddingSize = val;
-      return this;
-    }
-
-    /** Sets the authentication secret. */
-    public Builder withAuthSecret(final byte[] val) {
-      authSecret = val.clone();
-      return this;
-    }
-
-    /** Sets the public key of the recipient. */
-    public Builder withRecipientPublicKey(ECPublicKey val) throws GeneralSecurityException {
-      recipientPublicPoint = val.getW();
-      recipientPublicKey =
-          EllipticCurves.pointEncode(
-              WebPushConstants.NIST_P256_CURVE_TYPE,
-              WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
-              val.getW());
-
-      return this;
-    }
-
-    /**
-     * Sets the public key of the recipient.
-     *
-     * <p>The public key must be formatted as an uncompressed point format, i.e., it has {@code 65}
-     * bytes and the first byte must be {@code 0x04}.
-     */
-    public Builder withRecipientPublicKey(final byte[] val) throws GeneralSecurityException {
-      recipientPublicKey = val.clone();
-      recipientPublicPoint =
-          EllipticCurves.pointDecode(
-              WebPushConstants.NIST_P256_CURVE_TYPE,
-              WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
-              recipientPublicKey);
-      return this;
-    }
-
-    public WebPushHybridEncrypt build() throws GeneralSecurityException {
-      return new WebPushHybridEncrypt(this);
-    }
-  }
-
-  @Override
-  public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo /* unused */)
-      throws GeneralSecurityException {
-    if (contextInfo != null) {
-      throw new GeneralSecurityException("contextInfo must be null because it is unused");
-    }
-
-    if (plaintext.length > recordSize - paddingSize - WebPushConstants.CIPHERTEXT_OVERHEAD) {
-      throw new GeneralSecurityException(
-          String.format(
-              "plaintext too long; with record size = %d and padding size = %d, plaintext cannot"
-                  + " be longer than %d",
-              recordSize,
-              paddingSize,
-              recordSize - paddingSize - WebPushConstants.CIPHERTEXT_OVERHEAD));
-    }
-
-    // See https://tools.ietf.org/html/rfc8291#section-3.4.
-    KeyPair keyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey ephemeralPrivateKey = (ECPrivateKey) keyPair.getPrivate();
-    ECPublicKey ephemeralPublicKey = (ECPublicKey) keyPair.getPublic();
-    byte[] ecdhSecret =
-        EllipticCurves.computeSharedSecret(ephemeralPrivateKey, recipientPublicPoint);
-    byte[] ephemeralPublicKeyBytes =
-        EllipticCurves.pointEncode(
-            WebPushConstants.NIST_P256_CURVE_TYPE,
-            WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
-            ephemeralPublicKey.getW());
-    byte[] ikm =
-        WebPushUtil.computeIkm(ecdhSecret, authSecret, recipientPublicKey, ephemeralPublicKeyBytes);
-    byte[] salt = Random.randBytes(WebPushConstants.SALT_SIZE);
-    byte[] cek = WebPushUtil.computeCek(ikm, salt);
-    byte[] nonce = WebPushUtil.computeNonce(ikm, salt);
-    return ByteBuffer.allocate(
-            WebPushConstants.CIPHERTEXT_OVERHEAD + plaintext.length + paddingSize)
-        .put(salt)
-        .putInt(recordSize)
-        .put((byte) WebPushConstants.PUBLIC_KEY_SIZE)
-        .put(ephemeralPublicKeyBytes)
-        .put(encrypt(cek, nonce, plaintext))
-        .array();
-  }
-
-  private byte[] encrypt(final byte[] key, final byte[] nonce, final byte[] plaintext)
-      throws GeneralSecurityException {
-    Cipher cipher = EngineFactory.CIPHER.getInstance("AES/GCM/NoPadding");
-    GCMParameterSpec params = new GCMParameterSpec(8 * WebPushConstants.TAG_SIZE, nonce);
-    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), params);
-    byte[] paddedPlaintext = new byte[plaintext.length + 1 + paddingSize];
-    paddedPlaintext[plaintext.length] = WebPushConstants.PADDING_DELIMITER_BYTE;
-    System.arraycopy(plaintext, 0, paddedPlaintext, 0, plaintext.length);
-    return cipher.doFinal(paddedPlaintext);
-  }
-}
diff --git a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushUtil.java b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushUtil.java
deleted file mode 100644
index c2fc72e..0000000
--- a/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/WebPushUtil.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import com.google.crypto.tink.subtle.Bytes;
-import com.google.crypto.tink.subtle.Hkdf;
-import java.security.GeneralSecurityException;
-
-/** Various helpers. */
-final class WebPushUtil {
-  public static byte[] computeIkm(
-      final byte[] ecdhSecret,
-      final byte[] authSecret,
-      final byte[] uaPublic,
-      final byte[] asPublic)
-      throws GeneralSecurityException {
-    byte[] keyInfo = Bytes.concat(WebPushConstants.IKM_INFO, uaPublic, asPublic);
-    return Hkdf.computeHkdf(
-        WebPushConstants.HMAC_SHA256,
-        ecdhSecret /* ikm */,
-        authSecret /* salt */,
-        keyInfo,
-        WebPushConstants.IKM_SIZE);
-  }
-
-  public static byte[] computeCek(final byte[] ikm, final byte[] salt)
-      throws GeneralSecurityException {
-    return Hkdf.computeHkdf(
-        WebPushConstants.HMAC_SHA256,
-        ikm,
-        salt,
-        WebPushConstants.CEK_INFO,
-        WebPushConstants.CEK_KEY_SIZE);
-  }
-
-  public static byte[] computeNonce(final byte[] ikm, final byte[] salt)
-      throws GeneralSecurityException {
-    return Hkdf.computeHkdf(
-        WebPushConstants.HMAC_SHA256,
-        ikm,
-        salt,
-        WebPushConstants.NONCE_INFO,
-        WebPushConstants.NONCE_SIZE);
-  }
-
-  private WebPushUtil() {}
-}
diff --git a/apps/webpush/src/test/BUILD.bazel b/apps/webpush/src/test/BUILD.bazel
deleted file mode 100644
index 309b3ad..0000000
--- a/apps/webpush/src/test/BUILD.bazel
+++ /dev/null
@@ -1,38 +0,0 @@
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# Tests
-
-java_library(
-    name = "generator_test",
-    testonly = 1,
-    srcs = glob([
-        "**/*.java",
-    ]),
-    deps = [
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_constants",
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_decrypt",
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_hybrid_encrypt",
-        "//webpush/src/main/java/com/google/crypto/tink/apps/webpush:web_push_util",
-        "@maven//:junit_junit",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:base64",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:hex",
-        "@tink_java//src/main/java/com/google/crypto/tink/subtle:random",
-        "@tink_java//src/main/java/com/google/crypto/tink/testing:test_util",
-    ],
-)
-
-gen_java_test_rules(
-    test_files = glob([
-        "**/*Test.java",
-    ]),
-    deps = [
-        ":generator_test",
-    ],
-)
diff --git a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecryptTest.java b/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecryptTest.java
deleted file mode 100644
index f8a6987..0000000
--- a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridDecryptTest.java
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.subtle.Base64;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Random;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.Arrays;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code WebPushHybridDecrypt}. */
-@RunWith(JUnit4.class)
-public class WebPushHybridDecryptTest {
-  // Copied from https://tools.ietf.org/html/rfc8291#section-5.
-  private static final String PLAINTEXT = "V2hlbiBJIGdyb3cgdXAsIEkgd2FudCB0byBiZSBhIHdhdGVybWVsb24";
-  private static final String RECEIVER_PRIVATE_KEY = "q1dXpw3UpT5VOmu_cf_v6ih07Aems3njxI-JWgLcM94";
-  private static final String RECEIVER_PUBLIC_KEY =
-      "BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4";
-  private static final String AUTH_SECRET = "BTBZMqHH6r4Tts7J_aSIgg";
-  private static final String CIPHERTEXT =
-      "DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27ml"
-          + "mlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPT"
-          + "pK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN";
-  private static final int RECORD_SIZE = 4096;
-
-  @Test
-  public void testWithRfc8291TestVector() throws Exception {
-    byte[] plaintext = Base64.urlSafeDecode(PLAINTEXT);
-    byte[] recipientPrivateKey = Base64.urlSafeDecode(RECEIVER_PRIVATE_KEY);
-    byte[] recipientPublicKey = Base64.urlSafeDecode(RECEIVER_PUBLIC_KEY);
-    byte[] authSecret = Base64.urlSafeDecode(AUTH_SECRET);
-    byte[] ciphertext = Base64.urlSafeDecode(CIPHERTEXT);
-
-    HybridDecrypt hybridDecrypt =
-        new WebPushHybridDecrypt.Builder()
-            .withRecordSize(RECORD_SIZE)
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(recipientPublicKey)
-            .withRecipientPrivateKey(recipientPrivateKey)
-            .build();
-    assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
-  }
-
-  @Test
-  public void testEncryptDecryptWithInvalidRecordSizes() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-
-    // Test with out of range record sizes.
-    {
-      try {
-        new WebPushHybridDecrypt.Builder()
-            .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE + 1)
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-        fail("Expected IllegalArgumentException");
-      } catch (IllegalArgumentException ex) {
-        // expected.
-      }
-
-      try {
-        new WebPushHybridDecrypt.Builder()
-            .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1)
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-
-      } catch (IllegalArgumentException ex) {
-        // expected.
-      }
-    }
-
-    // Test with random mismatched record size.
-    {
-      for (int i = 0; i < 50; i++) {
-        int recordSize =
-            WebPushConstants.CIPHERTEXT_OVERHEAD
-                + Random.randInt(
-                    WebPushConstants.MAX_CIPHERTEXT_SIZE
-                        - WebPushConstants.CIPHERTEXT_OVERHEAD
-                        - 1);
-        HybridEncrypt hybridEncrypt =
-            new WebPushHybridEncrypt.Builder()
-                .withRecordSize(recordSize)
-                .withAuthSecret(authSecret)
-                .withRecipientPublicKey(uaPublicKey)
-                .build();
-        HybridDecrypt hybridDecrypt =
-            new WebPushHybridDecrypt.Builder()
-                .withRecordSize(recordSize + 1)
-                .withAuthSecret(authSecret)
-                .withRecipientPublicKey(uaPublicKey)
-                .withRecipientPrivateKey(uaPrivateKey)
-                .build();
-        byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD);
-        byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-
-        try {
-          hybridDecrypt.decrypt(ciphertext, null /* contextInfo */);
-          fail("Expected GeneralSecurityException");
-        } catch (GeneralSecurityException ex) {
-          // expected.
-        }
-      }
-    }
-  }
-
-  @Test
-  public void testNonNullContextInfo() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-
-    HybridEncrypt hybridEncrypt =
-        new WebPushHybridEncrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .build();
-    HybridDecrypt hybridDecrypt =
-        new WebPushHybridDecrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-    byte[] plaintext = Random.randBytes(20);
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-    try {
-      byte[] contextInfo = new byte[0];
-      hybridDecrypt.decrypt(ciphertext, contextInfo);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException ex) {
-      // expected;
-    }
-  }
-
-  @Test
-  public void testModifyCiphertext() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-
-    HybridEncrypt hybridEncrypt =
-        new WebPushHybridEncrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .build();
-    HybridDecrypt hybridDecrypt =
-        new WebPushHybridDecrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-    byte[] plaintext = Random.randBytes(20);
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-
-    // Flipping bits.
-    for (int b = 0; b < ciphertext.length; b++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length);
-        modified[b] ^= (byte) (1 << bit);
-        try {
-          byte[] unused = hybridDecrypt.decrypt(modified, null /* contextInfo */);
-          fail("Decrypting modified ciphertext should fail");
-        } catch (GeneralSecurityException ex) {
-          // This is expected.
-        }
-      }
-    }
-
-    // Truncate the message.
-    for (int length = 0; length < ciphertext.length; length++) {
-      byte[] modified = Arrays.copyOf(ciphertext, length);
-      try {
-        byte[] unused = hybridDecrypt.decrypt(modified, null /* contextInfo */);
-        fail("Decrypting modified ciphertext should fail");
-      } catch (GeneralSecurityException ex) {
-        // This is expected.
-      }
-    }
-  }
-
-  @Test
-  public void testEncryptDecrypt_withPadding_shouldWork() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-
-    int paddingSize = 20;
-    int plaintextSize = 20;
-    HybridEncrypt hybridEncrypt =
-        new WebPushHybridEncrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withPaddingSize(paddingSize)
-            .withRecipientPublicKey(uaPublicKey)
-            .build();
-    HybridDecrypt hybridDecrypt =
-        new WebPushHybridDecrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-    byte[] plaintext = Random.randBytes(plaintextSize);
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-
-    assertEquals(
-        ciphertext.length, plaintext.length + paddingSize + WebPushConstants.CIPHERTEXT_OVERHEAD);
-    assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
-  }
-}
diff --git a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncryptTest.java b/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncryptTest.java
deleted file mode 100644
index 7b84b84..0000000
--- a/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncryptTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.apps.webpush;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.crypto.tink.HybridDecrypt;
-import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.subtle.EllipticCurves;
-import com.google.crypto.tink.subtle.Hex;
-import com.google.crypto.tink.subtle.Random;
-import com.google.crypto.tink.testing.TestUtil;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.util.Set;
-import java.util.TreeSet;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Unit tests for {@code WebPushHybridEncrypt}. */
-@RunWith(JUnit4.class)
-public class WebPushHybridEncryptTest {
-  @Test
-  public void testEncryptDecrypt() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] uaPublicKeyBytes =
-        EllipticCurves.pointEncode(
-            WebPushConstants.NIST_P256_CURVE_TYPE,
-            WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
-            uaPublicKey.getW());
-    byte[] authSecret = Random.randBytes(16);
-    HybridEncrypt hybridEncrypt =
-        new WebPushHybridEncrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKeyBytes)
-            .build();
-    HybridDecrypt hybridDecrypt =
-        new WebPushHybridDecrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKeyBytes)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-
-    Set<String> salts = new TreeSet<>();
-    Set<String> ephemeralPublicKeys = new TreeSet<>();
-    Set<String> payloads = new TreeSet<>();
-    int numTests = 100;
-    if (TestUtil.isTsan()) {
-      numTests = 5;
-    }
-    for (int j = 0; j < numTests; j++) {
-      byte[] plaintext = Random.randBytes(j);
-      byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-      assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
-      assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
-
-      // Checks that the encryption is randomized.
-      ByteBuffer record = ByteBuffer.wrap(ciphertext);
-      byte[] salt = new byte[WebPushConstants.SALT_SIZE];
-      record.get(salt);
-      salts.add(Hex.encode(salt));
-
-      int unused1 = record.getInt();
-      int unused2 = (int) record.get();
-
-      byte[] ephemeralPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE];
-      record.get(ephemeralPublicKey);
-      ephemeralPublicKeys.add(Hex.encode(ephemeralPublicKey));
-
-      byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE];
-      record.get(payload);
-      payloads.add(Hex.encode(payload));
-    }
-    assertEquals(numTests, salts.size());
-    assertEquals(numTests, ephemeralPublicKeys.size());
-    assertEquals(numTests, payloads.size());
-  }
-
-  @Test
-  public void testEncryptDecryptWithVaryingRecordSizes() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-
-    int numTests = 100;
-    if (TestUtil.isTsan()) {
-      numTests = 5;
-    }
-    // Test with random, valid record sizes.
-    for (int i = 0; i < numTests; i++) {
-      int recordSize =
-          WebPushConstants.CIPHERTEXT_OVERHEAD
-              + Random.randInt(
-                  WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD);
-      HybridEncrypt hybridEncrypt =
-          new WebPushHybridEncrypt.Builder()
-              .withRecordSize(recordSize)
-              .withAuthSecret(authSecret)
-              .withRecipientPublicKey(uaPublicKey)
-              .build();
-      HybridDecrypt hybridDecrypt =
-          new WebPushHybridDecrypt.Builder()
-              .withRecordSize(recordSize)
-              .withAuthSecret(authSecret)
-              .withRecipientPublicKey(uaPublicKey)
-              .withRecipientPrivateKey(uaPrivateKey)
-              .build();
-
-      byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD);
-      byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-      assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
-      assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
-    }
-  }
-
-  @Test
-  public void testEncryptDecrypt_largestPossibleRecordSize() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-    // Test with largest possible record size.
-    HybridEncrypt hybridEncrypt =
-        new WebPushHybridEncrypt.Builder()
-            .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE)
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .build();
-    HybridDecrypt hybridDecrypt =
-        new WebPushHybridDecrypt.Builder()
-            .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE)
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-    byte[] plaintext =
-        Random.randBytes(
-            WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD);
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-    assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
-    assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
-  }
-
-  @Test
-  public void testEncryptDecrypt_smallestPossibleRecordSize() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-    // Test with smallest possible record size.
-    HybridEncrypt hybridEncrypt =
-        new WebPushHybridEncrypt.Builder()
-            .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD)
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .build();
-    HybridDecrypt hybridDecrypt =
-        new WebPushHybridDecrypt.Builder()
-            .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD)
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .withRecipientPrivateKey(uaPrivateKey)
-            .build();
-    byte[] plaintext = new byte[0];
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
-    assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
-    assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
-  }
-
-  @Test
-  public void testEncryptDecrypt_outOfRangeRecordSize_throws() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-
-    try {
-      new WebPushHybridEncrypt.Builder()
-          .withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE + 1)
-          .withAuthSecret(authSecret)
-          .withRecipientPublicKey(uaPublicKey)
-          .build();
-      fail("Expected IllegalArgumentException");
-    } catch (IllegalArgumentException ex) {
-      // expected.
-    }
-
-    try {
-      new WebPushHybridEncrypt.Builder()
-          .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1)
-          .withAuthSecret(authSecret)
-          .withRecipientPublicKey(uaPublicKey)
-          .build();
-      fail("Expected IllegalArgumentException");
-    } catch (IllegalArgumentException ex) {
-      // expected.
-    }
-  }
-
-  @Test
-  public void testNonNullContextInfo() throws Exception {
-    KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
-    ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
-    byte[] authSecret = Random.randBytes(16);
-
-    HybridEncrypt hybridEncrypt =
-        new WebPushHybridEncrypt.Builder()
-            .withAuthSecret(authSecret)
-            .withRecipientPublicKey(uaPublicKey)
-            .build();
-    byte[] plaintext = Random.randBytes(20);
-    byte[] contextInfo = new byte[0];
-
-    try {
-      byte[] unusedCiphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException ex) {
-      // expected;
-    }
-  }
-}
-
diff --git a/cc/.bazelrc b/cc/.bazelrc
index 94aad05..e2435c9 100644
--- a/cc/.bazelrc
+++ b/cc/.bazelrc
@@ -1,3 +1,7 @@
 # Fix for grpc build error on macOS.
 # See: https://github.com/bazelbuild/bazel/issues/4341
-build --copt -DGRPC_BAZEL_BUILD
+build --copt=-DGRPC_BAZEL_BUILD
+# Minumum C++ version. Override it building this project with
+# `bazel build --cxxopt='-std=c++<XY>' --host_cxxopt='c++<XY>' ...`
+# (Both -std and --host_cxxopt must be set to force the desired version.)
+build --cxxopt='-std=c++14' --host_cxxopt='-std=c++14'
diff --git a/cc/.bazelversion b/cc/.bazelversion
index ac14c3d..09b254e 100644
--- a/cc/.bazelversion
+++ b/cc/.bazelversion
@@ -1 +1 @@
-5.1.1
+6.0.0
diff --git a/cc/BUILD.bazel b/cc/BUILD.bazel
index 5064fdb..c88dd8e 100644
--- a/cc/BUILD.bazel
+++ b/cc/BUILD.bazel
@@ -23,8 +23,6 @@
         "aead_key_templates.h",
         "binary_keyset_reader.h",
         "binary_keyset_writer.h",
-        "catalogue.h",
-        "config.h",
         "deterministic_aead.h",
         "deterministic_aead_config.h",
         "deterministic_aead_factory.h",
@@ -77,6 +75,7 @@
         ":input_stream",
         ":json_keyset_reader",
         ":json_keyset_writer",
+        ":key",
         ":key_manager",
         ":keyset_handle",
         ":keyset_manager",
@@ -396,41 +395,6 @@
 )
 
 cc_library(
-    name = "catalogue",
-    hdrs = ["catalogue.h"],
-    include_prefix = "tink",
-    deps = [
-        ":key_manager",
-        "//util:statusor",
-        "@com_google_absl//absl/base:core_headers",
-    ],
-)
-
-cc_library(
-    name = "config",
-    srcs = ["core/config.cc"],
-    hdrs = ["config.h"],
-    include_prefix = "tink",
-    deps = [
-        ":catalogue",
-        ":key_manager",
-        ":registry",
-        "//aead:aead_config",
-        "//daead:deterministic_aead_config",
-        "//hybrid:hybrid_config",
-        "//mac:mac_config",
-        "//proto:config_cc_proto",
-        "//signature:signature_config",
-        "//streamingaead:streaming_aead_config",
-        "//util:errors",
-        "//util:status",
-        "//util:statusor",
-        "@com_google_absl//absl/status",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
-cc_library(
     name = "crypto_format",
     srcs = ["core/crypto_format.cc"],
     hdrs = ["crypto_format.h"],
@@ -453,6 +417,7 @@
         "//proto:tink_cc_proto",
         "//util:errors",
         "//util:statusor",
+        "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
@@ -507,20 +472,54 @@
     visibility = ["//visibility:public"],
     deps = [
         ":aead",
+        ":configuration",
+        ":insecure_secret_key_access",
+        ":key",
+        ":key_gen_configuration",
         ":key_manager",
+        ":key_status",
         ":keyset_reader",
         ":keyset_writer",
         ":primitive_set",
         ":registry",
+        "//internal:configuration_impl",
+        "//internal:key_gen_configuration_impl",
         "//internal:key_info",
+        "//internal:key_status_util",
+        "//internal:mutable_serialization_registry",
+        "//internal:proto_key_serialization",
+        "//internal:util",
         "//proto:tink_cc_proto",
         "//util:errors",
         "//util:keyset_util",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/log:check",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_library(
+    name = "keyset_handle_builder",
+    srcs = ["core/keyset_handle_builder.cc"],
+    hdrs = ["keyset_handle_builder.h"],
+    include_prefix = "tink",
+    visibility = ["//visibility:public"],
+    deps = [
+        ":key",
+        ":key_status",
+        ":keyset_handle",
+        ":parameters",
+        "//internal:keyset_handle_builder_entry",
+        "//proto:tink_cc_proto",
+        "//subtle:random",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
     ],
 )
 
@@ -565,9 +564,9 @@
     include_prefix = "tink",
     visibility = ["//visibility:public"],
     deps = [
+        ":key_gen_configuration",
         ":keyset_handle",
-        ":keyset_reader",
-        ":registry",
+        "//internal:key_gen_configuration_impl",
         "//proto:tink_cc_proto",
         "//util:enums",
         "//util:errors",
@@ -703,6 +702,19 @@
 )
 
 cc_library(
+    name = "partial_key_access_token",
+    hdrs = ["partial_key_access_token.h"],
+    include_prefix = "tink",
+)
+
+cc_library(
+    name = "partial_key_access",
+    hdrs = ["partial_key_access.h"],
+    include_prefix = "tink",
+    deps = [":partial_key_access_token"],
+)
+
+cc_library(
     name = "secret_key_access_token",
     hdrs = ["secret_key_access_token.h"],
     include_prefix = "tink",
@@ -712,9 +724,31 @@
     name = "insecure_secret_key_access",
     hdrs = ["insecure_secret_key_access.h"],
     include_prefix = "tink",
+    visibility = ["//visibility:public"],
     deps = [":secret_key_access_token"],
 )
 
+cc_library(
+    name = "restricted_data",
+    srcs = ["core/restricted_data.cc"],
+    hdrs = ["restricted_data.h"],
+    include_prefix = "tink",
+    deps = [
+        ":secret_key_access_token",
+        "//subtle:random",
+        "//util:secret_data",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/log:check",
+    ],
+)
+
+cc_library(
+    name = "key_status",
+    hdrs = ["key_status.h"],
+    include_prefix = "tink",
+    visibility = ["//visibility:public"],
+)
+
 # tests
 
 cc_test(
@@ -723,6 +757,7 @@
     srcs = ["core/version_test.cc"],
     deps = [
         ":version",
+        "//internal:util",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -788,18 +823,6 @@
 )
 
 cc_test(
-    name = "config_test",
-    size = "small",
-    srcs = ["core/config_test.cc"],
-    deps = [
-        ":config",
-        ":mac",
-        "//proto:config_cc_proto",
-        "@com_google_googletest//:gtest_main",
-    ],
-)
-
-cc_test(
     name = "crypto_format_test",
     size = "small",
     srcs = ["core/crypto_format_test.cc"],
@@ -820,6 +843,8 @@
         ":core/key_manager_impl",
         ":json_keyset_reader",
         ":json_keyset_writer",
+        ":key_gen_configuration",
+        ":key_status",
         ":keyset_handle",
         ":primitive_set",
         ":primitive_wrapper",
@@ -827,11 +852,16 @@
         "//aead:aead_key_templates",
         "//aead:aead_wrapper",
         "//aead:aes_gcm_key_manager",
+        "//config:fips_140_2",
+        "//config:key_gen_fips_140_2",
         "//config:tink_config",
+        "//config/internal:global_registry",
+        "//internal:fips_utils",
+        "//internal:key_gen_configuration_impl",
+        "//proto:aes_gcm_siv_cc_proto",
         "//proto:tink_cc_proto",
         "//signature:ecdsa_sign_key_manager",
         "//signature:signature_key_templates",
-        "//util:protobuf_helper",
         "//util:status",
         "//util:test_keyset_handle",
         "//util:test_matchers",
@@ -843,6 +873,34 @@
 )
 
 cc_test(
+    name = "keyset_handle_builder_test",
+    srcs = ["core/keyset_handle_builder_test.cc"],
+    deps = [
+        ":insecure_secret_key_access",
+        ":key_status",
+        ":keyset_handle_builder",
+        ":partial_key_access",
+        "//config:tink_config",
+        "//internal:legacy_proto_key",
+        "//internal:legacy_proto_parameters",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//mac:aes_cmac_key",
+        "//mac:aes_cmac_parameters",
+        "//mac:mac_key_templates",
+        "//proto:aes_cmac_cc_proto",
+        "//proto:tink_cc_proto",
+        "//subtle:random",
+        "//util:status",
+        "//util:test_matchers",
+        "//util:test_util",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "key_manager_test",
     size = "small",
     srcs = ["core/key_manager_test.cc"],
@@ -861,7 +919,6 @@
     size = "small",
     srcs = ["core/keyset_manager_test.cc"],
     deps = [
-        ":config",
         ":keyset_handle",
         ":keyset_manager",
         "//aead:aead_config",
@@ -893,9 +950,11 @@
     size = "small",
     srcs = ["core/primitive_set_test.cc"],
     deps = [
+        ":cleartext_keyset_handle",
         ":crypto_format",
         ":mac",
         ":primitive_set",
+        "//keyderivation:keyset_deriver",
         "//proto:tink_cc_proto",
         "//util:test_matchers",
         "//util:test_util",
@@ -999,3 +1058,82 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "core/partial_key_access_token_test",
+    srcs = ["core/partial_key_access_token_test.cc"],
+    deps = [
+        ":partial_key_access",
+        ":partial_key_access_token",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "core/restricted_data_test",
+    srcs = ["core/restricted_data_test.cc"],
+    deps = [
+        ":insecure_secret_key_access",
+        ":restricted_data",
+        "//subtle:random",
+        "//util:secret_data",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "proto_keyset_format",
+    srcs = ["proto_keyset_format.cc"],
+    hdrs = ["proto_keyset_format.h"],
+    include_prefix = "tink",
+    visibility = ["//visibility:public"],
+    deps = [
+        ":binary_keyset_reader",
+        ":binary_keyset_writer",
+        ":cleartext_keyset_handle",
+        ":keyset_handle",
+        ":secret_key_access_token",
+        "//util:secret_data",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "proto_keyset_format_test",
+    srcs = ["proto_keyset_format_test.cc"],
+    deps = [
+        ":insecure_secret_key_access",
+        ":keyset_handle_builder",
+        ":mac",
+        ":proto_keyset_format",
+        "//config:tink_config",
+        "//internal:legacy_proto_parameters",
+        "//internal:proto_parameters_serialization",
+        "//mac:mac_key_templates",
+        "//signature:signature_key_templates",
+        "//util:secret_data",
+        "//util:test_matchers",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "configuration",
+    hdrs = ["configuration.h"],
+    include_prefix = "tink",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//internal:key_type_info_store",
+        "//internal:keyset_wrapper_store",
+    ],
+)
+
+cc_library(
+    name = "key_gen_configuration",
+    hdrs = ["key_gen_configuration.h"],
+    include_prefix = "tink",
+    visibility = ["//visibility:public"],
+    deps = ["//internal:key_type_info_store"],
+)
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 2ef21b2..e13bee0 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -181,19 +181,6 @@
   public_configs = [ "//third_party/tink:tink_config" ]
 }
 
-# CC Library : catalogue
-source_set("catalogue") {
-  configs += [ "//build/config:no_rtti" ]
-  configs -= [ "//build/config:no_rtti" ]
-  sources = [ "catalogue.h" ]
-  public_deps = [
-    ":key_manager",
-    "//third_party/abseil-cpp/absl/base:core_headers",
-    "//third_party/tink/cc/util:statusor",
-  ]
-  public_configs = [ "//third_party/tink:tink_config" ]
-}
-
 # CC Library : crypto_format
 source_set("crypto_format") {
   configs += [ "//build/config:no_rtti" ]
@@ -221,6 +208,7 @@
   ]
   public_deps = [
     ":crypto_format",
+    "//third_party/abseil-cpp/absl/base:core_headers",
     "//third_party/abseil-cpp/absl/container:flat_hash_map",
     "//third_party/abseil-cpp/absl/memory:memory",
     "//third_party/abseil-cpp/absl/status:status",
@@ -268,17 +256,30 @@
   ]
   public_deps = [
     ":aead",
+    ":configuration",
+    ":insecure_secret_key_access",
+    ":key",
+    ":key_gen_configuration",
     ":key_manager",
+    ":key_status",
     ":keyset_reader",
     ":keyset_writer",
     ":primitive_set",
     ":registry",
     "//third_party/abseil-cpp/absl/base:core_headers",
     "//third_party/abseil-cpp/absl/container:flat_hash_map",
+    "//third_party/abseil-cpp/absl/log:check",
     "//third_party/abseil-cpp/absl/memory:memory",
     "//third_party/abseil-cpp/absl/status:status",
     "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc/internal:configuration_impl",
+    "//third_party/tink/cc/internal:key_gen_configuration_impl",
     "//third_party/tink/cc/internal:key_info",
+    "//third_party/tink/cc/internal:key_status_util",
+    "//third_party/tink/cc/internal:mutable_serialization_registry",
+    "//third_party/tink/cc/internal:proto_key_serialization",
+    "//third_party/tink/cc/internal:util",
     "//third_party/tink/cc/proto:tink_proto",
     "//third_party/tink/cc/util:errors",
     "//third_party/tink/cc/util:keyset_util",
@@ -433,3 +434,104 @@
   ]
   public_configs = [ "//third_party/tink:tink_config" ]
 }
+
+# CC Library : parameters
+source_set("parameters") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "parameters.h" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key
+source_set("key") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "key.h" ]
+  public_deps = [
+    ":parameters",
+    "//third_party/abseil-cpp/absl/types:optional",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : partial_key_access_token
+source_set("partial_key_access_token") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "partial_key_access_token.h" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : partial_key_access
+source_set("partial_key_access") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "partial_key_access.h" ]
+  public_deps = [ ":partial_key_access_token" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : secret_key_access_token
+source_set("secret_key_access_token") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "secret_key_access_token.h" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : insecure_secret_key_access
+source_set("insecure_secret_key_access") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "insecure_secret_key_access.h" ]
+  public_deps = [ ":secret_key_access_token" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : restricted_data
+source_set("restricted_data") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "core/restricted_data.cc",
+    "restricted_data.h",
+  ]
+  public_deps = [
+    ":secret_key_access_token",
+    "//third_party/abseil-cpp/absl/log:check",
+    "//third_party/boringssl:crypto",
+    "//third_party/tink/cc/subtle:random",
+    "//third_party/tink/cc/util:secret_data",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_status
+source_set("key_status") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "key_status.h" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : configuration
+source_set("configuration") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "configuration.h" ]
+  public_deps = [
+    "//third_party/tink/cc/internal:key_type_info_store",
+    "//third_party/tink/cc/internal:keyset_wrapper_store",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_gen_configuration
+source_set("key_gen_configuration") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "key_gen_configuration.h" ]
+  public_deps = [ "//third_party/tink/cc/internal:key_type_info_store" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
diff --git a/cc/CMakeLists.txt b/cc/CMakeLists.txt
index a7defba..1cd07e4 100644
--- a/cc/CMakeLists.txt
+++ b/cc/CMakeLists.txt
@@ -6,6 +6,7 @@
 add_subdirectory(mac)
 add_subdirectory(monitoring)
 add_subdirectory(jwt)
+add_subdirectory(keyderivation)
 add_subdirectory(prf)
 add_subdirectory(signature)
 add_subdirectory(streamingaead)
@@ -16,12 +17,6 @@
 
 # Configuration settings for the build.
 
-if (TINK_USE_ABSL_STATUS)
-    target_compile_definitions(tink_util_status PUBLIC TINK_USE_ABSL_STATUS)
-endif()
-if (TINK_USE_ABSL_STATUSOR)
-    target_compile_definitions(tink_util_statusor INTERFACE TINK_USE_ABSL_STATUSOR)
-endif()
 if(USE_ONLY_FIPS)
     target_compile_definitions(tink_internal_fips_utils PUBLIC TINK_USE_ONLY_FIPS)
 endif()
@@ -39,9 +34,7 @@
     aead_key_templates.h
     binary_keyset_reader.h
     binary_keyset_writer.h
-    catalogue.h
     cleartext_keyset_handle.h
-    config.h
     deterministic_aead.h
     deterministic_aead_config.h
     deterministic_aead_factory.h
@@ -92,6 +85,7 @@
     tink::core::input_stream
     tink::core::json_keyset_reader
     tink::core::json_keyset_writer
+    tink::core::key
     tink::core::key_manager
     tink::core::keyset_handle
     tink::core::keyset_manager
@@ -373,39 +367,6 @@
 )
 
 tink_cc_library(
-  NAME catalogue
-  SRCS
-    catalogue.h
-  DEPS
-    tink::core::key_manager
-    absl::core_headers
-    tink::util::statusor
-)
-
-tink_cc_library(
-  NAME config
-  SRCS
-    core/config.cc
-    config.h
-  DEPS
-    tink::core::catalogue
-    tink::core::key_manager
-    tink::core::registry
-    absl::status
-    absl::strings
-    tink::aead::aead_config
-    tink::daead::deterministic_aead_config
-    tink::hybrid::hybrid_config
-    tink::mac::mac_config
-    tink::signature::signature_config
-    tink::streamingaead::streaming_aead_config
-    tink::util::errors
-    tink::util::status
-    tink::util::statusor
-    tink::proto::config_cc_proto
-)
-
-tink_cc_library(
   NAME crypto_format
   SRCS
     core/crypto_format.cc
@@ -424,6 +385,7 @@
     primitive_set.h
   DEPS
     tink::core::crypto_format
+    absl::core_headers
     absl::flat_hash_map
     absl::memory
     absl::status
@@ -472,23 +434,55 @@
     keyset_handle.h
   DEPS
     tink::core::aead
+    tink::core::configuration
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::key_gen_configuration
     tink::core::key_manager
+    tink::core::key_status
     tink::core::keyset_reader
     tink::core::keyset_writer
     tink::core::primitive_set
     tink::core::registry
     absl::core_headers
     absl::flat_hash_map
+    absl::check
     absl::memory
     absl::status
     absl::strings
+    absl::optional
+    tink::internal::configuration_impl
+    tink::internal::key_gen_configuration_impl
     tink::internal::key_info
+    tink::internal::key_status_util
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::util
     tink::util::errors
     tink::util::keyset_util
     tink::proto::tink_cc_proto
 )
 
 tink_cc_library(
+  NAME keyset_handle_builder
+  SRCS
+    core/keyset_handle_builder.cc
+    keyset_handle_builder.h
+  DEPS
+    tink::core::key
+    tink::core::key_status
+    tink::core::keyset_handle
+    tink::core::parameters
+    absl::check
+    absl::status
+    absl::strings
+    absl::optional
+    tink::internal::keyset_handle_builder_entry
+    tink::subtle::random
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
   NAME cleartext_keyset_handle
   SRCS
     core/cleartext_keyset_handle.cc
@@ -525,13 +519,13 @@
     core/keyset_manager.cc
     keyset_manager.h
   DEPS
+    tink::core::key_gen_configuration
     tink::core::keyset_handle
-    tink::core::keyset_reader
-    tink::core::registry
     absl::core_headers
     absl::memory
     absl::status
     absl::synchronization
+    tink::internal::key_gen_configuration_impl
     tink::util::enums
     tink::util::errors
     tink::util::status
@@ -658,6 +652,26 @@
 )
 
 tink_cc_library(
+  NAME partial_key_access_token
+  SRCS
+    partial_key_access_token.h
+)
+
+tink_cc_library(
+  NAME partial_key_access
+  SRCS
+    partial_key_access.h
+  DEPS
+    tink::core::partial_key_access_token
+)
+
+tink_cc_library(
+  NAME secret_key_access_token
+  SRCS
+    secret_key_access_token.h
+)
+
+tink_cc_library(
   NAME insecure_secret_key_access
   SRCS
     insecure_secret_key_access.h
@@ -666,9 +680,22 @@
 )
 
 tink_cc_library(
-  NAME secret_key_access_token
+  NAME restricted_data
   SRCS
-    secret_key_access_token.h
+    core/restricted_data.cc
+    restricted_data.h
+  DEPS
+    tink::core::secret_key_access_token
+    absl::check
+    crypto
+    tink::subtle::random
+    tink::util::secret_data
+)
+
+tink_cc_library(
+  NAME key_status
+  SRCS
+    key_status.h
 )
 
 # tests
@@ -680,6 +707,7 @@
   DEPS
     tink::core::version
     gmock
+    tink::internal::util
 )
 
 tink_cc_test(
@@ -739,17 +767,6 @@
 )
 
 tink_cc_test(
-  NAME config_test
-  SRCS
-    core/config_test.cc
-  DEPS
-    tink::core::config
-    tink::core::mac
-    gmock
-    tink::proto::config_cc_proto
-)
-
-tink_cc_test(
   NAME crypto_format_test
   SRCS
     core/crypto_format_test.cc
@@ -770,6 +787,8 @@
     tink::core::key_manager_impl
     tink::core::json_keyset_reader
     tink::core::json_keyset_writer
+    tink::core::key_gen_configuration
+    tink::core::key_status
     tink::core::keyset_handle
     tink::core::primitive_set
     tink::core::primitive_wrapper
@@ -779,14 +798,47 @@
     tink::aead::aead_key_templates
     tink::aead::aead_wrapper
     tink::aead::aes_gcm_key_manager
+    tink::config::fips_140_2
+    tink::config::key_gen_fips_140_2
     tink::config::tink_config
+    tink::config::internal::global_registry
+    tink::internal::fips_utils
+    tink::internal::key_gen_configuration_impl
     tink::signature::ecdsa_sign_key_manager
     tink::signature::signature_key_templates
-    tink::util::protobuf_helper
     tink::util::status
     tink::util::test_keyset_handle
     tink::util::test_matchers
     tink::util::test_util
+    tink::proto::aes_gcm_siv_cc_proto
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME keyset_handle_builder_test
+  SRCS
+    core/keyset_handle_builder_test.cc
+  DEPS
+    tink::core::insecure_secret_key_access
+    tink::core::key_status
+    tink::core::keyset_handle_builder
+    tink::core::partial_key_access
+    gmock
+    absl::status
+    absl::strings
+    tink::config::tink_config
+    tink::internal::legacy_proto_key
+    tink::internal::legacy_proto_parameters
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::mac::aes_cmac_key
+    tink::mac::aes_cmac_parameters
+    tink::mac::mac_key_templates
+    tink::subtle::random
+    tink::util::status
+    tink::util::test_matchers
+    tink::util::test_util
+    tink::proto::aes_cmac_cc_proto
     tink::proto::tink_cc_proto
 )
 
@@ -808,7 +860,6 @@
   SRCS
     core/keyset_manager_test.cc
   DEPS
-    tink::core::config
     tink::core::keyset_handle
     tink::core::keyset_manager
     gmock
@@ -838,10 +889,12 @@
   SRCS
     core/primitive_set_test.cc
   DEPS
+    tink::core::cleartext_keyset_handle
     tink::core::crypto_format
     tink::core::mac
     tink::core::primitive_set
     gmock
+    tink::keyderivation::keyset_deriver
     tink::util::test_matchers
     tink::util::test_util
     tink::proto::tink_cc_proto
@@ -942,3 +995,78 @@
     tink::core::secret_key_access_testonly
     gmock
 )
+
+tink_cc_test(
+  NAME partial_key_access_token_test
+  SRCS
+    core/partial_key_access_token_test.cc
+  DEPS
+    tink::core::partial_key_access
+    tink::core::partial_key_access_token
+    gmock
+    absl::core_headers
+)
+
+tink_cc_test(
+  NAME restricted_data_test
+  SRCS
+    core/restricted_data_test.cc
+  DEPS
+    tink::core::insecure_secret_key_access
+    tink::core::restricted_data
+    gmock
+    tink::subtle::random
+    tink::util::secret_data
+)
+
+tink_cc_library(
+  NAME proto_keyset_format
+  SRCS
+    proto_keyset_format.cc
+    proto_keyset_format.h
+  DEPS
+    tink::core::binary_keyset_reader
+    tink::core::binary_keyset_writer
+    tink::core::cleartext_keyset_handle
+    tink::core::keyset_handle
+    tink::core::secret_key_access_token
+    absl::strings
+    tink::util::secret_data
+)
+
+tink_cc_test(
+  NAME proto_keyset_format_test
+  SRCS
+    proto_keyset_format_test.cc
+  DEPS
+    tink::core::insecure_secret_key_access
+    tink::core::keyset_handle_builder
+    tink::core::mac
+    tink::core::proto_keyset_format
+    gmock
+    absl::strings
+    tink::config::tink_config
+    tink::internal::legacy_proto_parameters
+    tink::internal::proto_parameters_serialization
+    tink::mac::mac_key_templates
+    tink::signature::signature_key_templates
+    tink::util::secret_data
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME configuration
+  SRCS
+    configuration.h
+  DEPS
+    tink::internal::key_type_info_store
+    tink::internal::keyset_wrapper_store
+)
+
+tink_cc_library(
+  NAME key_gen_configuration
+  SRCS
+    key_gen_configuration.h
+  DEPS
+    tink::internal::key_type_info_store
+)
diff --git a/cc/MODULE.bazel b/cc/MODULE.bazel
new file mode 100644
index 0000000..ab23a48
--- /dev/null
+++ b/cc/MODULE.bazel
@@ -0,0 +1,32 @@
+# Copyright 2023 Google LLC
+#
+# 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
+#
+#      http://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.
+
+"""Tink C++ Bazel Module definition."""
+module(
+    name = "tink_cc",
+    version = "2.0.0",
+)
+
+bazel_dep(name = "rules_cc", version = "0.0.5")
+bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
+bazel_dep(name = "platforms", version = "0.0.6")
+bazel_dep(name = "bazel_skylib", version = "1.3.0")
+bazel_dep(name = "googletest", version = "1.12.1", repo_name = "com_google_googletest")
+bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf")
+bazel_dep(name = "boringssl", version = "0.0.0-20230215-5c22014")
+bazel_dep(name = "rapidjson", version = "1.1.0")
+bazel_dep(name = "abseil-cpp", version = "20230125.1", repo_name="com_google_absl")
+
+wycheproof_extension = use_extension("//:extensions.bzl", "wycheproof_extension")
+use_repo(wycheproof_extension, "wycheproof")
diff --git a/cc/WORKSPACE b/cc/WORKSPACE
index ca79c62..af5976a 100644
--- a/cc/WORKSPACE
+++ b/cc/WORKSPACE
@@ -13,9 +13,3 @@
 load("@tink_cc//:tink_cc_deps_init.bzl", "tink_cc_deps_init")
 
 tink_cc_deps_init()
-
-load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig")
-
-# Creates a default toolchain config for RBE. Use this as is if you are
-# using the rbe_ubuntu16_04 container, otherwise refer to RBE docs.
-rbe_autoconfig(name = "rbe_default")
diff --git a/cc/WORKSPACE.bzlmod b/cc/WORKSPACE.bzlmod
new file mode 100644
index 0000000..057d6aa
--- /dev/null
+++ b/cc/WORKSPACE.bzlmod
@@ -0,0 +1 @@
+# This replaces the content of the WORKSPACE file when using --enable_bzlmod.
diff --git a/cc/aead.h b/cc/aead.h
index 803fc51..45017d6 100644
--- a/cc/aead.h
+++ b/cc/aead.h
@@ -51,7 +51,7 @@
       absl::string_view ciphertext,
       absl::string_view associated_data) const = 0;
 
-  virtual ~Aead() {}
+  virtual ~Aead() = default;
 };
 
 }  // namespace tink
diff --git a/cc/aead/BUILD.bazel b/cc/aead/BUILD.bazel
index 28b3e8b..10648c4 100644
--- a/cc/aead/BUILD.bazel
+++ b/cc/aead/BUILD.bazel
@@ -214,6 +214,7 @@
         "//subtle:random",
         "//util:constants",
         "//util:enums",
+        "//util:input_stream_util",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
@@ -281,6 +282,7 @@
     deps = [
         "//:aead",
         "//:registry",
+        "//aead/internal:aead_util",
         "//proto:tink_cc_proto",
         "//util:status",
         "//util:statusor",
@@ -302,6 +304,7 @@
         "//:core/template_util",
         "//:kms_client",
         "//:kms_clients",
+        "//aead/internal:aead_util",
         "//internal:fips_utils",
         "//proto:kms_envelope_cc_proto",
         "//proto:tink_cc_proto",
@@ -329,6 +332,95 @@
     ],
 )
 
+cc_library(
+    name = "failing_aead",
+    testonly = 1,
+    srcs = ["failing_aead.cc"],
+    hdrs = ["failing_aead.h"],
+    include_prefix = "tink/aead",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//:aead",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "aead_parameters",
+    hdrs = ["aead_parameters.h"],
+    include_prefix = "tink/aead",
+    deps = ["//:parameters"],
+)
+
+cc_library(
+    name = "aead_key",
+    hdrs = ["aead_key.h"],
+    include_prefix = "tink/aead",
+    deps = [
+        ":aead_parameters",
+        "//:key",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "aes_gcm_parameters",
+    srcs = ["aes_gcm_parameters.cc"],
+    hdrs = ["aes_gcm_parameters.h"],
+    include_prefix = "tink/aead",
+    deps = [
+        ":aead_parameters",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "aes_gcm_key",
+    srcs = ["aes_gcm_key.cc"],
+    hdrs = ["aes_gcm_key.h"],
+    include_prefix = "tink/aead",
+    deps = [
+        ":aead_key",
+        ":aes_gcm_parameters",
+        "//:partial_key_access_token",
+        "//:restricted_data",
+        "//subtle:subtle_util",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_library(
+    name = "aes_gcm_proto_serialization",
+    srcs = ["aes_gcm_proto_serialization.cc"],
+    hdrs = ["aes_gcm_proto_serialization.h"],
+    include_prefix = "tink/aead",
+    deps = [
+        ":aes_gcm_key",
+        ":aes_gcm_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//internal:key_parser",
+        "//internal:key_serializer",
+        "//internal:mutable_serialization_registry",
+        "//internal:parameters_parser",
+        "//internal:parameters_serializer",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:aes_gcm_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
 # tests
 
 cc_test(
@@ -373,11 +465,11 @@
         "//:primitive_set",
         "//:registry",
         "//config:tink_fips",
+        "//internal:fips_utils",
         "//proto:tink_cc_proto",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
-        "//util:test_util",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
         "@com_google_googletest//:gtest_main",
@@ -516,7 +608,6 @@
     deps = [
         ":aes_ctr_hmac_aead_key_manager",
         "//:aead",
-        "//:mac",
         "//proto:aes_ctr_cc_proto",
         "//proto:aes_ctr_hmac_aead_cc_proto",
         "//proto:common_cc_proto",
@@ -526,6 +617,7 @@
         "//subtle:aead_test_util",
         "//subtle:aes_ctr_boringssl",
         "//util:enums",
+        "//util:istream_input_stream",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
@@ -589,7 +681,9 @@
         ":aead_key_templates",
         ":kms_envelope_aead",
         "//:aead",
+        "//:keyset_handle",
         "//:registry",
+        "//internal:ssl_util",
         "//mac:mac_key_templates",
         "//proto:aes_gcm_cc_proto",
         "//util:status",
@@ -599,6 +693,7 @@
         "@com_google_absl//absl/base:endian",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -608,6 +703,7 @@
     size = "small",
     srcs = ["kms_envelope_aead_key_manager_test.cc"],
     deps = [
+        ":aead_config",
         ":aead_key_templates",
         ":aes_eax_key_manager",
         ":kms_envelope_aead",
@@ -616,9 +712,11 @@
         "//:kms_client",
         "//:kms_clients",
         "//:registry",
+        "//mac:mac_key_templates",
         "//proto:kms_envelope_cc_proto",
         "//proto:tink_cc_proto",
         "//subtle:aead_test_util",
+        "//util:fake_kms_client",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -650,19 +748,6 @@
     ],
 )
 
-cc_library(
-    name = "failing_aead",
-    testonly = 1,
-    srcs = ["failing_aead.cc"],
-    hdrs = ["failing_aead.h"],
-    include_prefix = "tink/aead",
-    visibility = ["//visibility:public"],
-    deps = [
-        "//:aead",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
 cc_test(
     name = "failing_aead_test",
     srcs = ["failing_aead_test.cc"],
@@ -673,3 +758,51 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "aes_gcm_parameters_test",
+    srcs = ["aes_gcm_parameters_test.cc"],
+    deps = [
+        ":aes_gcm_parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "aes_gcm_key_test",
+    srcs = ["aes_gcm_key_test.cc"],
+    deps = [
+        ":aes_gcm_key",
+        ":aes_gcm_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "aes_gcm_proto_serialization_test",
+    size = "small",
+    srcs = ["aes_gcm_proto_serialization_test.cc"],
+    deps = [
+        ":aes_gcm_key",
+        ":aes_gcm_parameters",
+        ":aes_gcm_proto_serialization",
+        "//:insecure_secret_key_access",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//internal:mutable_serialization_registry",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:aes_gcm_cc_proto",
+        "//proto:tink_cc_proto",
+        "//subtle:random",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/aead/BUILD.gn b/cc/aead/BUILD.gn
index f4dc781..6eae32e 100644
--- a/cc/aead/BUILD.gn
+++ b/cc/aead/BUILD.gn
@@ -204,6 +204,7 @@
     "//third_party/tink/cc/subtle:random",
     "//third_party/tink/cc/util:constants",
     "//third_party/tink/cc/util:enums",
+    "//third_party/tink/cc/util:input_stream_util",
     "//third_party/tink/cc/util:secret_data",
     "//third_party/tink/cc/util:status",
     "//third_party/tink/cc/util:statusor",
@@ -277,6 +278,7 @@
     "//third_party/abseil-cpp/absl/strings:strings",
     "//third_party/tink/cc:aead",
     "//third_party/tink/cc:registry",
+    "//third_party/tink/cc/aead/internal:aead_util",
     "//third_party/tink/cc/proto:tink_proto",
     "//third_party/tink/cc/util:status",
     "//third_party/tink/cc/util:statusor",
@@ -302,6 +304,7 @@
     "//third_party/tink/cc:core/template_util",
     "//third_party/tink/cc:kms_client",
     "//third_party/tink/cc:kms_clients",
+    "//third_party/tink/cc/aead/internal:aead_util",
     "//third_party/tink/cc/internal:fips_utils",
     "//third_party/tink/cc/proto:kms_envelope_proto",
     "//third_party/tink/cc/proto:tink_proto",
diff --git a/cc/aead/CMakeLists.txt b/cc/aead/CMakeLists.txt
index c4adc4e..41264b9 100644
--- a/cc/aead/CMakeLists.txt
+++ b/cc/aead/CMakeLists.txt
@@ -199,6 +199,7 @@
     tink::subtle::random
     tink::util::constants
     tink::util::enums
+    tink::util::input_stream_util
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
@@ -266,6 +267,7 @@
     absl::strings
     tink::core::aead
     tink::core::registry
+    tink::aead::internal::aead_util
     tink::util::status
     tink::util::statusor
     tink::proto::tink_cc_proto
@@ -286,6 +288,7 @@
     tink::core::template_util
     tink::core::kms_client
     tink::core::kms_clients
+    tink::aead::internal::aead_util
     tink::internal::fips_utils
     tink::util::constants
     tink::util::status
@@ -304,6 +307,91 @@
     absl::strings
     tink::core::aead
     tink::util::statusor
+  TESTONLY
+)
+
+tink_cc_library(
+  NAME failing_aead
+  SRCS
+    failing_aead.cc
+    failing_aead.h
+  DEPS
+    absl::strings
+    tink::core::aead
+  TESTONLY
+)
+
+tink_cc_library(
+  NAME aead_parameters
+  SRCS
+    aead_parameters.h
+  DEPS
+    tink::core::parameters
+)
+
+tink_cc_library(
+  NAME aead_key
+  SRCS
+    aead_key.h
+  DEPS
+    tink::aead::aead_parameters
+    absl::strings
+    tink::core::key
+)
+
+tink_cc_library(
+  NAME aes_gcm_parameters
+  SRCS
+    aes_gcm_parameters.cc
+    aes_gcm_parameters.h
+  DEPS
+    tink::aead::aead_parameters
+    absl::strings
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_library(
+  NAME aes_gcm_key
+  SRCS
+    aes_gcm_key.cc
+    aes_gcm_key.h
+  DEPS
+    tink::aead::aead_key
+    tink::aead::aes_gcm_parameters
+    absl::strings
+    absl::optional
+    tink::core::partial_key_access_token
+    tink::core::restricted_data
+    tink::subtle::subtle_util
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_library(
+  NAME aes_gcm_proto_serialization
+  SRCS
+    aes_gcm_proto_serialization.cc
+    aes_gcm_proto_serialization.h
+  DEPS
+    tink::aead::aes_gcm_key
+    tink::aead::aes_gcm_parameters
+    absl::status
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::mutable_serialization_registry
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::util::status
+    tink::util::statusor
+    tink::proto::aes_gcm_cc_proto
+    tink::proto::tink_cc_proto
 )
 
 # tests
@@ -351,10 +439,10 @@
     tink::core::primitive_set
     tink::core::registry
     tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
-    tink::util::test_util
     tink::proto::tink_cc_proto
 )
 
@@ -487,11 +575,11 @@
     gmock
     absl::status
     tink::core::aead
-    tink::core::mac
     tink::subtle::subtle
     tink::subtle::aead_test_util
     tink::subtle::aes_ctr_boringssl
     tink::util::enums
+    tink::util::istream_input_stream
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
@@ -558,8 +646,11 @@
     absl::endian
     absl::memory
     absl::status
+    absl::strings
     tink::core::aead
+    tink::core::keyset_handle
     tink::core::registry
+    tink::internal::ssl_util
     tink::mac::mac_key_templates
     tink::util::status
     tink::util::statusor
@@ -573,6 +664,7 @@
   SRCS
     kms_envelope_aead_key_manager_test.cc
   DEPS
+    tink::aead::aead_config
     tink::aead::aead_key_templates
     tink::aead::aes_eax_key_manager
     tink::aead::kms_envelope_aead
@@ -584,7 +676,9 @@
     tink::core::kms_client
     tink::core::kms_clients
     tink::core::registry
+    tink::mac::mac_key_templates
     tink::subtle::aead_test_util
+    tink::util::fake_kms_client
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -612,16 +706,6 @@
     tink::proto::tink_cc_proto
 )
 
-tink_cc_library(
-  NAME failing_aead
-  SRCS
-    failing_aead.cc
-    failing_aead.h
-  DEPS
-    absl::strings
-    tink::core::aead
-)
-
 tink_cc_test(
   NAME failing_aead_test
   SRCS
@@ -632,3 +716,50 @@
     absl::status
     tink::util::test_matchers
 )
+
+tink_cc_test(
+  NAME aes_gcm_parameters_test
+  SRCS
+    aes_gcm_parameters_test.cc
+  DEPS
+    tink::aead::aes_gcm_parameters
+    gmock
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_test(
+  NAME aes_gcm_key_test
+  SRCS
+    aes_gcm_key_test.cc
+  DEPS
+    tink::aead::aes_gcm_key
+    tink::aead::aes_gcm_parameters
+    gmock
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_test(
+  NAME aes_gcm_proto_serialization_test
+  SRCS
+    aes_gcm_proto_serialization_test.cc
+  DEPS
+    tink::aead::aes_gcm_key
+    tink::aead::aes_gcm_parameters
+    tink::aead::aes_gcm_proto_serialization
+    gmock
+    tink::core::insecure_secret_key_access
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::subtle::random
+    tink::util::test_matchers
+    tink::proto::aes_gcm_cc_proto
+    tink::proto::tink_cc_proto
+)
diff --git a/cc/aead/aead_config.cc b/cc/aead/aead_config.cc
index d627064..b5a84ca 100644
--- a/cc/aead/aead_config.cc
+++ b/cc/aead/aead_config.cc
@@ -34,15 +34,6 @@
 
 namespace crypto {
 namespace tink {
-
-using ::google::crypto::tink::RegistryConfig;
-
-// static
-const RegistryConfig& AeadConfig::Latest() {
-  static const RegistryConfig* config = new RegistryConfig();
-  return *config;
-}
-
 // static
 util::Status AeadConfig::Register() {
   auto status = MacConfig::Register();
diff --git a/cc/aead/aead_config.h b/cc/aead/aead_config.h
index 6431d64..08cb196 100644
--- a/cc/aead/aead_config.h
+++ b/cc/aead/aead_config.h
@@ -34,14 +34,6 @@
 //
 class AeadConfig {
  public:
-  static constexpr char kCatalogueName[] = "TinkAead";
-  static constexpr char kPrimitiveName[] = "Aead";
-
-  // Returns config of Aead implementations supported
-  // in the current Tink release.
-  ABSL_DEPRECATED("This is not supported anymore.")
-  static const google::crypto::tink::RegistryConfig& Latest();
-
   // Registers Aead primitive wrapper and key managers for all Aead key types
   // from the current Tink release.
   static crypto::tink::util::Status Register();
diff --git a/cc/aead/aead_config_test.cc b/cc/aead/aead_config_test.cc
index 726e3d1..4a7945d 100644
--- a/cc/aead/aead_config_test.cc
+++ b/cc/aead/aead_config_test.cc
@@ -29,27 +29,24 @@
 #include "tink/aead/aead_key_templates.h"
 #include "tink/aead/aes_gcm_key_manager.h"
 #include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/keyset_handle.h"
 #include "tink/primitive_set.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
-#include "tink/util/test_util.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
-using ::crypto::tink::test::DummyAead;
 using ::crypto::tink::test::IsOk;
 using ::crypto::tink::test::StatusIs;
-using ::google::crypto::tink::KeysetInfo;
-using ::google::crypto::tink::KeyStatusType;
+using ::crypto::tink::util::StatusOr;
 using ::google::crypto::tink::KeyTemplate;
-using ::google::crypto::tink::OutputPrefixType;
-using ::testing::Eq;
+using ::testing::IsNull;
 using ::testing::Not;
 using ::testing::Test;
 
@@ -80,35 +77,17 @@
 
   ASSERT_THAT(AeadConfig::Register(), IsOk());
 
-  KeysetInfo::KeyInfo key_info;
-  key_info.set_status(KeyStatusType::ENABLED);
-  key_info.set_key_id(1234);
-  key_info.set_output_prefix_type(OutputPrefixType::RAW);
-  auto primitive_set = absl::make_unique<PrimitiveSet<Aead>>();
-  ASSERT_THAT(primitive_set->set_primary(*primitive_set->AddPrimitive(
-                  absl::make_unique<DummyAead>("dummy"), key_info)),
-              IsOk());
-
-  util::StatusOr<std::unique_ptr<Aead>> primitive_result =
-      Registry::Wrap(std::move(primitive_set));
-
-  ASSERT_THAT(primitive_result, IsOk());
-  util::StatusOr<std::string> encryption_result =
-      (*primitive_result)->Encrypt("secret", "");
-  ASSERT_THAT(encryption_result, IsOk());
-
-  util::StatusOr<std::string> decryption_result =
-      DummyAead("dummy").Decrypt(*encryption_result, "");
-  ASSERT_THAT(decryption_result, IsOk());
-  EXPECT_THAT(*decryption_result, Eq("secret"));
-
-  decryption_result = DummyAead("dummy").Decrypt(*encryption_result, "wrong");
-  EXPECT_THAT(decryption_result, Not(IsOk()));
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm());
+  ASSERT_THAT(keyset_handle.status(), IsOk());
+  StatusOr<std::unique_ptr<Aead>> aead = (*keyset_handle)->GetPrimitive<Aead>();
+  ASSERT_THAT(aead.status(), IsOk());
+  ASSERT_THAT(*aead, Not(IsNull()));
 }
 
 // FIPS-only mode tests
 TEST_F(AeadConfigTest, RegisterNonFipsTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode with BoringCrypto.";
   }
 
@@ -128,7 +107,7 @@
 }
 
 TEST_F(AeadConfigTest, RegisterFipsValidTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode with BoringCrypto.";
   }
 
@@ -148,7 +127,7 @@
 }
 
 TEST_F(AeadConfigTest, RegisterFailsIfBoringCryptoNotAvailable) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Only supported in FIPS-only mode with BoringCrypto not available.";
   }
diff --git a/cc/aead/aead_key.h b/cc/aead/aead_key.h
new file mode 100644
index 0000000..869ddc8
--- /dev/null
+++ b/cc/aead/aead_key.h
@@ -0,0 +1,52 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AEAD_KEY_H_
+#define TINK_AEAD_AEAD_KEY_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/aead/aead_parameters.h"
+#include "tink/key.h"
+
+namespace crypto {
+namespace tink {
+
+// Represents a function to encrypt and decrypt data using authenticated
+// encryption with associated data (AEAD).
+class AeadKey : public Key {
+ public:
+  // Returns the bytes prefixed to every ciphertext generated by this key.
+  //
+  // In order to make key rotation more efficient, Tink allows every AEAD key to
+  // have an associated ciphertext output prefix. When decrypting a ciphertext,
+  // only keys with a matching prefix have to be tried.
+  //
+  // Note that a priori, the output prefix may not be unique in a keyset
+  // (i.e., different keys in a keyset may have the same prefix or one prefix
+  // may be a prefix of another). To avoid this, built-in Tink keys use the
+  // convention that the prefix is either '0x00<big endian key id>' or
+  // '0x01<big endian key id>'.
+  virtual absl::string_view GetOutputPrefix() const = 0;
+
+  const AeadParameters& GetParameters() const override = 0;
+
+  bool operator==(const Key& other) const override = 0;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_AEAD_AEAD_KEY_H_
diff --git a/cc/aead/aead_parameters.h b/cc/aead/aead_parameters.h
new file mode 100644
index 0000000..dc5caad
--- /dev/null
+++ b/cc/aead/aead_parameters.h
@@ -0,0 +1,32 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AEAD_PARAMETERS_H_
+#define TINK_AEAD_AEAD_PARAMETERS_H_
+
+#include "tink/parameters.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes an `AeadKey` (e.g., key attributes), excluding the randomly chosen
+// key material.
+class AeadParameters : public Parameters {};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_AEAD_AEAD_PARAMETERS_H_
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager.cc b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
index ffe7867..10a0634 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
@@ -34,6 +34,7 @@
 #include "tink/subtle/ind_cpa_cipher.h"
 #include "tink/subtle/random.h"
 #include "tink/util/enums.h"
+#include "tink/util/input_stream_util.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -52,12 +53,14 @@
 constexpr int kMinTagSizeInBytes = 10;
 }
 
-using crypto::tink::util::Enums;
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
-using google::crypto::tink::AesCtrHmacAeadKey;
-using google::crypto::tink::AesCtrHmacAeadKeyFormat;
-using google::crypto::tink::HashType;
+using ::crypto::tink::util::Enums;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+using ::google::crypto::tink::AesCtrHmacAeadKey;
+using ::google::crypto::tink::AesCtrHmacAeadKeyFormat;
+using ::google::crypto::tink::AesCtrKey;
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::HmacKey;
 
 StatusOr<AesCtrHmacAeadKey> AesCtrHmacAeadKeyManager::CreateKey(
     const AesCtrHmacAeadKeyFormat& aes_ctr_hmac_aead_key_format) const {
@@ -176,5 +179,53 @@
   return HmacKeyManager().ValidateKeyFormat(key_format.hmac_key_format());
 }
 
+// To ensure the resulting key can provide key commitment, the AES-CTR key must
+// be derived first, then the HMAC key. This avoids situation where it's
+// possible to brute force raw key material so that the 32th byte of the
+// keystream is a 0 Give party A a key with this raw key material, saying that
+// the size of the HMAC key is 32 bytes and the size of the AES key is 16 bytes.
+// Give party B a key with this raw key material, saying that the size of the
+// HMAC key is 31 bytes and the size of the AES key is 16 bytes. Since HMAC will
+// pad the key with zeroes, this leads to both parties using the same HMAC key,
+// but a different AES key (offset by 1 byte)
+StatusOr<AesCtrHmacAeadKey> AesCtrHmacAeadKeyManager::DeriveKey(
+    const AesCtrHmacAeadKeyFormat& key_format,
+    InputStream* input_stream) const {
+  Status status = ValidateKeyFormat(key_format);
+  if (!status.ok()) {
+    return status;
+  }
+  StatusOr<std::string> aes_ctr_randomness = ReadBytesFromStream(
+      key_format.aes_ctr_key_format().key_size(), input_stream);
+  if (!aes_ctr_randomness.ok()) {
+    if (absl::IsOutOfRange(aes_ctr_randomness.status())) {
+      return crypto::tink::util::Status(
+          absl::StatusCode::kInvalidArgument,
+          "Could not get enough pseudorandomness from input stream");
+    }
+    return aes_ctr_randomness.status();
+  }
+  StatusOr<HmacKey> hmac_key =
+      HmacKeyManager().DeriveKey(key_format.hmac_key_format(), input_stream);
+  if (!hmac_key.ok()) {
+    return hmac_key.status();
+  }
+
+  google::crypto::tink::AesCtrHmacAeadKey key;
+  key.set_version(get_version());
+  *key.mutable_hmac_key() = hmac_key.value();
+
+  AesCtrKey* aes_ctr_key = key.mutable_aes_ctr_key();
+  aes_ctr_key->set_version(get_version());
+  aes_ctr_key->set_key_value(aes_ctr_randomness.value());
+  *aes_ctr_key->mutable_params() = key_format.aes_ctr_key_format().params();
+
+  status = ValidateKey(key);
+  if (!status.ok()) {
+    return status;
+  }
+  return key;
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager.h b/cc/aead/aes_ctr_hmac_aead_key_manager.h
index a241a9c..e3b2f42 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager.h
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager.h
@@ -67,6 +67,10 @@
   CreateKey(const google::crypto::tink::AesCtrHmacAeadKeyFormat& key_format)
       const override;
 
+  crypto::tink::util::StatusOr<google::crypto::tink::AesCtrHmacAeadKey>
+  DeriveKey(const google::crypto::tink::AesCtrHmacAeadKeyFormat& key_format,
+            InputStream* input_stream) const override;
+
   internal::FipsCompatibility FipsStatus() const override {
     return internal::FipsCompatibility::kRequiresBoringCrypto;
   }
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc b/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
index 6815ec3..b84711a 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
@@ -19,6 +19,7 @@
 #include <stdint.h>
 
 #include <memory>
+#include <sstream>
 #include <string>
 #include <utility>
 
@@ -26,13 +27,13 @@
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "tink/aead.h"
-#include "tink/mac.h"
 #include "tink/subtle/aead_test_util.h"
 #include "tink/subtle/aes_ctr_boringssl.h"
 #include "tink/subtle/encrypt_then_authenticate.h"
 #include "tink/subtle/hmac_boringssl.h"
 #include "tink/subtle/ind_cpa_cipher.h"
 #include "tink/util/enums.h"
+#include "tink/util/istream_input_stream.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -48,11 +49,11 @@
 
 using ::crypto::tink::test::IsOk;
 using ::crypto::tink::test::StatusIs;
+using ::crypto::tink::util::IstreamInputStream;
 using ::crypto::tink::util::StatusOr;
 using ::google::crypto::tink::AesCtrHmacAeadKey;
 using ::google::crypto::tink::AesCtrHmacAeadKeyFormat;
 using ::google::crypto::tink::HashType;
-using ::google::crypto::tink::KeyData;
 using ::testing::Eq;
 using ::testing::Not;
 using ::testing::SizeIs;
@@ -146,14 +147,24 @@
   AesCtrHmacAeadKeyFormat key_format = CreateValidKeyFormat();
   for (int len = 0; len < 42; ++len) {
     key_format.mutable_aes_ctr_key_format()->set_key_size(len);
+    IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+      "0123456789abcde0123456789abcdefghijklmnopqrztuvwxyz0123456789abcde01"
+      "23456789abcdefghijklmnopqrztuvwxyz0123456789abcde0123456789abcdefghi"
+      "jklmnopqrztuvwxyz")};
     if (len == 16 || len == 32) {
       EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
                   IsOk())
           << "for length " << len;
+      EXPECT_THAT(
+          AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+          IsOk());
     } else {
       EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
                   Not(IsOk()))
           << "for length " << len;
+      EXPECT_THAT(
+          AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+          Not(IsOk()));
     }
   }
 }
@@ -162,14 +173,24 @@
   AesCtrHmacAeadKeyFormat key_format = CreateValidKeyFormat();
   for (int len = 0; len < 42; ++len) {
     key_format.mutable_hmac_key_format()->set_key_size(len);
+    IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+      "0123456789abcde0123456789abcdefghijklmnopqrztuvwxyz0123456789abcde01"
+      "23456789abcdefghijklmnopqrztuvwxyz0123456789abcde0123456789abcdefghi"
+      "jklmnopqrztuvwxyz")};
     if (len >= 16) {
       EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
                   IsOk())
           << "for length " << len;
+      EXPECT_THAT(
+          AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+          IsOk());
     } else {
       EXPECT_THAT(AesCtrHmacAeadKeyManager().ValidateKeyFormat(key_format),
                   Not(IsOk()))
           << "for length " << len;
+      EXPECT_THAT(
+          AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream),
+          Not(IsOk()));
     }
   }
 }
@@ -222,6 +243,92 @@
               IsOk());
 }
 
+TEST(AesCtrHmacAeadKeyManagerTest, Derive16ByteKey) {
+  AesCtrHmacAeadKeyFormat key_format;
+  key_format.mutable_aes_ctr_key_format()->set_key_size(16);
+  key_format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+  key_format.mutable_hmac_key_format()->set_key_size(16);
+  key_format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+  key_format.mutable_hmac_key_format()->mutable_params()->set_hash(
+      google::crypto::tink::SHA256);
+  key_format.mutable_hmac_key_format()->set_version(0);
+
+  IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+      "0123456789abcde_YELLOW_SUBMARINE_EXTRA")};
+
+  StatusOr<AesCtrHmacAeadKey> derived_key =
+      AesCtrHmacAeadKeyManager().DeriveKey(key_format, &input_stream);
+  ASSERT_THAT(derived_key, IsOk());
+  EXPECT_THAT(derived_key.value().aes_ctr_key().key_value(),
+              Eq("0123456789abcde_"));
+  EXPECT_THAT(derived_key.value().hmac_key().key_value(),
+              Eq("YELLOW_SUBMARINE"));
+  EXPECT_THAT(derived_key.value().hmac_key().params().hash(),
+              key_format.hmac_key_format().params().hash());
+  EXPECT_THAT(derived_key.value().hmac_key().params().tag_size(),
+              key_format.hmac_key_format().params().tag_size());
+  EXPECT_THAT(derived_key.value().aes_ctr_key().params().iv_size(),
+              Eq(key_format.aes_ctr_key_format().params().iv_size()));
+}
+
+TEST(AesCtrHmacAeadKeyManagerTest, Derive32ByteKey) {
+  AesCtrHmacAeadKeyFormat format;
+  format.mutable_aes_ctr_key_format()->set_key_size(32);
+  format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+  format.mutable_hmac_key_format()->set_key_size(32);
+  format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+  format.mutable_hmac_key_format()->mutable_params()->set_hash(
+      google::crypto::tink::SHA256);
+  format.mutable_hmac_key_format()->set_version(0);
+
+  IstreamInputStream input_stream{absl::make_unique<std::stringstream>(
+      "0123456789abcde0123456789abcdef_YELLOW_SUBMARINE_YELLOW_SUBMARIN")};
+
+  StatusOr<AesCtrHmacAeadKey> derived_key =
+      AesCtrHmacAeadKeyManager().DeriveKey(format, &input_stream);
+  ASSERT_THAT(derived_key, IsOk());
+  EXPECT_THAT(derived_key.value().aes_ctr_key().key_value(),
+              Eq("0123456789abcde0123456789abcdef_"));
+  EXPECT_THAT(derived_key.value().hmac_key().key_value(),
+              Eq("YELLOW_SUBMARINE_YELLOW_SUBMARIN"));
+}
+
+TEST(AesCtrHmacAeadKeyManagerTest, DeriveKeyNotEnoughRandomnessForAesCtrKey) {
+  AesCtrHmacAeadKeyFormat format;
+  format.mutable_aes_ctr_key_format()->set_key_size(32);
+  format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+  format.mutable_hmac_key_format()->set_key_size(32);
+  format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+  format.mutable_hmac_key_format()->mutable_params()->set_hash(
+      google::crypto::tink::SHA256);
+  format.mutable_hmac_key_format()->set_version(0);
+
+  IstreamInputStream input_stream{
+      absl::make_unique<std::stringstream>("0123456789")};
+
+  ASSERT_THAT(
+      AesCtrHmacAeadKeyManager().DeriveKey(format, &input_stream).status(),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesCtrHmacAeadKeyManagerTest, DeriveKeyNotEnoughRandomnessForHmacKey) {
+  AesCtrHmacAeadKeyFormat format;
+  format.mutable_aes_ctr_key_format()->set_key_size(16);
+  format.mutable_aes_ctr_key_format()->mutable_params()->set_iv_size(16);
+  format.mutable_hmac_key_format()->set_key_size(32);
+  format.mutable_hmac_key_format()->mutable_params()->set_tag_size(16);
+  format.mutable_hmac_key_format()->mutable_params()->set_hash(
+      google::crypto::tink::SHA256);
+  format.mutable_hmac_key_format()->set_version(0);
+
+  IstreamInputStream input_stream{
+      absl::make_unique<std::stringstream>("YELLOW_SUBMARINE")};
+
+  ASSERT_THAT(
+      AesCtrHmacAeadKeyManager().DeriveKey(format, &input_stream).status(),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/aes_gcm_key.cc b/cc/aead/aes_gcm_key.cc
new file mode 100644
index 0000000..4e93443
--- /dev/null
+++ b/cc/aead/aes_gcm_key.cc
@@ -0,0 +1,107 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/aead/aes_gcm_key.h"
+
+#include <string>
+
+#include "absl/strings/escaping.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/subtle_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+util::StatusOr<std::string> ComputeOutputPrefix(
+    const AesGcmParameters& parameters, absl::optional<int> id_requirement) {
+  switch (parameters.GetVariant()) {
+    case AesGcmParameters::Variant::kNoPrefix:
+      return std::string("");  // Empty prefix.
+    case AesGcmParameters::Variant::kCrunchy:
+      if (!id_requirement.has_value()) {
+        return util::Status(
+            absl::StatusCode::kInvalidArgument,
+            "id requirement must have value with kCrunchy or kLegacy");
+      }
+      return absl::StrCat(absl::HexStringToBytes("00"),
+                          subtle::BigEndian32(*id_requirement));
+    case AesGcmParameters::Variant::kTink:
+      if (!id_requirement.has_value()) {
+        return util::Status(absl::StatusCode::kInvalidArgument,
+                            "id requirement must have value with kTink");
+      }
+      return absl::StrCat(absl::HexStringToBytes("01"),
+                          subtle::BigEndian32(*id_requirement));
+    default:
+      return util::Status(
+          absl::StatusCode::kInvalidArgument,
+          absl::StrCat("Invalid variant: ", parameters.GetVariant()));
+  }
+}
+
+}  // namespace
+
+util::StatusOr<AesGcmKey> AesGcmKey::Create(const AesGcmParameters& parameters,
+                                            const RestrictedData& key_bytes,
+                                            absl::optional<int> id_requirement,
+                                            PartialKeyAccessToken token) {
+  if (parameters.KeySizeInBytes() != key_bytes.size()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Key size does not match AES-GCM parameters");
+  }
+  if (parameters.HasIdRequirement() && !id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key without ID requirement with parameters with ID "
+        "requirement");
+  }
+  if (!parameters.HasIdRequirement() && id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key with ID requirement with parameters without ID "
+        "requirement");
+  }
+  util::StatusOr<std::string> output_prefix =
+      ComputeOutputPrefix(parameters, id_requirement);
+  if (!output_prefix.ok()) {
+    return output_prefix.status();
+  }
+  return AesGcmKey(parameters, key_bytes, id_requirement,
+                   *std::move(output_prefix));
+}
+
+bool AesGcmKey::operator==(const Key& other) const {
+  const AesGcmKey* that = dynamic_cast<const AesGcmKey*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (GetParameters() != that->GetParameters()) {
+    return false;
+  }
+  if (id_requirement_ != that->id_requirement_) {
+    return false;
+  }
+  return key_bytes_ == that->key_bytes_;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/aes_gcm_key.h b/cc/aead/aes_gcm_key.h
new file mode 100644
index 0000000..8159ea7
--- /dev/null
+++ b/cc/aead/aes_gcm_key.h
@@ -0,0 +1,84 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AES_GCM_KEY_H_
+#define TINK_AEAD_AES_GCM_KEY_H_
+
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aead_key.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Represents an AEAD that uses AES-GCM.
+class AesGcmKey : public AeadKey {
+ public:
+  // Copyable and movable.
+  AesGcmKey(const AesGcmKey& other) = default;
+  AesGcmKey& operator=(const AesGcmKey& other) = default;
+  AesGcmKey(AesGcmKey&& other) = default;
+  AesGcmKey& operator=(AesGcmKey&& other) = default;
+
+  // Creates a new AES-GCM key.  If the parameters specify a variant that uses
+  // a prefix, then the id is used to compute this prefix.
+  static util::StatusOr<AesGcmKey> Create(const AesGcmParameters& parameters,
+                                          const RestrictedData& key_bytes,
+                                          absl::optional<int> id_requirement,
+                                          PartialKeyAccessToken token);
+
+  // Returns the underlying AES key.
+  util::StatusOr<RestrictedData> GetKeyBytes(
+      PartialKeyAccessToken token) const {
+    return key_bytes_;
+  }
+
+  absl::string_view GetOutputPrefix() const override { return output_prefix_; }
+
+  const AesGcmParameters& GetParameters() const override { return parameters_; }
+
+  absl::optional<int> GetIdRequirement() const override {
+    return id_requirement_;
+  }
+
+  bool operator==(const Key& other) const override;
+
+ private:
+  AesGcmKey(const AesGcmParameters& parameters, const RestrictedData& key_bytes,
+            absl::optional<int> id_requirement,
+            std::string output_prefix)
+      : parameters_(parameters),
+        key_bytes_(key_bytes),
+        id_requirement_(id_requirement),
+        output_prefix_(std::move(output_prefix)) {}
+
+  AesGcmParameters parameters_;
+  RestrictedData key_bytes_;
+  absl::optional<int> id_requirement_;
+  std::string output_prefix_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_AEAD_AES_GCM_KEY_H_
diff --git a/cc/aead/aes_gcm_key_test.cc b/cc/aead/aes_gcm_key_test.cc
new file mode 100644
index 0000000..06a0a35
--- /dev/null
+++ b/cc/aead/aes_gcm_key_test.cc
@@ -0,0 +1,285 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/aead/aes_gcm_key.h"
+
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  AesGcmParameters::Variant variant;
+  absl::optional<int> id_requirement;
+  std::string output_prefix;
+};
+
+using AesGcmKeyTest = TestWithParam<std::tuple<int, int, TestCase>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    AesGcmKeyTestSuite, AesGcmKeyTest,
+    Combine(Values(16, 24, 32), Range(12, 16),
+            Values(TestCase{AesGcmParameters::Variant::kTink, 0x02030400,
+                            std::string("\x01\x02\x03\x04\x00", 5)},
+                   TestCase{AesGcmParameters::Variant::kCrunchy, 0x01030005,
+                            std::string("\x00\x01\x03\x00\x05", 5)},
+                   TestCase{AesGcmParameters::Variant::kNoPrefix, absl::nullopt,
+                            ""})));
+
+TEST_P(AesGcmKeyTest, CreateSucceeds) {
+  int key_size;
+  int iv_and_tag_size;  // NOTE: There's no requirement for IV size == tag size.
+  TestCase test_case;
+  std::tie(key_size, iv_and_tag_size, test_case) = GetParam();
+
+  util::StatusOr<AesGcmParameters> params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(key_size)
+          .SetIvSizeInBytes(iv_and_tag_size)
+          .SetTagSizeInBytes(iv_and_tag_size)
+          .SetVariant(test_case.variant)
+          .Build();
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  EXPECT_THAT(key->GetParameters(), Eq(*params));
+  EXPECT_THAT(key->GetIdRequirement(), Eq(test_case.id_requirement));
+  EXPECT_THAT(key->GetOutputPrefix(), Eq(test_case.output_prefix));
+}
+
+TEST(AesGcmKeyTest, CreateKeyWithMismatchedKeySizeFails) {
+  // Key size parameter is 32 bytes.
+  util::StatusOr<AesGcmParameters> params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(params, IsOk());
+
+  // Key material is 16 bytes (another valid key length).
+  RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/16);
+
+  EXPECT_THAT(AesGcmKey::Create(*params, mismatched_secret,
+                                /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmKeyTest, CreateKeyWithInvalidIdRequirementFails) {
+  util::StatusOr<AesGcmParameters> no_prefix_params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+          .Build();
+  ASSERT_THAT(no_prefix_params, IsOk());
+
+  util::StatusOr<AesGcmParameters> tink_params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  EXPECT_THAT(AesGcmKey::Create(*no_prefix_params, secret,
+                                /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      AesGcmKey::Create(*tink_params, secret,
+                        /*id_requirement=*/absl::nullopt, GetPartialKeyAccess())
+          .status(),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmKeyTest, GetKeyBytes) {
+  int key_size;
+  int iv_and_tag_size;  // NOTE: There's no requirement for IV size == tag size.
+  TestCase test_case;
+  std::tie(key_size, iv_and_tag_size, test_case) = GetParam();
+
+  util::StatusOr<AesGcmParameters> params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(key_size)
+          .SetIvSizeInBytes(iv_and_tag_size)
+          .SetTagSizeInBytes(iv_and_tag_size)
+          .SetVariant(test_case.variant)
+          .Build();
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), IsOkAndHolds(secret));
+}
+
+TEST_P(AesGcmKeyTest, KeyEquals) {
+  int key_size;
+  int iv_and_tag_size;  // NOTE: There's no requirement for IV size == tag size.
+  TestCase test_case;
+  std::tie(key_size, iv_and_tag_size, test_case) = GetParam();
+
+  util::StatusOr<AesGcmParameters> params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(key_size)
+          .SetIvSizeInBytes(iv_and_tag_size)
+          .SetTagSizeInBytes(iv_and_tag_size)
+          .SetVariant(test_case.variant)
+          .Build();
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key == *other_key);
+  EXPECT_TRUE(*other_key == *key);
+  EXPECT_FALSE(*key != *other_key);
+  EXPECT_FALSE(*other_key != *key);
+}
+
+TEST(AesGcmKeyTest, DifferentVariantNotEqual) {
+  util::StatusOr<AesGcmParameters> crunchy_params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kCrunchy)
+          .Build();
+  ASSERT_THAT(crunchy_params, IsOk());
+
+  util::StatusOr<AesGcmParameters> tink_params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<AesGcmKey> key =
+      AesGcmKey::Create(*crunchy_params, secret, /*id_requirement=*/0x01020304,
+                        GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesGcmKey> other_key =
+      AesGcmKey::Create(*tink_params, secret, /*id_requirement=*/0x01020304,
+                        GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesGcmKeyTest, DifferentSecretDataNotEqual) {
+  util::StatusOr<AesGcmParameters> params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret1 = RestrictedData(/*num_random_bytes=*/32);
+  RestrictedData secret2 = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *params, secret1, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create(
+      *params, secret2, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesGcmKeyTest, DifferentIdRequirementNotEqual) {
+  util::StatusOr<AesGcmParameters> params =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *params, secret, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesGcmKey> other_key = AesGcmKey::Create(
+      *params, secret, /*id_requirement=*/0x02030405, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/aes_gcm_parameters.cc b/cc/aead/aes_gcm_parameters.cc
new file mode 100644
index 0000000..d78d888
--- /dev/null
+++ b/cc/aead/aes_gcm_parameters.cc
@@ -0,0 +1,103 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/aead/aes_gcm_parameters.h"
+
+#include <set>
+
+#include "absl/strings/str_cat.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetKeySizeInBytes(
+    int key_size) {
+  key_size_in_bytes_ = key_size;
+  return *this;
+}
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetIvSizeInBytes(
+    int iv_size) {
+  iv_size_in_bytes_ = iv_size;
+  return *this;
+}
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetTagSizeInBytes(
+    int tag_size) {
+  tag_size_in_bytes_ = tag_size;
+  return *this;
+}
+
+AesGcmParameters::Builder& AesGcmParameters::Builder::SetVariant(
+    Variant variant) {
+  variant_ = variant;
+  return *this;
+}
+
+util::StatusOr<AesGcmParameters> AesGcmParameters::Builder::Build() {
+  if (key_size_in_bytes_ != 16 && key_size_in_bytes_ != 24 &&
+      key_size_in_bytes_ != 32) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Key size should be 16, 24, or 32 bytes, got ",
+                     key_size_in_bytes_, " bytes."));
+  }
+  if (iv_size_in_bytes_ <= 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("IV size should be positive, got ",
+                                     iv_size_in_bytes_, " bytes."));
+  }
+  if (tag_size_in_bytes_ < 12 || tag_size_in_bytes_ > 16) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Tag size should be between 12 and 16 bytes, got ",
+                     tag_size_in_bytes_, " bytes."));
+  }
+  static const std::set<Variant>* supported_variants = new std::set<Variant>(
+      {Variant::kTink, Variant::kCrunchy, Variant::kNoPrefix});
+  if (supported_variants->find(variant_) == supported_variants->end()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create AES-GCM parameters with unknown variant.");
+  }
+  return AesGcmParameters(key_size_in_bytes_, iv_size_in_bytes_,
+                          tag_size_in_bytes_, variant_);
+}
+
+bool AesGcmParameters::operator==(const Parameters& other) const {
+  const AesGcmParameters* that = dynamic_cast<const AesGcmParameters*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (key_size_in_bytes_ != that->key_size_in_bytes_) {
+    return false;
+  }
+  if (iv_size_in_bytes_ != that->iv_size_in_bytes_) {
+    return false;
+  }
+  if (tag_size_in_bytes_ != that->tag_size_in_bytes_) {
+    return false;
+  }
+  if (variant_ != that->variant_) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/aes_gcm_parameters.h b/cc/aead/aes_gcm_parameters.h
new file mode 100644
index 0000000..de87b2c
--- /dev/null
+++ b/cc/aead/aes_gcm_parameters.h
@@ -0,0 +1,105 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AES_GCM_PARAMETERS_H_
+#define TINK_AEAD_AES_GCM_PARAMETERS_H_
+
+#include "tink/aead/aead_parameters.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes the parameters of an `AesGcmKey`.
+class AesGcmParameters : public AeadParameters {
+ public:
+  // Description of the output prefix prepended to the ciphertext.
+  enum class Variant : int {
+    // Prepends '0x01<big endian key id>' to the ciphertext.
+    kTink = 1,
+    // Prepends '0x00<big endian key id>' to the ciphertext.
+    kCrunchy = 2,
+    // Does not prepend any prefix (i.e., keys must have no ID requirement).
+    kNoPrefix = 3,
+    // Added to guard from failures that may be caused by future expansions.
+    kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+  };
+
+  // Creates AES-GCM parameters instances.
+  class Builder {
+   public:
+    // Copyable and movable.
+    Builder(const Builder& other) = default;
+    Builder& operator=(const Builder& other) = default;
+    Builder(Builder&& other) = default;
+    Builder& operator=(Builder&& other) = default;
+
+    // Creates initially empty parameters builder.
+    Builder() = default;
+
+    Builder& SetKeySizeInBytes(int key_size);
+    Builder& SetIvSizeInBytes(int iv_size);
+    Builder& SetTagSizeInBytes(int tag_size);
+    Builder& SetVariant(Variant variant);
+
+    // Creates AES-GCM parameters object from this builder.
+    util::StatusOr<AesGcmParameters> Build();
+
+   private:
+    int key_size_in_bytes_;
+    int iv_size_in_bytes_;
+    int tag_size_in_bytes_;
+    Variant variant_;
+  };
+
+  // Copyable and movable.
+  AesGcmParameters(const AesGcmParameters& other) = default;
+  AesGcmParameters& operator=(const AesGcmParameters& other) = default;
+  AesGcmParameters(AesGcmParameters&& other) = default;
+  AesGcmParameters& operator=(AesGcmParameters&& other) = default;
+
+  int KeySizeInBytes() const { return key_size_in_bytes_; }
+
+  int IvSizeInBytes() const { return iv_size_in_bytes_; }
+
+  int TagSizeInBytes() const { return tag_size_in_bytes_; }
+
+  Variant GetVariant() const { return variant_; }
+
+  bool HasIdRequirement() const override {
+    return variant_ != Variant::kNoPrefix;
+  }
+
+  bool operator==(const Parameters& other) const override;
+
+ private:
+  AesGcmParameters(int key_size_in_bytes, int iv_size_in_bytes,
+                   int tag_size_in_bytes, Variant variant)
+      : key_size_in_bytes_(key_size_in_bytes),
+        iv_size_in_bytes_(iv_size_in_bytes),
+        tag_size_in_bytes_(tag_size_in_bytes),
+        variant_(variant) {}
+
+  int key_size_in_bytes_;
+  int iv_size_in_bytes_;
+  int tag_size_in_bytes_;
+  Variant variant_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_AEAD_AES_GCM_PARAMETERS_H_
diff --git a/cc/aead/aes_gcm_parameters_test.cc b/cc/aead/aes_gcm_parameters_test.cc
new file mode 100644
index 0000000..7238bb9
--- /dev/null
+++ b/cc/aead/aes_gcm_parameters_test.cc
@@ -0,0 +1,386 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/aead/aes_gcm_parameters.h"
+
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct BuildTestCase {
+  AesGcmParameters::Variant variant;
+  int key_size;
+  int iv_size;
+  int tag_size;
+  bool has_id_requirement;
+};
+
+using AesGcmParametersBuildTest = TestWithParam<BuildTestCase>;
+
+INSTANTIATE_TEST_SUITE_P(
+    AesGcmParametersBuildTestSuite, AesGcmParametersBuildTest,
+    Values(BuildTestCase{AesGcmParameters::Variant::kTink, /*key_size=*/16,
+                         /*iv_size=*/12, /*tag_size=*/12,
+                         /*has_id_requirement=*/true},
+           BuildTestCase{AesGcmParameters::Variant::kCrunchy, /*key_size=*/24,
+                         /*iv_size=*/14, /*tag_size=*/14,
+                         /*has_id_requirement=*/true},
+           BuildTestCase{AesGcmParameters::Variant::kNoPrefix,
+                         /*key_size=*/32, /*iv_size=*/16, /*tag_size=*/16,
+                         /*has_id_requirement=*/false}));
+
+TEST_P(AesGcmParametersBuildTest, Build) {
+  BuildTestCase test_case = GetParam();
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(test_case.key_size)
+          .SetIvSizeInBytes(test_case.iv_size)
+          .SetTagSizeInBytes(test_case.tag_size)
+          .SetVariant(test_case.variant)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  EXPECT_THAT(parameters->KeySizeInBytes(), Eq(test_case.key_size));
+  EXPECT_THAT(parameters->IvSizeInBytes(), Eq(test_case.iv_size));
+  EXPECT_THAT(parameters->TagSizeInBytes(), Eq(test_case.tag_size));
+  EXPECT_THAT(parameters->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(parameters->HasIdRequirement(), Eq(test_case.has_id_requirement));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingVariantFails) {
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(16)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidVariantFails) {
+  EXPECT_THAT(
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::
+                          kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements)
+          .Build()
+          .status(),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingKeySizeFails) {
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidKeySizeFails) {
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(15)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(17)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(23)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(25)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(31)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(33)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingIvSizeFails) {
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(16)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidIvSizeFails) {
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(16)
+                  .SetIvSizeInBytes(0)
+                  .SetTagSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithoutSettingTagSizeFails) {
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(16)
+                  .SetIvSizeInBytes(16)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, BuildWithInvalidTagSizeFails) {
+  // Too small.
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(16)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(11)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  // Too big.
+  EXPECT_THAT(AesGcmParameters::Builder()
+                  .SetKeySizeInBytes(16)
+                  .SetIvSizeInBytes(16)
+                  .SetTagSizeInBytes(17)
+                  .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+                  .Build()
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesGcmParametersTest, CopyConstructor) {
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(16)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  AesGcmParameters copy(*parameters);
+  EXPECT_THAT(copy.KeySizeInBytes(), Eq(16));
+  EXPECT_THAT(copy.IvSizeInBytes(), Eq(16));
+  EXPECT_THAT(copy.TagSizeInBytes(), Eq(16));
+  EXPECT_THAT(copy.GetVariant(), Eq(AesGcmParameters::Variant::kTink));
+  EXPECT_THAT(copy.HasIdRequirement(), IsTrue());
+}
+
+TEST(AesGcmParametersTest, CopyAssignment) {
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(16)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  AesGcmParameters copy = *parameters;
+  EXPECT_THAT(copy.KeySizeInBytes(), Eq(16));
+  EXPECT_THAT(copy.IvSizeInBytes(), Eq(16));
+  EXPECT_THAT(copy.TagSizeInBytes(), Eq(16));
+  EXPECT_THAT(copy.GetVariant(), Eq(AesGcmParameters::Variant::kTink));
+  EXPECT_THAT(copy.HasIdRequirement(), IsTrue());
+}
+
+using AesGcmParametersVariantTest =
+    TestWithParam<std::tuple<int, int, AesGcmParameters::Variant>>;
+
+INSTANTIATE_TEST_SUITE_P(AesGcmParametersVariantTestSuite,
+                         AesGcmParametersVariantTest,
+                         Combine(Values(16, 24, 32), Range(12, 16),
+                                 Values(AesGcmParameters::Variant::kTink,
+                                        AesGcmParameters::Variant::kCrunchy,
+                                        AesGcmParameters::Variant::kNoPrefix)));
+
+TEST_P(AesGcmParametersVariantTest, ParametersEquals) {
+  int key_size;
+  int iv_and_tag_size;
+  AesGcmParameters::Variant variant;
+  std::tie(key_size, iv_and_tag_size, variant) = GetParam();
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(key_size)
+          .SetIvSizeInBytes(iv_and_tag_size)
+          .SetTagSizeInBytes(iv_and_tag_size)
+          .SetVariant(variant)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesGcmParameters> other_parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(key_size)
+          .SetIvSizeInBytes(iv_and_tag_size)
+          .SetTagSizeInBytes(iv_and_tag_size)
+          .SetVariant(variant)
+          .Build();
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters == *other_parameters);
+  EXPECT_TRUE(*other_parameters == *parameters);
+  EXPECT_FALSE(*parameters != *other_parameters);
+  EXPECT_FALSE(*other_parameters != *parameters);
+}
+
+TEST(AesGcmParametersTest, KeySizeNotEqual) {
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesGcmParameters> other_parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(24)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesGcmParametersTest, IvSizeNotEqual) {
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesGcmParameters> other_parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(12)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesGcmParametersTest, TagSizeNotEqual) {
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesGcmParameters> other_parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(14)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesGcmParametersTest, VariantNotEqual) {
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kTink)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesGcmParameters> other_parameters =
+      AesGcmParameters::Builder()
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(16)
+          .SetTagSizeInBytes(16)
+          .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+          .Build();
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/aes_gcm_proto_serialization.cc b/cc/aead/aes_gcm_proto_serialization.cc
new file mode 100644
index 0000000..57272ff
--- /dev/null
+++ b/cc/aead/aes_gcm_proto_serialization.cc
@@ -0,0 +1,273 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/aead/aes_gcm_proto_serialization.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/aead/aes_gcm_key.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::OutputPrefixType;
+
+using AesGcmProtoParametersParserImpl =
+    internal::ParametersParserImpl<internal::ProtoParametersSerialization,
+                                   AesGcmParameters>;
+using AesGcmProtoParametersSerializerImpl =
+    internal::ParametersSerializerImpl<AesGcmParameters,
+                                       internal::ProtoParametersSerialization>;
+using AesGcmProtoKeyParserImpl =
+    internal::KeyParserImpl<internal::ProtoKeySerialization, AesGcmKey>;
+using AesGcmProtoKeySerializerImpl =
+    internal::KeySerializerImpl<AesGcmKey, internal::ProtoKeySerialization>;
+
+const absl::string_view kTypeUrl =
+    "type.googleapis.com/google.crypto.tink.AesGcmKey";
+
+util::StatusOr<AesGcmParameters::Variant> ToVariant(
+    OutputPrefixType output_prefix_type) {
+  switch (output_prefix_type) {
+    case OutputPrefixType::LEGACY:
+       ABSL_FALLTHROUGH_INTENDED;  // Parse LEGACY output prefix as CRUNCHY.
+    case OutputPrefixType::CRUNCHY:
+      return AesGcmParameters::Variant::kCrunchy;
+    case OutputPrefixType::RAW:
+      return AesGcmParameters::Variant::kNoPrefix;
+    case OutputPrefixType::TINK:
+      return AesGcmParameters::Variant::kTink;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine AesGcmParameters::Variant");
+  }
+}
+
+util::StatusOr<OutputPrefixType> ToOutputPrefixType(
+    AesGcmParameters::Variant variant) {
+  switch (variant) {
+    case AesGcmParameters::Variant::kCrunchy:
+      return OutputPrefixType::CRUNCHY;
+    case AesGcmParameters::Variant::kNoPrefix:
+      return OutputPrefixType::RAW;
+    case AesGcmParameters::Variant::kTink:
+      return OutputPrefixType::TINK;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine output prefix type");
+  }
+}
+
+// Legacy Tink AES-GCM key proto format assumes 12-byte random IVs and 16-byte
+// tags.
+util::Status ValidateParamsForProto(const AesGcmParameters& params) {
+  if (params.IvSizeInBytes() != 12) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Tink currently restricts AES-GCM IV size to 12 bytes.");
+  }
+  if (params.TagSizeInBytes() != 16) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Tink currently restricts AES-GCM tag size to 16 bytes.");
+  }
+  return util::OkStatus();
+}
+
+util::StatusOr<AesGcmParameters> ParseParameters(
+    const internal::ProtoParametersSerialization& serialization) {
+  if (serialization.GetKeyTemplate().type_url() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing AesGcmParameters.");
+  }
+
+  AesGcmKeyFormat proto_key_format;
+  if (!proto_key_format.ParseFromString(
+          serialization.GetKeyTemplate().value())) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse AesGcmKeyFormat proto");
+  }
+  if (proto_key_format.version() != 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Only version 0 keys are accepted.");
+  }
+
+  util::StatusOr<AesGcmParameters::Variant> variant =
+      ToVariant(serialization.GetKeyTemplate().output_prefix_type());
+  if (!variant.ok()) return variant.status();
+
+  // Legacy Tink AES-GCM key proto format assumes 12-byte random IVs and 16-byte
+  // tags.
+  return AesGcmParameters::Builder()
+      .SetVariant(*variant)
+      .SetKeySizeInBytes(proto_key_format.key_size())
+      .SetIvSizeInBytes(12)
+      .SetTagSizeInBytes(16)
+      .Build();
+}
+
+util::StatusOr<internal::ProtoParametersSerialization> SerializeParameters(
+    const AesGcmParameters& parameters) {
+  util::Status valid_params = ValidateParamsForProto(parameters);
+  if (!valid_params.ok()) return valid_params;
+
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(parameters.GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+  AesGcmKeyFormat proto_key_format;
+  proto_key_format.set_key_size(parameters.KeySizeInBytes());
+
+  return internal::ProtoParametersSerialization::Create(
+      kTypeUrl, *output_prefix_type, proto_key_format.SerializeAsString());
+}
+
+util::StatusOr<AesGcmKey> ParseKey(
+    const internal::ProtoKeySerialization& serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  if (serialization.TypeUrl() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing AesGcmKey.");
+  }
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+  google::crypto::tink::AesGcmKey proto_key;
+  RestrictedData restricted_data = serialization.SerializedKeyProto();
+  // OSS proto library complains if input is not converted to a string.
+  if (!proto_key.ParseFromString(
+          std::string(restricted_data.GetSecret(*token)))) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse AesGcmKey proto");
+  }
+  if (proto_key.version() != 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Only version 0 keys are accepted.");
+  }
+
+  util::StatusOr<AesGcmParameters::Variant> variant =
+      ToVariant(serialization.GetOutputPrefixType());
+  if (!variant.ok()) return variant.status();
+
+  // Legacy AES-GCM key proto format assumes 12-byte random IVs and 16-byte
+  // tags.
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(*variant)
+          .SetKeySizeInBytes(proto_key.key_value().length())
+          .SetIvSizeInBytes(12)
+          .SetTagSizeInBytes(16)
+          .Build();
+  if (!parameters.ok()) return parameters.status();
+
+  return AesGcmKey::Create(
+      *parameters, RestrictedData(proto_key.key_value(), *token),
+      serialization.IdRequirement(), GetPartialKeyAccess());
+}
+
+util::StatusOr<internal::ProtoKeySerialization> SerializeKey(
+    const AesGcmKey& key, absl::optional<SecretKeyAccessToken> token) {
+  util::Status valid_params = ValidateParamsForProto(key.GetParameters());
+  if (!valid_params.ok()) return valid_params;
+
+  util::StatusOr<RestrictedData> restricted_input =
+      key.GetKeyBytes(GetPartialKeyAccess());
+  if (!restricted_input.ok()) return restricted_input.status();
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+
+  google::crypto::tink::AesGcmKey proto_key;
+  proto_key.set_version(0);
+  // OSS proto library complains if input is not converted to a string.
+  proto_key.set_key_value(std::string(restricted_input->GetSecret(*token)));
+
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(key.GetParameters().GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+  RestrictedData restricted_output =
+      RestrictedData(proto_key.SerializeAsString(), *token);
+  return internal::ProtoKeySerialization::Create(
+      kTypeUrl, restricted_output, google::crypto::tink::KeyData::SYMMETRIC,
+      *output_prefix_type, key.GetIdRequirement());
+}
+
+AesGcmProtoParametersParserImpl* AesGcmProtoParametersParser() {
+  static auto* parser =
+      new AesGcmProtoParametersParserImpl(kTypeUrl, ParseParameters);
+  return parser;
+}
+
+AesGcmProtoParametersSerializerImpl* AesGcmProtoParametersSerializer() {
+  static auto* serializer =
+      new AesGcmProtoParametersSerializerImpl(kTypeUrl, SerializeParameters);
+  return serializer;
+}
+
+AesGcmProtoKeyParserImpl* AesGcmProtoKeyParser() {
+  static auto* parser = new AesGcmProtoKeyParserImpl(kTypeUrl, ParseKey);
+  return parser;
+}
+
+AesGcmProtoKeySerializerImpl* AesGcmProtoKeySerializer() {
+  static auto* serializer = new AesGcmProtoKeySerializerImpl(SerializeKey);
+  return serializer;
+}
+
+}  // namespace
+
+util::Status RegisterAesGcmProtoSerialization() {
+  util::Status status =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .RegisterParametersParser(AesGcmProtoParametersParser());
+  if (!status.ok()) return status;
+
+  status = internal::MutableSerializationRegistry::GlobalInstance()
+               .RegisterParametersSerializer(AesGcmProtoParametersSerializer());
+  if (!status.ok()) return status;
+
+  status = internal::MutableSerializationRegistry::GlobalInstance()
+               .RegisterKeyParser(AesGcmProtoKeyParser());
+  if (!status.ok()) return status;
+
+  return internal::MutableSerializationRegistry::GlobalInstance()
+      .RegisterKeySerializer(AesGcmProtoKeySerializer());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/aes_gcm_proto_serialization.h b/cc/aead/aes_gcm_proto_serialization.h
new file mode 100644
index 0000000..68a49f4
--- /dev/null
+++ b/cc/aead/aes_gcm_proto_serialization.h
@@ -0,0 +1,31 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_AEAD_AES_GCM_PROTO_SERIALIZATION_H_
+#define TINK_AEAD_AES_GCM_PROTO_SERIALIZATION_H_
+
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+// Registers proto parsers and serializers for AES-GCM parameters and keys.
+crypto::tink::util::Status RegisterAesGcmProtoSerialization();
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_AEAD_AES_GCM_PROTO_SERIALIZATION_H_
diff --git a/cc/aead/aes_gcm_proto_serialization_test.cc b/cc/aead/aes_gcm_proto_serialization_test.cc
new file mode 100644
index 0000000..f19c820
--- /dev/null
+++ b/cc/aead/aes_gcm_proto_serialization_test.cc
@@ -0,0 +1,515 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/aead/aes_gcm_proto_serialization.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aes_gcm_key.h"
+#include "tink/aead/aes_gcm_parameters.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_matchers.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::subtle::Random;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::NotNull;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  AesGcmParameters::Variant variant;
+  OutputPrefixType output_prefix_type;
+  int key_size;
+  int iv_size;
+  int tag_size;
+  absl::optional<int> id;
+  std::string output_prefix;
+};
+
+class AesGcmProtoSerializationTest : public TestWithParam<TestCase> {
+ protected:
+  void SetUp() override {
+    internal::MutableSerializationRegistry::GlobalInstance().Reset();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    AesGcmProtoSerializationTestSuite, AesGcmProtoSerializationTest,
+    Values(TestCase{AesGcmParameters::Variant::kTink, OutputPrefixType::TINK,
+                    /*key_size=*/16, /*iv_size=*/12, /*tag_size=*/16,
+                    /*id=*/0x02030400,
+                    /*output_prefix=*/std::string("\x01\x02\x03\x04\x00", 5)},
+           TestCase{AesGcmParameters::Variant::kCrunchy,
+                    OutputPrefixType::CRUNCHY, /*key_size=*/16, /*iv_size=*/12,
+                    /*tag_size=*/16, /*id=*/0x01030005,
+                    /*output_prefix=*/std::string("\x00\x01\x03\x00\x05", 5)},
+           TestCase{AesGcmParameters::Variant::kNoPrefix, OutputPrefixType::RAW,
+                    /*key_size=*/32, /*iv_size=*/12, /*tag_size=*/16,
+                    /*id=*/absl::nullopt, /*output_prefix=*/""}));
+
+TEST_P(AesGcmProtoSerializationTest, ParseParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  AesGcmKeyFormat key_format_proto;
+  key_format_proto.set_version(0);
+  key_format_proto.set_key_size(test_case.key_size);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey",
+          test_case.output_prefix_type, key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), test_case.id.has_value());
+
+  const AesGcmParameters* gcm_params =
+      dynamic_cast<const AesGcmParameters*>(params->get());
+  ASSERT_THAT(gcm_params, NotNull());
+  EXPECT_THAT(gcm_params->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(gcm_params->KeySizeInBytes(), Eq(test_case.key_size));
+  EXPECT_THAT(gcm_params->IvSizeInBytes(), Eq(test_case.iv_size));
+  EXPECT_THAT(gcm_params->TagSizeInBytes(), Eq(test_case.tag_size));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseParametersWithInvalidSerialization) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  AesGcmKeyFormat key_format_proto;
+  key_format_proto.set_version(0);
+  key_format_proto.set_key_size(16);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey",
+          OutputPrefixType::RAW, "invalid_serialization");
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseParametersWithUnkownOutputPrefix) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  AesGcmKeyFormat key_format_proto;
+  key_format_proto.set_version(0);
+  key_format_proto.set_key_size(16);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey",
+          OutputPrefixType::UNKNOWN_PREFIX,
+          key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseParametersWithInvalidVersion) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  AesGcmKeyFormat key_format_proto;
+  key_format_proto.set_version(1);
+  key_format_proto.set_key_size(16);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey",
+          OutputPrefixType::RAW,
+          key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmProtoSerializationTest, SerializeParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(test_case.variant)
+          .SetKeySizeInBytes(test_case.key_size)
+          .SetIvSizeInBytes(test_case.iv_size)
+          .SetTagSizeInBytes(test_case.tag_size)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+
+  const internal::ProtoParametersSerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoParametersSerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().type_url(),
+              Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().output_prefix_type(),
+              Eq(test_case.output_prefix_type));
+
+  AesGcmKeyFormat key_format;
+  ASSERT_THAT(
+      key_format.ParseFromString(proto_serialization->GetKeyTemplate().value()),
+      IsTrue());
+  EXPECT_THAT(key_format.key_size(), Eq(test_case.key_size));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeParametersWithDisallowedIvSize) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+          .SetKeySizeInBytes(16)
+          .SetIvSizeInBytes(14)
+          .SetTagSizeInBytes(16)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  EXPECT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeParametersWithDisallowedTagSize) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+          .SetKeySizeInBytes(16)
+          .SetIvSizeInBytes(12)
+          .SetTagSizeInBytes(14)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  EXPECT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmProtoSerializationTest, ParseKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  google::crypto::tink::AesGcmKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+          KeyData::SYMMETRIC, test_case.output_prefix_type, test_case.id);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT((*key)->GetIdRequirement(), Eq(test_case.id));
+  EXPECT_THAT((*key)->GetParameters().HasIdRequirement(),
+              test_case.id.has_value());
+
+  util::StatusOr<AesGcmParameters> expected_parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(test_case.variant)
+          .SetKeySizeInBytes(test_case.key_size)
+          .SetIvSizeInBytes(test_case.iv_size)
+          .SetTagSizeInBytes(test_case.tag_size)
+          .Build();
+  ASSERT_THAT(expected_parameters, IsOk());
+
+  util::StatusOr<AesGcmKey> expected_key = AesGcmKey::Create(
+      *expected_parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      test_case.id, GetPartialKeyAccess());
+  ASSERT_THAT(expected_key, IsOk());
+
+  EXPECT_THAT(**key, Eq(*expected_key));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseLegacyKeyAsCrunchy) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(32);
+  google::crypto::tink::AesGcmKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::LEGACY, /*id_requirement=*/123);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+
+  const AesGcmKey* aes_gcm_key = dynamic_cast<const AesGcmKey*>(key->get());
+  ASSERT_THAT(aes_gcm_key, NotNull());
+  EXPECT_THAT(aes_gcm_key->GetParameters().GetVariant(),
+              Eq(AesGcmParameters::Variant::kCrunchy));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseKeyWithInvalidSerialization) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  RestrictedData serialized_key =
+      RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseKeyNoSecretKeyAccess) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  google::crypto::tink::AesGcmKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, /*token=*/absl::nullopt);
+  EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, ParseKeyWithInvalidVersion) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  google::crypto::tink::AesGcmKey key_proto;
+  key_proto.set_version(1);  // Invalid version number.
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesGcmKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesGcmProtoSerializationTest, SerializeKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(test_case.variant)
+          .SetKeySizeInBytes(test_case.key_size)
+          .SetIvSizeInBytes(test_case.iv_size)
+          .SetTagSizeInBytes(test_case.tag_size)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      test_case.id, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+
+  const internal::ProtoKeySerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoKeySerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->TypeUrl(),
+              Eq("type.googleapis.com/google.crypto.tink.AesGcmKey"));
+  EXPECT_THAT(proto_serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC));
+  EXPECT_THAT(proto_serialization->GetOutputPrefixType(),
+              Eq(test_case.output_prefix_type));
+  EXPECT_THAT(proto_serialization->IdRequirement(), Eq(test_case.id));
+
+  google::crypto::tink::AesGcmKey proto_key;
+  // OSS proto library complains if input is not converted to a string.
+  ASSERT_THAT(proto_key.ParseFromString(std::string(
+                  proto_serialization->SerializedKeyProto().GetSecret(
+                      InsecureSecretKeyAccess::Get()))),
+              IsTrue());
+  EXPECT_THAT(proto_key.key_value().size(), Eq(test_case.key_size));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeKeyWithDisallowedIvSize) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(14)
+          .SetTagSizeInBytes(16)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(32);
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  EXPECT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeKeyWithDisallowedTagSize) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+          .SetKeySizeInBytes(32)
+          .SetIvSizeInBytes(12)
+          .SetTagSizeInBytes(14)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(32);
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  EXPECT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesGcmProtoSerializationTest, SerializeKeyNoSecretKeyAccess) {
+  ASSERT_THAT(RegisterAesGcmProtoSerialization(), IsOk());
+
+  util::StatusOr<AesGcmParameters> parameters =
+      AesGcmParameters::Builder()
+          .SetVariant(AesGcmParameters::Variant::kNoPrefix)
+          .SetKeySizeInBytes(16)
+          .SetIvSizeInBytes(12)
+          .SetTagSizeInBytes(16)
+          .Build();
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  util::StatusOr<AesGcmKey> key = AesGcmKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(*key, absl::nullopt);
+  EXPECT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/cord_aead.h b/cc/aead/cord_aead.h
index 46a60ce..e57ba5f 100644
--- a/cc/aead/cord_aead.h
+++ b/cc/aead/cord_aead.h
@@ -56,7 +56,7 @@
       absl::Cord ciphertext,
       absl::Cord associated_data) const = 0;
 
-  virtual ~CordAead() {}
+  virtual ~CordAead() = default;
 };
 
 }  // namespace tink
diff --git a/cc/aead/cord_aead_wrapper.cc b/cc/aead/cord_aead_wrapper.cc
index 0efb3a0..263b3fc 100644
--- a/cc/aead/cord_aead_wrapper.cc
+++ b/cc/aead/cord_aead_wrapper.cc
@@ -56,7 +56,7 @@
   crypto::tink::util::StatusOr<absl::Cord> Decrypt(
       absl::Cord ciphertext, absl::Cord associated_data) const override;
 
-  ~CordAeadSetWrapper() override {}
+  ~CordAeadSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<CordAead>> aead_set_;
diff --git a/cc/aead/failing_aead.cc b/cc/aead/failing_aead.cc
index 81835c5..9f6c0f8 100644
--- a/cc/aead/failing_aead.cc
+++ b/cc/aead/failing_aead.cc
@@ -15,8 +15,10 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/aead/failing_aead.h"
 
+#include <memory>
 #include <string>
 #include <utility>
+
 #include "absl/strings/string_view.h"
 
 namespace crypto {
diff --git a/cc/aead/failing_aead.h b/cc/aead/failing_aead.h
index 70b80a9..88d8c81 100644
--- a/cc/aead/failing_aead.h
+++ b/cc/aead/failing_aead.h
@@ -16,6 +16,7 @@
 #ifndef TINK_AEAD_FAILING_AEAD_H_
 #define TINK_AEAD_FAILING_AEAD_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
diff --git a/cc/aead/internal/BUILD.bazel b/cc/aead/internal/BUILD.bazel
index 0f7c72f..225bffc 100644
--- a/cc/aead/internal/BUILD.bazel
+++ b/cc/aead/internal/BUILD.bazel
@@ -11,6 +11,7 @@
         "//util:errors",
         "//util:statusor",
         "@boringssl//:crypto",
+        "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/status",
     ],
 )
@@ -61,13 +62,11 @@
         "//internal:ssl_unique_ptr",
         "//subtle:random",
         "//subtle:subtle_util",
-        "//util:errors",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "@boringssl//:crypto",
         "@com_google_absl//absl/status",
-        "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:cord",
     ],
 )
@@ -154,7 +153,7 @@
     name = "cord_aes_gcm_boringssl_test",
     size = "small",
     srcs = ["cord_aes_gcm_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:aes_gcm"],
+    data = ["//testvectors:aes_gcm"],
     deps = [
         ":cord_aes_gcm_boringssl",
         "//subtle:aes_gcm_boringssl",
@@ -199,7 +198,7 @@
 cc_test(
     name = "zero_copy_aes_gcm_boringssl_test",
     srcs = ["zero_copy_aes_gcm_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:aes_gcm"],
+    data = ["//testvectors:aes_gcm"],
     deps = [
         ":wycheproof_aead",
         ":zero_copy_aead",
@@ -235,21 +234,20 @@
     name = "ssl_aead_test",
     srcs = ["ssl_aead_test.cc"],
     data = [
-        "@wycheproof//testvectors:aes_gcm",
-        "@wycheproof//testvectors:aes_gcm_siv",
-        "@wycheproof//testvectors:chacha20_poly1305",
+        "//testvectors:aes_gcm",
+        "//testvectors:aes_gcm_siv",
+        "//testvectors:chacha20_poly1305",
     ],
     deps = [
         ":ssl_aead",
         ":wycheproof_aead",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//internal:ssl_util",
         "//subtle:subtle_util",
         "//util:secret_data",
         "//util:statusor",
         "//util:test_matchers",
         "@com_google_absl//absl/container:flat_hash_set",
-        "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/types:span",
@@ -265,6 +263,7 @@
         ":ssl_aead",
         "//config:tink_fips",
         "//internal:ssl_util",
+        "//internal:util",
         "//subtle:subtle_util",
         "//util:secret_data",
         "//util:statusor",
diff --git a/cc/aead/internal/BUILD.gn b/cc/aead/internal/BUILD.gn
index ffabdb7..1202a08 100644
--- a/cc/aead/internal/BUILD.gn
+++ b/cc/aead/internal/BUILD.gn
@@ -14,6 +14,7 @@
     "aead_util.h",
   ]
   public_deps = [
+    "//third_party/abseil-cpp/absl/container:flat_hash_set",
     "//third_party/abseil-cpp/absl/status:status",
     "//third_party/boringssl:crypto",
     "//third_party/tink/cc/util:errors",
@@ -60,13 +61,11 @@
     ":aead_util",
     "//third_party/abseil-cpp/absl/status:status",
     "//third_party/abseil-cpp/absl/strings:cord",
-    "//third_party/abseil-cpp/absl/strings:strings",
     "//third_party/boringssl:crypto",
     "//third_party/tink/cc/aead:cord_aead",
     "//third_party/tink/cc/internal:ssl_unique_ptr",
     "//third_party/tink/cc/subtle:random",
     "//third_party/tink/cc/subtle:subtle_util",
-    "//third_party/tink/cc/util:errors",
     "//third_party/tink/cc/util:secret_data",
     "//third_party/tink/cc/util:status",
     "//third_party/tink/cc/util:statusor",
diff --git a/cc/aead/internal/CMakeLists.txt b/cc/aead/internal/CMakeLists.txt
index e7c1a07..217d2b4 100644
--- a/cc/aead/internal/CMakeLists.txt
+++ b/cc/aead/internal/CMakeLists.txt
@@ -6,6 +6,7 @@
     aead_util.cc
     aead_util.h
   DEPS
+    absl::flat_hash_set
     absl::status
     crypto
     tink::util::errors
@@ -21,6 +22,7 @@
     absl::strings
     tink::subtle::wycheproof_util
     tink::util::statusor
+  TESTONLY
 )
 
 tink_cc_library(
@@ -52,14 +54,12 @@
   DEPS
     tink::aead::internal::aead_util
     absl::status
-    absl::strings
     absl::cord
     crypto
     tink::aead::cord_aead
     tink::internal::ssl_unique_ptr
     tink::subtle::random
     tink::subtle::subtle_util
-    tink::util::errors
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
@@ -74,6 +74,7 @@
     gmock
     absl::strings
     tink::util::statusor
+  TESTONLY
 )
 
 tink_cc_library(
@@ -159,11 +160,10 @@
     tink::aead::internal::wycheproof_aead
     gmock
     absl::flat_hash_set
-    absl::memory
     absl::status
     absl::strings
     absl::span
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::internal::ssl_util
     tink::subtle::subtle_util
     tink::util::secret_data
@@ -241,6 +241,7 @@
     absl::span
     tink::config::tink_fips
     tink::internal::ssl_util
+    tink::internal::util
     tink::subtle::subtle_util
     tink::util::secret_data
     tink::util::statusor
diff --git a/cc/aead/internal/aead_from_zero_copy_test.cc b/cc/aead/internal/aead_from_zero_copy_test.cc
index 2fe4486..9693661 100644
--- a/cc/aead/internal/aead_from_zero_copy_test.cc
+++ b/cc/aead/internal/aead_from_zero_copy_test.cc
@@ -49,7 +49,7 @@
 
 TEST(AeadFromZeroCopyTest, EncryptSucceeds) {
   std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
-      absl::WrapUnique(new MockZeroCopyAead());
+      std::make_unique<MockZeroCopyAead>();
   EXPECT_CALL(*mock_zero_copy_aead, MaxEncryptionSize(kPlaintext.size()))
       .WillOnce(Return(kCiphertext.size()));
   EXPECT_CALL(*mock_zero_copy_aead, Encrypt(kPlaintext, kAssociatedData, _))
@@ -66,7 +66,7 @@
 
 TEST(AeadFromZeroCopyTest, EncryptFailsIfZeroCopyEncryptFails) {
   std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
-      absl::WrapUnique(new MockZeroCopyAead());
+      std::make_unique<MockZeroCopyAead>();
   EXPECT_CALL(*mock_zero_copy_aead, MaxEncryptionSize(kPlaintext.size()))
       .WillOnce(Return(kCiphertext.size()));
   EXPECT_CALL(*mock_zero_copy_aead, Encrypt(kPlaintext, kAssociatedData, _))
@@ -79,7 +79,7 @@
 
 TEST(AeadFromZeroCopyTest, DecryptSucceeds) {
   std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
-      absl::WrapUnique(new MockZeroCopyAead());
+      std::make_unique<MockZeroCopyAead>();
   EXPECT_CALL(*mock_zero_copy_aead, MaxDecryptionSize(kCiphertext.size()))
       .WillOnce(Return(kPlaintext.size()));
   EXPECT_CALL(*mock_zero_copy_aead, Decrypt(kCiphertext, kAssociatedData, _))
@@ -96,7 +96,7 @@
 
 TEST(AeadFromZeroCopyTest, EncryptFailsIfZeroCopyDecryptFails) {
   std::unique_ptr<MockZeroCopyAead> mock_zero_copy_aead =
-      absl::WrapUnique(new MockZeroCopyAead());
+      std::make_unique<MockZeroCopyAead>();
   EXPECT_CALL(*mock_zero_copy_aead, MaxDecryptionSize(kCiphertext.size()))
       .WillOnce(Return(kPlaintext.size()));
   EXPECT_CALL(*mock_zero_copy_aead, Decrypt(kCiphertext, kAssociatedData, _))
diff --git a/cc/aead/internal/aead_util.cc b/cc/aead/internal/aead_util.cc
index bfe0719..22c0bce 100644
--- a/cc/aead/internal/aead_util.cc
+++ b/cc/aead/internal/aead_util.cc
@@ -15,6 +15,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/aead/internal/aead_util.h"
 
+#include <string>
+
 #include "absl/status/status.h"
 #include "openssl/evp.h"
 #include "tink/util/errors.h"
@@ -24,6 +26,18 @@
 namespace tink {
 namespace internal {
 
+bool IsSupportedKmsEnvelopeAeadDekKeyType(absl::string_view key_type) {
+  static const auto *kSupportedDekKeyTypes =
+      new absl::flat_hash_set<std::string>({
+          "type.googleapis.com/google.crypto.tink.AesGcmKey",
+          "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key",
+          "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey",
+          "type.googleapis.com/google.crypto.tink.AesEaxKey",
+          "type.googleapis.com/google.crypto.tink.AesGcmSivKey",
+      });
+  return kSupportedDekKeyTypes->contains(key_type);
+}
+
 util::StatusOr<const EVP_CIPHER *> GetAesGcmCipherForKeySize(
     uint32_t key_size_in_bytes) {
   switch (key_size_in_bytes) {
diff --git a/cc/aead/internal/aead_util.h b/cc/aead/internal/aead_util.h
index c8c84d2..094b22d 100644
--- a/cc/aead/internal/aead_util.h
+++ b/cc/aead/internal/aead_util.h
@@ -16,6 +16,9 @@
 #ifndef TINK_AEAD_INTERNAL_AEAD_UTIL_H_
 #define TINK_AEAD_INTERNAL_AEAD_UTIL_H_
 
+#include <string>
+
+#include "absl/container/flat_hash_set.h"
 #include "openssl/evp.h"
 #include "tink/util/statusor.h"
 
@@ -23,6 +26,8 @@
 namespace tink {
 namespace internal {
 
+bool IsSupportedKmsEnvelopeAeadDekKeyType(absl::string_view key_type);
+
 // Returns a pointer to an AES-GCM EVP_CIPHER for the given key size.
 util::StatusOr<const EVP_CIPHER *> GetAesGcmCipherForKeySize(
     uint32_t key_size_in_bytes);
diff --git a/cc/aead/internal/aead_util_test.cc b/cc/aead/internal/aead_util_test.cc
index ea3941a..83c717f 100644
--- a/cc/aead/internal/aead_util_test.cc
+++ b/cc/aead/internal/aead_util_test.cc
@@ -27,6 +27,8 @@
 
 using ::crypto::tink::test::IsOk;
 using ::crypto::tink::test::IsOkAndHolds;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
 using ::testing::Not;
 
 TEST(AeadUtilTest, GetAesGcmCipherForKeySize) {
@@ -42,6 +44,15 @@
   }
 }
 
+TEST(AeadUtilTest, SupportedKmsEnvelopeAeadDekKeyTypes) {
+  EXPECT_THAT(IsSupportedKmsEnvelopeAeadDekKeyType(
+                  "type.googleapis.com/google.crypto.tink.AesGcmKey"),
+              IsTrue());
+  EXPECT_THAT(IsSupportedKmsEnvelopeAeadDekKeyType(
+                  "type.googleapis.com/google.crypto.tink.KmsEnvelopeAeadKey"),
+              IsFalse());
+}
+
 #ifdef OPENSSL_IS_BORINGSSL
 
 TEST(AeadUtilTest, GetAesAeadForKeySize) {
diff --git a/cc/aead/internal/cord_aes_gcm_boringssl.cc b/cc/aead/internal/cord_aes_gcm_boringssl.cc
index 321e9bf..4356bee 100644
--- a/cc/aead/internal/cord_aes_gcm_boringssl.cc
+++ b/cc/aead/internal/cord_aes_gcm_boringssl.cc
@@ -17,22 +17,18 @@
 #include "tink/aead/internal/cord_aes_gcm_boringssl.h"
 
 #include <cstdint>
-#include <iterator>
 #include <memory>
 #include <string>
 #include <utility>
-#include <vector>
 
 #include "absl/status/status.h"
 #include "absl/strings/cord.h"
 #include "openssl/evp.h"
-#include "openssl/err.h"
 #include "tink/aead/cord_aead.h"
 #include "tink/aead/internal/aead_util.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/subtle_util.h"
-#include "tink/util/errors.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -47,8 +43,8 @@
 
 // Set the IV `iv` for the given `context`. if `encryption` is true, set the
 // context for encryption, and for decryption otherwise.
-util::Status SetIv(EVP_CIPHER_CTX* context, absl::string_view iv,
-                   bool encryption) {
+util::Status SetIvAndDirection(EVP_CIPHER_CTX* context, absl::string_view iv,
+                               bool encryption) {
   const int encryption_flag = encryption ? 1 : 0;
   // Set the IV size.
   if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, iv.size(),
@@ -67,28 +63,100 @@
   return util::OkStatus();
 }
 
+#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L
+// Returns a new EVP_CIPHER_CTX for encryption (`encryption` == true) or
+// decryption (`encryption` == false). It tries to skip part of the
+// initialization copying `partial_context`.
+util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> NewContextFromPartial(
+    EVP_CIPHER_CTX* partial_context, absl::string_view iv, bool encryption) {
+  internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
+  if (context == nullptr) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "EVP_CIPHER_CTX_new failed");
+  }
+  // Try making a copy of `partial_context` to skip some pre-computations.
+  //
+  // NOTE: With BoringSSL and OpenSSL 1.1.1 EVP_CIPHER_CTX_copy makes a copy
+  // of the `cipher_data` field of `context` as well, which contains the key
+  // material and IV (see [1] and [2]).
+  //
+  // [1]https://github.com/google/boringssl/blob/4c8bcf0da2951cacd8ed8eaa7fd2df4b22fca23b/crypto/fipsmodule/cipher/cipher.c#L116
+  // [2]https://github.com/openssl/openssl/blob/830bf8e1e4749ad65c51b6a1d0d769ae689404ba/crypto/evp/evp_enc.c#L703
+  if (EVP_CIPHER_CTX_copy(context.get(), partial_context) <= 0) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "EVP_CIPHER_CTX_copy failed");
+  }
+  util::Status res =
+      SetIvAndDirection(context.get(), iv, /*encryption=*/encryption);
+  if (!res.ok()) {
+    return res;
+  }
+  return std::move(context);
+}
+#else
+// Returns a new EVP_CIPHER_CTX for encryption (`encryption` == true) or
+// decryption (`encryption` == false) with given `key` and `iv`.
+//
+// NOTE: Copying the context fails with OpenSSL 3.0, which doesn't provide a
+// `dupctx` function for aead ciphers (see [1], [2]).
+//
+// [1]https://github.com/openssl/openssl/blob/eb52450f5151e8e78743ab05de21a344823316f5/crypto/evp/evp_enc.c#L1427
+// [2]https://github.com/openssl/openssl/blob/cac250755efd0c40cc6127a0e4baceb8d226c7e3/providers/implementations/include/prov/ciphercommon_aead.h#L30
+util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> NewContext(
+    const util::SecretData& key, absl::string_view iv, bool encryption) {
+  internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
+  if (context == nullptr) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "EVP_CIPHER_CTX_new failed");
+  }
+  util::StatusOr<const EVP_CIPHER*> cipher =
+      internal::GetAesGcmCipherForKeySize(key.size());
+  if (!cipher.ok()) {
+    return cipher.status();
+  }
+  if (EVP_CipherInit_ex(context.get(), *cipher, /*impl=*/nullptr,
+                        reinterpret_cast<const uint8_t*>(key.data()),
+                        /*iv=*/nullptr, /*enc=*/1) <= 0) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Context initialization failed");
+  }
+  util::Status res =
+      SetIvAndDirection(context.get(), iv, /*encryption=*/encryption);
+  if (!res.ok()) {
+    return res;
+  }
+  return std::move(context);
+}
+#endif
+
 }  // namespace
 
-util::StatusOr<std::unique_ptr<CordAead> > CordAesGcmBoringSsl::New(
-    util::SecretData key_value) {
+util::StatusOr<std::unique_ptr<CordAead>> CordAesGcmBoringSsl::New(
+    const util::SecretData& key_value) {
   util::StatusOr<const EVP_CIPHER*> cipher =
       internal::GetAesGcmCipherForKeySize(key_value.size());
   if (!cipher.ok()) {
     return cipher.status();
   }
 
-  internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
-  // Initialize the cipher now to have some precomputations on the key. The
-  // direction (enc/dec) is not important since it will be overwritten later.
-  if (EVP_CipherInit_ex(context.get(), *cipher, /*engine=*/nullptr,
+  internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context(EVP_CIPHER_CTX_new());
+  // Initialize a partial context for the cipher to allow OpenSSL/BoringSSL
+  // making some precomputations on the key. Encrypt and Decrypt will try making
+  // a copy of this context to avoid doing the same initializations again and to
+  // guarantee thread safety.
+  //
+  // NOTE: It doesn't matter at this point if we set the direction to encryption
+  // or decryption, it will be overwritten later any time we call
+  // EVP_CipherInit_ex.
+  if (EVP_CipherInit_ex(partial_context.get(), *cipher, /*engine=*/nullptr,
                         reinterpret_cast<const uint8_t*>(&key_value[0]),
                         /*iv=*/nullptr, /*enc=*/1) <= 0) {
     return util::Status(absl::StatusCode::kInternal,
                         "Context initialization failed");
   }
 
-  std::unique_ptr<CordAead> aead =
-      absl::WrapUnique(new CordAesGcmBoringSsl(std::move(context)));
+  std::unique_ptr<CordAead> aead = absl::WrapUnique(
+      new CordAesGcmBoringSsl(std::move(partial_context), key_value));
   return std::move(aead);
 }
 
@@ -96,18 +164,21 @@
     absl::Cord plaintext, absl::Cord associated_data) const {
   std::string iv = subtle::Random::GetRandomBytes(kIvSizeInBytes);
 
-  internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
-  EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
-  util::Status res = SetIv(context.get(), iv, /*encryption=*/true);
-  if (!res.ok()) {
-    return res;
+#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L
+  util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+      NewContextFromPartial(partial_context_.get(), iv, /*encryption=*/true);
+#else
+  util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+      NewContext(key_, iv, /*encryption=*/true);
+#endif
+  if (!context.ok()) {
+    return context.status();
   }
 
   int len = 0;
   // Process AAD.
   for (auto ad_chunk : associated_data.Chunks()) {
-    if (!EVP_EncryptUpdate(context.get(), /*out=*/nullptr, &len,
+    if (!EVP_EncryptUpdate(context->get(), /*out=*/nullptr, &len,
                            reinterpret_cast<const uint8_t*>(ad_chunk.data()),
                            ad_chunk.size())) {
       return util::Status(absl::StatusCode::kInternal, "Encryption failed");
@@ -124,7 +195,7 @@
 
   for (auto plaintext_chunk : plaintext.Chunks()) {
     if (!EVP_EncryptUpdate(
-            context.get(),
+            context->get(),
             reinterpret_cast<uint8_t*>(&(buffer[ciphertext_buffer_offset])),
             &len, reinterpret_cast<const uint8_t*>(plaintext_chunk.data()),
             plaintext_chunk.size())) {
@@ -132,13 +203,14 @@
     }
     ciphertext_buffer_offset += plaintext_chunk.size();
   }
-  if (!EVP_EncryptFinal_ex(context.get(), nullptr, &len)) {
+  if (!EVP_EncryptFinal_ex(context->get(), nullptr, &len)) {
     return util::Status(absl::StatusCode::kInternal, "Encryption failed");
   }
 
   std::string tag;
   subtle::ResizeStringUninitialized(&tag, kTagSizeInBytes);
-  if (!EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_GCM_GET_TAG, kTagSizeInBytes,
+  if (!EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_GCM_GET_TAG,
+                           kTagSizeInBytes,
                            reinterpret_cast<uint8_t*>(&tag[0]))) {
     return util::Status(absl::StatusCode::kInternal, "Encryption failed");
   }
@@ -157,23 +229,26 @@
     return util::Status(absl::StatusCode::kInternal, "Ciphertext too short");
   }
 
-  // First bytes contain IV.
+  // First bytes contain the IV.
   std::string iv = std::string(ciphertext.Subcord(0, kIvSizeInBytes));
   absl::Cord raw_ciphertext = ciphertext.Subcord(
       kIvSizeInBytes, ciphertext.size() - kIvSizeInBytes - kTagSizeInBytes);
 
-  internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
-  EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
-  util::Status res = SetIv(context.get(), iv, /*encryption=*/false);
-  if (!res.ok()) {
-    return res;
+#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L
+  util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+      NewContextFromPartial(partial_context_.get(), iv, /*encryption=*/false);
+#else
+  util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+      NewContext(key_, iv, /*encryption=*/false);
+#endif
+  if (!context.ok()) {
+    return context.status();
   }
 
   int len = 0;
   // Process associated data.
   for (auto ad_chunk : associated_data.Chunks()) {
-    if (!EVP_DecryptUpdate(context.get(), nullptr, &len,
+    if (!EVP_DecryptUpdate(context->get(), /*out=*/nullptr, &len,
                            reinterpret_cast<const uint8_t*>(ad_chunk.data()),
                            ad_chunk.size())) {
       return util::Status(absl::StatusCode::kInternal, "Decryption failed");
@@ -192,7 +267,7 @@
       });
 
   for (auto ct_chunk : raw_ciphertext.Chunks()) {
-    if (!EVP_DecryptUpdate(context.get(),
+    if (!EVP_DecryptUpdate(context->get(),
                            reinterpret_cast<uint8_t*>(
                                &plaintext_buffer[plaintext_buffer_offset]),
                            &len,
@@ -207,13 +282,13 @@
   std::string tag = std::string(
       ciphertext.Subcord(ciphertext.size() - kTagSizeInBytes, kTagSizeInBytes));
 
-  if (!EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_GCM_SET_TAG, kTagSizeInBytes,
-                           &tag[0])) {
+  if (!EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_GCM_SET_TAG,
+                           kTagSizeInBytes, &tag[0])) {
     return util::Status(absl::StatusCode::kInternal,
                         "Could not set authentication tag");
   }
   // Verify authentication tag.
-  if (!EVP_DecryptFinal_ex(context.get(), nullptr, &len)) {
+  if (!EVP_DecryptFinal_ex(context->get(), nullptr, &len)) {
     return util::Status(absl::StatusCode::kInternal, "Authentication failed");
   }
   return result;
diff --git a/cc/aead/internal/cord_aes_gcm_boringssl.h b/cc/aead/internal/cord_aes_gcm_boringssl.h
index 9dcd288..273b523 100644
--- a/cc/aead/internal/cord_aes_gcm_boringssl.h
+++ b/cc/aead/internal/cord_aes_gcm_boringssl.h
@@ -20,12 +20,10 @@
 #include <memory>
 #include <utility>
 
-#include "absl/strings/string_view.h"
 #include "openssl/evp.h"
 #include "tink/aead/cord_aead.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/util/secret_data.h"
-#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
 namespace crypto {
@@ -35,7 +33,7 @@
 class CordAesGcmBoringSsl : public CordAead {
  public:
   static crypto::tink::util::StatusOr<std::unique_ptr<CordAead>> New(
-      util::SecretData key_value);
+      const util::SecretData& key_value);
 
   crypto::tink::util::StatusOr<absl::Cord> Encrypt(
       absl::Cord plaintext, absl::Cord associated_data) const override;
@@ -44,10 +42,15 @@
       absl::Cord ciphertext, absl::Cord associated_data) const override;
 
  private:
-  explicit CordAesGcmBoringSsl(internal::SslUniquePtr<EVP_CIPHER_CTX> context)
-      : context_(std::move(context)) {}
+  explicit CordAesGcmBoringSsl(
+      internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context,
+      const util::SecretData& key)
+      : partial_context_(std::move(partial_context)), key_(key) {}
 
-  internal::SslUniquePtr<EVP_CIPHER_CTX> context_;
+  // Partially-initialized EVP_CIPHER_CTX context that is copied for every
+  // Encrypt/Decrypt operation.
+  internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context_;
+  util::SecretData key_;
 };
 
 }  // namespace internal
diff --git a/cc/aead/internal/ssl_aead.cc b/cc/aead/internal/ssl_aead.cc
index 4b62a8a..c8b2629 100644
--- a/cc/aead/internal/ssl_aead.cc
+++ b/cc/aead/internal/ssl_aead.cc
@@ -44,36 +44,10 @@
 
 ABSL_CONST_INIT const int kXchacha20Poly1305TagSizeInBytes = 16;
 ABSL_CONST_INIT const int kAesGcmTagSizeInBytes = 16;
+ABSL_CONST_INIT const int kAesGcmSivTagSizeInBytes = 16;
 
 namespace {
 
-// Sets `iv` to the given `context`, as well as the "direction"
-// (encrypt/decrypt) based on `encryption`.
-util::Status SetIvAndDirection(EVP_CIPHER_CTX *context, absl::string_view iv,
-                               bool encryption) {
-  if (context == nullptr) {
-    return util::Status(absl::StatusCode::kInternal,
-                        "Context must not be null");
-  }
-  const int encryption_flag = encryption ? 1 : 0;
-  // Set the size for IV first, then set the IV bytes.
-  if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_AEAD_SET_IVLEN, iv.size(),
-                          /*ptr=*/nullptr) <= 0) {
-    return util::Status(absl::StatusCode::kInternal, "Setting IV size failed");
-  }
-  if (EVP_CipherInit_ex(context, /*cipher=*/nullptr, /*impl=*/nullptr,
-                        /*key=*/nullptr,
-                        reinterpret_cast<const uint8_t *>(iv.data()),
-                        /*enc=*/encryption_flag) <= 0) {
-    return util::Status(
-        absl::StatusCode::kInternal,
-        absl::StrCat("Failed to initialize the context with IV of size ",
-                     iv.size(), " and for ",
-                     encryption ? "encryption" : "decryption"));
-  }
-  return util::OkStatus();
-}
-
 // Encrypts/Decrypts `data` and writes the result into `out`. The direction
 // (encrypt/decrypt) is given by `context`. `out` is assumed to be large enough
 // to hold the encrypted/decrypted content.
@@ -108,9 +82,9 @@
 
 class OpenSslOneShotAeadImpl : public SslOneShotAead {
  public:
-  OpenSslOneShotAeadImpl(internal::SslUniquePtr<EVP_CIPHER_CTX> context,
-                         size_t tag_size)
-      : context_(std::move(context)), tag_size_(tag_size) {}
+  explicit OpenSslOneShotAeadImpl(const util::SecretData &key,
+                                  const EVP_CIPHER *cipher, size_t tag_size)
+      : key_(key), cipher_(cipher), tag_size_(tag_size) {}
 
   util::StatusOr<int64_t> Encrypt(absl::string_view plaintext,
                                   absl::string_view associated_data,
@@ -140,25 +114,15 @@
                        associated_data.size()));
     }
 
-    // For thread safety we copy the context and only then set the IV. This
-    // allows to allocate an AesGcmBoringSsl cipher, and initialize the context
-    // to force precomputation on the key, and only then set a different IV
-    // for each call to `Encrypt`.
-    internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
-    // This makes a copy of the `cipher_data` field of the context too, which
-    // contains the key material and IV (see
-    // https://github.com/google/boringssl/blob/master/crypto/fipsmodule/cipher/cipher.c#L116).
-    EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
-    util::Status res =
-        SetIvAndDirection(context.get(), iv, /*encryption=*/true);
-    if (!res.ok()) {
-      return res;
+    util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+        GetContext(iv, /*encryption=*/true);
+    if (!context.ok()) {
+      return context.status();
     }
 
     // Set the associated data.
     int len = 0;
-    if (EVP_EncryptUpdate(context.get(), /*out=*/nullptr, &len,
+    if (EVP_EncryptUpdate(context->get(), /*out=*/nullptr, &len,
                           reinterpret_cast<const uint8_t *>(ad.data()),
                           ad.size()) <= 0) {
       return util::Status(absl::StatusCode::kInternal,
@@ -166,18 +130,18 @@
     }
 
     util::StatusOr<int64_t> raw_ciphertext_bytes =
-        UpdateCipher(context.get(), plaintext_data, out);
+        UpdateCipher(context->get(), plaintext_data, out);
     if (!raw_ciphertext_bytes.ok()) {
       return raw_ciphertext_bytes.status();
     }
 
-    if (EVP_EncryptFinal_ex(context.get(), /*out=*/nullptr, &len) <= 0) {
+    if (EVP_EncryptFinal_ex(context->get(), /*out=*/nullptr, &len) <= 0) {
       return util::Status(absl::StatusCode::kInternal, "Finalization failed");
     }
 
     // Write the tag after the ciphertext.
     absl::Span<char> tag = out.subspan(*raw_ciphertext_bytes, tag_size_);
-    if (EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_AEAD_GET_TAG, tag_size_,
+    if (EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_AEAD_GET_TAG, tag_size_,
                             reinterpret_cast<uint8_t *>(tag.data())) <= 0) {
       return util::Status(absl::StatusCode::kInternal, "Failed to get the tag");
     }
@@ -218,18 +182,15 @@
                        associated_data.size()));
     }
 
-    internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
-    EVP_CIPHER_CTX_copy(context.get(), context_.get());
-
-    util::Status res =
-        SetIvAndDirection(context.get(), iv, /*encryption=*/false);
-    if (!res.ok()) {
-      return res;
+    util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context =
+        GetContext(iv, /*encryption=*/false);
+    if (!context.ok()) {
+      return context.status();
     }
 
     int len = 0;
     // Add the associated data.
-    if (EVP_DecryptUpdate(context.get(), /*out=*/nullptr, &len,
+    if (EVP_DecryptUpdate(context->get(), /*out=*/nullptr, &len,
                           reinterpret_cast<const uint8_t *>(ad.data()),
                           ad.size()) <= 0) {
       return util::Status(absl::StatusCode::kInternal,
@@ -246,7 +207,7 @@
     auto tag = std::string(ciphertext.substr(raw_ciphertext_size, tag_size_));
 
     // Set the tag.
-    if (EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_AEAD_SET_TAG, tag_size_,
+    if (EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_AEAD_SET_TAG, tag_size_,
                             reinterpret_cast<uint8_t *>(&tag[0])) <= 0) {
       return util::Status(absl::StatusCode::kInternal,
                           "Could not set authentication tag");
@@ -267,12 +228,12 @@
         absl::MakeCleanup([out] { OPENSSL_cleanse(out.data(), out.size()); });
 
     util::StatusOr<int64_t> written_bytes =
-        UpdateCipher(context.get(), raw_ciphertext, out_buffer);
+        UpdateCipher(context->get(), raw_ciphertext, out_buffer);
     if (!written_bytes.ok()) {
       return written_bytes.status();
     }
 
-    if (!EVP_DecryptFinal_ex(context.get(), /*out=*/nullptr, &len)) {
+    if (!EVP_DecryptFinal_ex(context->get(), /*out=*/nullptr, &len)) {
       return util::Status(absl::StatusCode::kInternal, "Authentication failed");
     }
 
@@ -281,20 +242,70 @@
     return *written_bytes;
   }
 
+  int64_t CiphertextSize(int64_t plaintext_length) const override {
+    return plaintext_length + tag_size_;
+  }
+
+  int64_t PlaintextSize(int64_t ciphertext_length) const override {
+    if (ciphertext_length < tag_size_) {
+      return 0;
+    }
+    return ciphertext_length - tag_size_;
+  }
+
  private:
-  const internal::SslUniquePtr<EVP_CIPHER_CTX> context_;
+  // Returns a new EVP_CIPHER_CTX for encryption (`ecryption` == true) or
+  // decryption (`encryption` == false).
+  util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> GetContext(
+      absl::string_view iv, bool encryption) const {
+    internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
+    if (context == nullptr) {
+      return util::Status(absl::StatusCode::kInternal,
+                          "EVP_CIPHER_CTX_new failed");
+    }
+    const int encryption_flag = encryption ? 1 : 0;
+    if (EVP_CipherInit_ex(context.get(), cipher_, /*impl=*/nullptr,
+                          /*key=*/nullptr, /*iv=*/nullptr,
+                          encryption_flag) <= 0) {
+      return util::Status(
+          absl::StatusCode::kInternal,
+          absl::StrCat("Failed initializializing context for ",
+                       encryption ? "encryption" : "decryption"));
+    }
+    // Set the size for IV first, then set the IV bytes.
+    if (EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_AEAD_SET_IVLEN, iv.size(),
+                            /*ptr=*/nullptr) <= 0) {
+      return util::Status(
+          absl::StatusCode::kInternal,
+          absl::StrCat("Failed stting size of the IV to ", iv.size()));
+    }
+    if (EVP_CipherInit_ex(context.get(), /*cipher=*/nullptr, /*impl=*/nullptr,
+                          reinterpret_cast<const uint8_t *>(key_.data()),
+                          reinterpret_cast<const uint8_t *>(iv.data()),
+                          encryption_flag) <= 0) {
+      return util::Status(
+          absl::StatusCode::kInternal,
+          absl::StrCat("Failed to set key of size ", key_.size(),
+                       "and IV of size ", iv.size()));
+    }
+
+    return std::move(context);
+  }
+
+  const util::SecretData key_;
+  const EVP_CIPHER *cipher_;
   const size_t tag_size_;
 };
 
 #ifdef OPENSSL_IS_BORINGSSL
 
-// Implementation of the one-shot AEAD cypter. This is purposely internal to an
-// anonymous namespace to disallow direct use of this class other than through
-// the Create* functions below.
+// Implementation of the one-shot AEAD cypter. This is purposely internal to
+// an anonymous namespace to disallow direct use of this class other than
+// through the Create* functions below.
 class BoringSslOneShotAeadImpl : public SslOneShotAead {
  public:
-  BoringSslOneShotAeadImpl(internal::SslUniquePtr<EVP_AEAD_CTX> context,
-                           size_t tag_size)
+  explicit BoringSslOneShotAeadImpl(
+      internal::SslUniquePtr<EVP_AEAD_CTX> context, size_t tag_size)
       : context_(std::move(context)), tag_size_(tag_size) {}
 
   util::StatusOr<int64_t> Encrypt(absl::string_view plaintext,
@@ -388,70 +399,24 @@
     return out_len;
   }
 
+  int64_t CiphertextSize(int64_t plaintext_length) const override {
+    return plaintext_length + tag_size_;
+  }
+
+  int64_t PlaintextSize(int64_t ciphertext_length) const override {
+    if (ciphertext_length < tag_size_) {
+      return 0;
+    }
+    return ciphertext_length - tag_size_;
+  }
+
+ private:
   const internal::SslUniquePtr<EVP_AEAD_CTX> context_;
   const size_t tag_size_;
 };
 
 #endif
 
-#ifdef OPENSSL_IS_BORINGSSL
-// Always use the BoringSSL APIs when available.
-using SslOneShotAeadImpl = BoringSslOneShotAeadImpl;
-#else
-using SslOneShotAeadImpl = OpenSslOneShotAeadImpl;
-#endif
-
-// One shot implementing AES-GCM.
-class SslAesGcmOneShotAead : public SslOneShotAeadImpl {
- public:
-#ifdef OPENSSL_IS_BORINGSSL
-  explicit SslAesGcmOneShotAead(internal::SslUniquePtr<EVP_AEAD_CTX> context)
-      : BoringSslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#else
-  explicit SslAesGcmOneShotAead(internal::SslUniquePtr<EVP_CIPHER_CTX> context)
-      : SslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#endif
-
-  int64_t CiphertextSize(int64_t plaintext_length) const override {
-    return plaintext_length + kAesGcmTagSizeInBytes;
-  }
-
-  int64_t PlaintextSize(int64_t ciphertext_length) const override {
-    if (ciphertext_length < kAesGcmTagSizeInBytes) {
-      return 0;
-    }
-    return ciphertext_length - kAesGcmTagSizeInBytes;
-  }
-};
-
-// One shot implementing AES-GCM-SIV.
-using SslAesGcmSivOneShotAead = SslAesGcmOneShotAead;
-
-// One shot implementing XCHACHA-POLY-1305.
-class SslXchacha20Poly1305OneShotAead : public SslOneShotAeadImpl {
- public:
-#ifdef OPENSSL_IS_BORINGSSL
-  explicit SslXchacha20Poly1305OneShotAead(
-      internal::SslUniquePtr<EVP_AEAD_CTX> context)
-      : BoringSslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#else
-  explicit SslXchacha20Poly1305OneShotAead(
-      internal::SslUniquePtr<EVP_CIPHER_CTX> context)
-      : SslOneShotAeadImpl(std::move(context), kAesGcmTagSizeInBytes) {}
-#endif
-
-  int64_t CiphertextSize(int64_t plaintext_length) const override {
-    return plaintext_length + kXchacha20Poly1305TagSizeInBytes;
-  }
-
-  int64_t PlaintextSize(int64_t ciphertext_length) const override {
-    if (ciphertext_length < kXchacha20Poly1305TagSizeInBytes) {
-      return 0;
-    }
-    return ciphertext_length - kXchacha20Poly1305TagSizeInBytes;
-  }
-};
-
 }  // namespace
 
 util::StatusOr<std::unique_ptr<SslOneShotAead>> CreateAesGcmOneShotCrypter(
@@ -470,6 +435,8 @@
         absl::StatusCode::kInternal,
         absl::StrCat("EVP_AEAD_CTX_new failed: ", internal::GetSslErrors()));
   }
+  return {absl::make_unique<BoringSslOneShotAeadImpl>(std::move(context),
+                                                      kAesGcmTagSizeInBytes)};
 #else
   util::StatusOr<const EVP_CIPHER *> aead_cipher =
       GetAesGcmCipherForKeySize(key.size());
@@ -477,25 +444,9 @@
     return aead_cipher.status();
   }
 
-  internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new());
-
-  if (context == nullptr) {
-    return util::Status(absl::StatusCode::kInternal,
-                        "EVP_CIPHER_CTX_new failed");
-  }
-
-  // Initialize the context for the cipher having OpenSSL to make some
-  // precomputations on the key. It doesn't matter at this point if we set
-  // encryption or decryption, it will be overwritten later on anyways any
-  // time we call EVP_CipherInit_ex.
-  if (EVP_CipherInit_ex(context.get(), *aead_cipher, /*impl=*/nullptr,
-                        reinterpret_cast<const uint8_t *>(&key[0]),
-                        /*iv=*/nullptr, /*enc=*/1) <= 0) {
-    return util::Status(absl::StatusCode::kInternal,
-                        "Context initialization failed");
-  }
+  return absl::make_unique<OpenSslOneShotAeadImpl>(key, *aead_cipher,
+                                                   kAesGcmTagSizeInBytes);
 #endif
-  return {absl::make_unique<SslAesGcmOneShotAead>(std::move(context))};
 }
 
 util::StatusOr<std::unique_ptr<SslOneShotAead>> CreateAesGcmSivOneShotCrypter(
@@ -513,7 +464,8 @@
                         absl::StrCat("EVP_AEAD_CTX_new initialization Failed: ",
                                      internal::GetSslErrors()));
   }
-  return {absl::make_unique<SslAesGcmSivOneShotAead>(std::move(context))};
+  return {absl::make_unique<BoringSslOneShotAeadImpl>(
+      std::move(context), kAesGcmSivTagSizeInBytes)};
 #else
   return util::Status(absl::StatusCode::kUnimplemented,
                       "AES-GCM-SIV is unimplemented for OpenSSL");
@@ -538,8 +490,8 @@
                         absl::StrCat("EVP_AEAD_CTX_new initialization Failed: ",
                                      internal::GetSslErrors()));
   }
-  return {
-      absl::make_unique<SslXchacha20Poly1305OneShotAead>(std::move(context))};
+  return {absl::make_unique<BoringSslOneShotAeadImpl>(
+      std::move(context), kXchacha20Poly1305TagSizeInBytes)};
 #else
   return util::Status(absl::StatusCode::kUnimplemented,
                       "Xchacha20-Poly1305 is unimplemented for OpenSSL");
diff --git a/cc/aead/internal/ssl_aead.h b/cc/aead/internal/ssl_aead.h
index 6ff877a..3fdc158 100644
--- a/cc/aead/internal/ssl_aead.h
+++ b/cc/aead/internal/ssl_aead.h
@@ -28,9 +28,10 @@
 namespace tink {
 namespace internal {
 
+// Tag sizes.
 ABSL_CONST_INIT extern const int kXchacha20Poly1305TagSizeInBytes;
-// Tag size for both AES-GCM and AES-GCM-SIV.
 ABSL_CONST_INIT extern const int kAesGcmTagSizeInBytes;
+ABSL_CONST_INIT extern const int kAesGcmSivTagSizeInBytes;
 
 // Interface for one-shot AEAD crypters.
 class SslOneShotAead {
diff --git a/cc/aead/internal/ssl_aead_large_inputs_test.cc b/cc/aead/internal/ssl_aead_large_inputs_test.cc
index 1ca2400..9427b78 100644
--- a/cc/aead/internal/ssl_aead_large_inputs_test.cc
+++ b/cc/aead/internal/ssl_aead_large_inputs_test.cc
@@ -33,6 +33,7 @@
 #include "tink/aead/internal/ssl_aead.h"
 #include "tink/config/tink_fips.h"
 #include "tink/internal/ssl_util.h"
+#include "tink/internal/util.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/statusor.h"
@@ -87,6 +88,9 @@
 
 // Encrypt/decrypt with an input larger than a MAX int.
 TEST_P(SslOneShotAeadLargeInputsTest, EncryptDecryptLargeInput) {
+  if (IsWindows()) {
+    GTEST_SKIP() << "Skipping on Windows: this currently times out";
+  }
   const int64_t buff_size =
       static_cast<int64_t>(std::numeric_limits<int>::max()) + 1024;
   std::string large_input(buff_size, '0');
diff --git a/cc/aead/internal/ssl_aead_test.cc b/cc/aead/internal/ssl_aead_test.cc
index 375e9cd..23fd35b 100644
--- a/cc/aead/internal/ssl_aead_test.cc
+++ b/cc/aead/internal/ssl_aead_test.cc
@@ -34,7 +34,7 @@
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "tink/aead/internal/wycheproof_aead.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/internal/ssl_util.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/util/secret_data.h"
@@ -524,7 +524,7 @@
 }
 
 TEST(SslOneShotAeadTest, AesGcmTestFipsOnly) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (IsFipsModeEnabled() && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test should not run in FIPS mode when BoringCrypto is "
                     "unavailable.";
   }
@@ -539,7 +539,7 @@
 }
 
 TEST(SslOneShotAeadTest, AesGcmTestTestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!IsFipsModeEnabled() || IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
diff --git a/cc/aead/internal/zero_copy_aead_wrapper.cc b/cc/aead/internal/zero_copy_aead_wrapper.cc
index 3ed95cc..b1e8114 100644
--- a/cc/aead/internal/zero_copy_aead_wrapper.cc
+++ b/cc/aead/internal/zero_copy_aead_wrapper.cc
@@ -60,7 +60,7 @@
       absl::string_view ciphertext,
       absl::string_view associated_data) const override;
 
-  ~ZeroCopyAeadSetWrapper() override {}
+  ~ZeroCopyAeadSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<ZeroCopyAead>> aead_set_;
diff --git a/cc/aead/internal/zero_copy_aead_wrapper.h b/cc/aead/internal/zero_copy_aead_wrapper.h
index 1964a6d..beca0aa 100644
--- a/cc/aead/internal/zero_copy_aead_wrapper.h
+++ b/cc/aead/internal/zero_copy_aead_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_AEAD_INTERNAL_ZERO_COPY_AEAD_WRAPPER_H_
 #define TINK_AEAD_INTERNAL_ZERO_COPY_AEAD_WRAPPER_H_
 
+#include <memory>
+
 #include "tink/aead.h"
 #include "tink/aead/internal/zero_copy_aead.h"
 #include "tink/primitive_set.h"
@@ -46,4 +48,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_AEAD_AEAD_WRAPPER_H_
+#endif  // TINK_AEAD_INTERNAL_ZERO_COPY_AEAD_WRAPPER_H_
diff --git a/cc/aead/internal/zero_copy_aead_wrapper_test.cc b/cc/aead/internal/zero_copy_aead_wrapper_test.cc
index 489b7de..b4e2218 100644
--- a/cc/aead/internal/zero_copy_aead_wrapper_test.cc
+++ b/cc/aead/internal/zero_copy_aead_wrapper_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/aead/internal/zero_copy_aead_wrapper.h"
 
+#include <cstring>
 #include <memory>
 #include <string>
 #include <utility>
diff --git a/cc/aead/kms_envelope_aead.cc b/cc/aead/kms_envelope_aead.cc
index 887c253..8f36b0d 100644
--- a/cc/aead/kms_envelope_aead.cc
+++ b/cc/aead/kms_envelope_aead.cc
@@ -27,6 +27,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/aead/internal/aead_util.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -60,6 +61,11 @@
 util::StatusOr<std::unique_ptr<Aead>> KmsEnvelopeAead::New(
     const google::crypto::tink::KeyTemplate& dek_template,
     std::unique_ptr<Aead> remote_aead) {
+  if (!internal::IsSupportedKmsEnvelopeAeadDekKeyType(
+          dek_template.type_url())) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "unsupported key type");
+  }
   if (remote_aead == nullptr) {
     return util::Status(absl::StatusCode::kInvalidArgument,
                         "remote_aead must be non-null");
diff --git a/cc/aead/kms_envelope_aead.h b/cc/aead/kms_envelope_aead.h
index 1fd9d89..acfe066 100644
--- a/cc/aead/kms_envelope_aead.h
+++ b/cc/aead/kms_envelope_aead.h
@@ -58,7 +58,7 @@
       absl::string_view ciphertext,
       absl::string_view associated_data) const override;
 
-  ~KmsEnvelopeAead() override {}
+  ~KmsEnvelopeAead() override = default;
 
  private:
   KmsEnvelopeAead(const google::crypto::tink::KeyTemplate& dek_template,
diff --git a/cc/aead/kms_envelope_aead_key_manager.h b/cc/aead/kms_envelope_aead_key_manager.h
index 04b9e31..69c99d1 100644
--- a/cc/aead/kms_envelope_aead_key_manager.h
+++ b/cc/aead/kms_envelope_aead_key_manager.h
@@ -25,6 +25,7 @@
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "tink/aead.h"
+#include "tink/aead/internal/aead_util.h"
 #include "tink/core/key_type_manager.h"
 #include "tink/core/template_util.h"
 #include "tink/internal/fips_utils.h"
@@ -75,6 +76,11 @@
       return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
                                         "Missing kek_uri.");
     }
+    if (!internal::IsSupportedKmsEnvelopeAeadDekKeyType(
+            format.dek_template().type_url())) {
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "unsupported dek key type");
+    }
     return util::OkStatus();
   }
 
diff --git a/cc/aead/kms_envelope_aead_key_manager_test.cc b/cc/aead/kms_envelope_aead_key_manager_test.cc
index 7b32aa1..4cee9c1 100644
--- a/cc/aead/kms_envelope_aead_key_manager_test.cc
+++ b/cc/aead/kms_envelope_aead_key_manager_test.cc
@@ -26,13 +26,16 @@
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "tink/aead.h"
+#include "tink/aead/aead_config.h"
 #include "tink/aead/aead_key_templates.h"
 #include "tink/aead/aes_eax_key_manager.h"
 #include "tink/aead/kms_envelope_aead.h"
 #include "tink/kms_client.h"
 #include "tink/kms_clients.h"
+#include "tink/mac/mac_key_templates.h"
 #include "tink/registry.h"
 #include "tink/subtle/aead_test_util.h"
+#include "tink/util/fake_kms_client.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
@@ -46,8 +49,9 @@
 using ::crypto::tink::test::DummyAead;
 using ::crypto::tink::test::DummyKmsClient;
 using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
 using ::crypto::tink::test::StatusIs;
-using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::KeyTemplate;
 using ::google::crypto::tink::KmsEnvelopeAeadKey;
 using ::google::crypto::tink::KmsEnvelopeAeadKeyFormat;
 using ::testing::Eq;
@@ -118,7 +122,15 @@
 
 TEST(KmsEnvelopeAeadKeyManagerTest, ValidateKeyFormatNoTemplate) {
   KmsEnvelopeAeadKeyFormat key_format;
-  *key_format.mutable_dek_template() = AeadKeyTemplates::Aes128Eax();
+  key_format.set_kek_uri("Some uri");
+  EXPECT_THAT(KmsEnvelopeAeadKeyManager().ValidateKeyFormat(key_format),
+              Not(IsOk()));
+}
+
+TEST(KmsEnvelopeAeadKeyManagerTest, ValidateKeyFormatInvalidDekTemplate) {
+  KmsEnvelopeAeadKeyFormat key_format;
+  key_format.set_kek_uri("Some uri");
+  *key_format.mutable_dek_template() = MacKeyTemplates::HmacSha256();
   EXPECT_THAT(KmsEnvelopeAeadKeyManager().ValidateKeyFormat(key_format),
               Not(IsOk()));
 }
@@ -229,6 +241,54 @@
               IsOk());
 }
 
+class KmsEnvelopeAeadKeyManagerDekTemplatesTest
+    : public testing::TestWithParam<KeyTemplate> {
+  void SetUp() override { ASSERT_THAT(AeadConfig::Register(), IsOk()); }
+};
+
+TEST_P(KmsEnvelopeAeadKeyManagerDekTemplatesTest, EncryptDecryp) {
+  util::StatusOr<std::string> kek_uri_result =
+      test::FakeKmsClient::CreateFakeKeyUri();
+  ASSERT_THAT(kek_uri_result, IsOk());
+  std::string kek_uri = kek_uri_result.value();
+  util::Status register_fake_kms_client_status =
+      test::FakeKmsClient::RegisterNewClient(kek_uri, /*credentials_path=*/"");
+  ASSERT_THAT(register_fake_kms_client_status, IsOk());
+
+  KeyTemplate dek_template = GetParam();
+  KeyTemplate env_template =
+      AeadKeyTemplates::KmsEnvelopeAead(kek_uri, dek_template);
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      KeysetHandle::GenerateNew(env_template);
+  ASSERT_THAT(handle, IsOk());
+  util::StatusOr<std::unique_ptr<Aead>> envelope_aead =
+      (*handle)->GetPrimitive<Aead>();
+  ASSERT_THAT(envelope_aead, IsOk());
+
+  std::string plaintext = "plaintext";
+  std::string associated_data = "associated_data";
+  util::StatusOr<std::string> ciphertext =
+      (*envelope_aead)->Encrypt(plaintext, associated_data);
+  ASSERT_THAT(ciphertext, IsOk());
+  util::StatusOr<std::string> decrypted =
+      (*envelope_aead)->Decrypt(ciphertext.value(), associated_data);
+  EXPECT_THAT(decrypted, IsOkAndHolds(plaintext));
+
+  std::string invalid_associated_data = "invalid_associated_data";
+  util::StatusOr<std::string> decrypted_with_invalid_associated_data =
+      (*envelope_aead)->Decrypt(ciphertext.value(), invalid_associated_data);
+  EXPECT_THAT(decrypted_with_invalid_associated_data.status(), Not(IsOk()));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    KmsEnvelopeAeadKeyManagerDekTemplatesTest,
+    KmsEnvelopeAeadKeyManagerDekTemplatesTest,
+    testing::Values(AeadKeyTemplates::Aes128Gcm(),
+                    AeadKeyTemplates::Aes256Gcm(),
+                    AeadKeyTemplates::Aes128CtrHmacSha256(),
+                    AeadKeyTemplates::Aes128Eax(),
+                    AeadKeyTemplates::Aes128GcmNoPrefix()));
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/kms_envelope_aead_test.cc b/cc/aead/kms_envelope_aead_test.cc
index 673a1b5..ef8395d 100644
--- a/cc/aead/kms_envelope_aead_test.cc
+++ b/cc/aead/kms_envelope_aead_test.cc
@@ -21,15 +21,18 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/base/internal/endian.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
+#include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/aead/aead_config.h"
 #include "tink/aead/aead_key_templates.h"
+#include "tink/keyset_handle.h"
 #include "tink/mac/mac_key_templates.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
@@ -37,147 +40,249 @@
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 #include "proto/aes_gcm.pb.h"
+#include "tink/internal/ssl_util.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
-using crypto::tink::test::DummyAead;
-using crypto::tink::test::IsOk;
-using crypto::tink::test::StatusIs;
-using testing::HasSubstr;
+using ::crypto::tink::Aead;
+using ::crypto::tink::test::DummyAead;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyTemplate;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::SizeIs;
+using ::testing::Test;
 
+constexpr int kEncryptedDekPrefixSize = 4;
+constexpr absl::string_view kRemoteAeadName = "kms-backed-aead";
 
-TEST(KmsEnvelopeAeadTest, BasicEncryptDecrypt) {
-  EXPECT_THAT(AeadConfig::Register(), IsOk());
+class KmsEnvelopeAeadTest : public Test {
+ protected:
+  void TearDown() override { Registry::Reset(); }
+};
 
-  auto dek_template = AeadKeyTemplates::Aes128Eax();
-  std::string remote_aead_name = "kms-backed-aead";
-  auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
+TEST_F(KmsEnvelopeAeadTest, EncryptDecryptSucceed) {
+  ASSERT_THAT(AeadConfig::Register(), IsOk());
 
-  auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
-  EXPECT_THAT(aead_result, IsOk());
-  auto aead = std::move(aead_result.value());
+  // Use an AES-128-GCM primitive as the remote one.
+  util::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm());
+  ASSERT_THAT(keyset_handle, IsOk());
+  KeyTemplate dek_template = AeadKeyTemplates::Aes128Eax();
+  util::StatusOr<std::unique_ptr<Aead>> remote_aead =
+      (*keyset_handle)->GetPrimitive<Aead>();
+
+  util::StatusOr<std::unique_ptr<Aead>> envelope_aead =
+      KmsEnvelopeAead::New(dek_template, *std::move(remote_aead));
+  ASSERT_THAT(envelope_aead, IsOk());
+
   std::string message = "Some data to encrypt.";
-  std::string aad = "Some data to authenticate.";
-  auto encrypt_result = aead->Encrypt(message, aad);
-  EXPECT_THAT(encrypt_result, IsOk());
-  auto decrypt_result = aead->Decrypt(encrypt_result.value(), aad);
-  EXPECT_THAT(decrypt_result, IsOk());
-  EXPECT_EQ(decrypt_result.value(), message);
+  std::string aad = "Some associated data.";
+  util::StatusOr<std::string> encrypt_result =
+      (*envelope_aead)->Encrypt(message, aad);
+  ASSERT_THAT(encrypt_result, IsOk());
+  util::StatusOr<std::string> decrypt_result =
+      (*envelope_aead)->Decrypt(encrypt_result.value(), aad);
+  EXPECT_THAT(decrypt_result, IsOkAndHolds(message));
 }
 
-TEST(KmsEnvelopeAeadTest, NullAead) {
-  auto dek_template = AeadKeyTemplates::Aes128Eax();
-  auto aead_result = KmsEnvelopeAead::New(dek_template, nullptr);
-  EXPECT_THAT(aead_result.status(), StatusIs(absl::StatusCode::kInvalidArgument,
-                                             HasSubstr("non-null")));
-}
-
-TEST(KmsEnvelopeAeadTest, MissingDekKeyManager) {
-  Registry::Reset();
-  auto dek_template = AeadKeyTemplates::Aes128Eax();
-  std::string remote_aead_name = "kms-backed-aead";
-  auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
-  auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
-  EXPECT_THAT(aead_result.status(),
-              StatusIs(absl::StatusCode::kNotFound, HasSubstr("AesEaxKey")));
-}
-
-TEST(KmsEnvelopeAeadTest, WrongDekPrimitive) {
-  EXPECT_THAT(AeadConfig::Register(), IsOk());
-  auto dek_template = MacKeyTemplates::HmacSha256();
-  std::string remote_aead_name = "kms-backed-aead";
-  auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
-  auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
-  EXPECT_THAT(aead_result.status(),
-              StatusIs(absl::StatusCode::kInvalidArgument,
-                       HasSubstr("not among supported primitives")));
-}
-
-TEST(KmsEnvelopeAeadTest, DecryptionErrors) {
-  EXPECT_THAT(AeadConfig::Register(), IsOk());
-
-  auto dek_template = AeadKeyTemplates::Aes128Gcm();
-  std::string remote_aead_name = "kms-backed-aead";
-  auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
-
-  auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
-  EXPECT_THAT(aead_result, IsOk());
-  auto aead = std::move(aead_result.value());
-  std::string message = "Some data to encrypt.";
-  std::string aad = "Some data to authenticate.";
-  auto encrypt_result = aead->Encrypt(message, aad);
-  EXPECT_THAT(encrypt_result, IsOk());
-  auto ct = encrypt_result.value();
-
-  // Empty ciphertext.
-  auto decrypt_result = aead->Decrypt("", aad);
+TEST_F(KmsEnvelopeAeadTest, NewFailsIfReamoteAeadIsNull) {
+  KeyTemplate dek_template = AeadKeyTemplates::Aes128Eax();
   EXPECT_THAT(
-      decrypt_result.status(),
+      KmsEnvelopeAead::New(dek_template, /*remote_aead=*/nullptr).status(),
+      StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("non-null")));
+}
+
+TEST_F(KmsEnvelopeAeadTest, NewFailsIfDekKeyManagerIsNotRegistered) {
+  KeyTemplate dek_template = AeadKeyTemplates::Aes128Eax();
+  auto remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+  EXPECT_THAT(
+      KmsEnvelopeAead::New(dek_template, std::move(remote_aead)).status(),
+      StatusIs(absl::StatusCode::kNotFound, HasSubstr("AesEaxKey")));
+}
+
+TEST_F(KmsEnvelopeAeadTest, NewFailsIfUsingDekTemplateOfUnsupportedKeyType) {
+  ASSERT_THAT(AeadConfig::Register(), IsOk());
+  KeyTemplate dek_template = MacKeyTemplates::HmacSha256();
+  auto remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+  EXPECT_THAT(
+      KmsEnvelopeAead::New(dek_template, std::move(remote_aead)).status(),
+      StatusIs(absl::StatusCode::kInvalidArgument,
+               HasSubstr("unsupported key type")));
+}
+
+TEST_F(KmsEnvelopeAeadTest, DecryptFailsWithInvalidCiphertextOrAad) {
+  ASSERT_THAT(AeadConfig::Register(), IsOk());
+
+  KeyTemplate dek_template = AeadKeyTemplates::Aes128Gcm();
+  auto remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
+  ASSERT_THAT(aead, IsOk());
+
+  std::string message = "Some data to encrypt.";
+  std::string aad = "Some associated data.";
+  util::StatusOr<std::string> encrypt_result = (*aead)->Encrypt(message, aad);
+  ASSERT_THAT(encrypt_result, IsOk());
+  auto ciphertext = absl::string_view(*encrypt_result);
+
+  // Ciphertext has size zero or smaller than 4 bytes.
+  EXPECT_THAT(
+      (*aead)->Decrypt(/*ciphertext=*/"", aad).status(),
+      StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("too short")));
+  EXPECT_THAT(
+      (*aead)->Decrypt(/*ciphertext=*/"sh", aad).status(),
       StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("too short")));
 
-  // Short ciphertext.
-  decrypt_result = aead->Decrypt("sh", aad);
+  // Ciphertext is smaller than the size of the key.
+  const int dek_encrypted_key_size = absl::big_endian::Load32(
+      reinterpret_cast<const uint8_t*>(ciphertext.data()));
+  // We leave only key size and key truncated by one.
   EXPECT_THAT(
-      decrypt_result.status(),
-      StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("too short")));
-
-  // Truncated ciphertext.
-  decrypt_result = aead->Decrypt(ct.substr(2), aad);
-  EXPECT_THAT(
-      decrypt_result.status(),
+      (*aead)
+          ->Decrypt(ciphertext.substr(0, 4 + dek_encrypted_key_size - 1), aad)
+          .status(),
       StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("invalid")));
 
-  // Corrupted ciphertext.
-  auto ct_copy = ct;
-  ct_copy[4] = 'a';  // corrupt serialized DEK.
-  decrypt_result = aead->Decrypt(ct_copy, aad);
+  std::string corrupted_ciphertext = *encrypt_result;
+  // Corrupt the serialized DEK.
+  corrupted_ciphertext[4] = 'a';
   EXPECT_THAT(
-      decrypt_result.status(),
+      (*aead)->Decrypt(corrupted_ciphertext, aad).status(),
       StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("invalid")));
 
   // Wrong associated data.
-  decrypt_result = aead->Decrypt(ct, "wrong aad");
-  EXPECT_THAT(decrypt_result.status(),
+  EXPECT_THAT((*aead)->Decrypt(ciphertext, "wrong aad").status(),
               StatusIs(absl::StatusCode::kInternal,
                        HasSubstr("Authentication failed")));
 }
 
-TEST(KmsEnvelopeAeadTest, KeyFormat) {
-  EXPECT_THAT(AeadConfig::Register(), IsOk());
+TEST_F(KmsEnvelopeAeadTest, DekMaintainsCorrectKeyFormat) {
+  ASSERT_THAT(AeadConfig::Register(), IsOk());
 
-  auto dek_template = AeadKeyTemplates::Aes128Gcm();
+  KeyTemplate dek_template = AeadKeyTemplates::Aes128Gcm();
+  auto kms_remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      KmsEnvelopeAead::New(dek_template, std::move(kms_remote_aead));
+  ASSERT_THAT(aead, IsOk());
 
-  // Construct a remote AEAD which uses same key template for this test.
-  std::string remote_aead_name = "kms-backed-aead";
-  auto remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
-
-  // Create envelope AEAD and encrypt some data.
-  auto aead_result = KmsEnvelopeAead::New(dek_template, std::move(remote_aead));
-  EXPECT_THAT(aead_result, IsOk());
-
-  auto aead = std::move(aead_result.value());
   std::string message = "Some data to encrypt.";
-  std::string aad = "Some data to authenticate.";
-  auto encrypt_result = aead->Encrypt(message, aad);
-  EXPECT_THAT(encrypt_result, IsOk());
-  auto ct = encrypt_result.value();
+  std::string aad = "Some associated data.";
+  util::StatusOr<std::string> ciphertext = (*aead)->Encrypt(message, aad);
+  ASSERT_THAT(ciphertext, IsOk());
 
-  // Recover DEK from ciphertext
-  auto enc_dek_size =
-      absl::big_endian::Load32(reinterpret_cast<const uint8_t*>(ct.data()));
-
-  remote_aead = absl::make_unique<DummyAead>(remote_aead_name);
-  auto dek_decrypt_result =
-      remote_aead->Decrypt(ct.substr(4, enc_dek_size), "");
+  // Recover DEK from ciphertext (see
+  // https://developers.google.com/tink/wire-format#envelope_encryption).
+  auto enc_dek_size = absl::big_endian::Load32(
+      reinterpret_cast<const uint8_t*>(ciphertext->data()));
+  DummyAead remote_aead = DummyAead(kRemoteAeadName);
+  absl::string_view encrypted_dek =
+      absl::string_view(*ciphertext)
+          .substr(kEncryptedDekPrefixSize, enc_dek_size);
+  util::StatusOr<std::string> dek_proto_bytes =
+      remote_aead.Decrypt(encrypted_dek,
+                          /*associated_data=*/"");
+  ASSERT_THAT(dek_proto_bytes, IsOk());
 
   // Check if we can deserialize a GCM key proto from the decrypted DEK.
   google::crypto::tink::AesGcmKey key;
-  EXPECT_THAT(key.ParseFromString(dek_decrypt_result.value()), true);
-  EXPECT_THAT(key.key_value().size(), testing::Eq(16));
+  EXPECT_TRUE(key.ParseFromString(dek_proto_bytes.value()));
+  EXPECT_THAT(key.key_value(), SizeIs(16));
 }
 
+TEST_F(KmsEnvelopeAeadTest, MultipleEncryptionsProduceDifferentDeks) {
+  ASSERT_THAT(AeadConfig::Register(), IsOk());
+
+  KeyTemplate dek_template = AeadKeyTemplates::Aes128Gcm();
+  auto kms_remote_aead = absl::make_unique<DummyAead>(kRemoteAeadName);
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      KmsEnvelopeAead::New(dek_template, std::move(kms_remote_aead));
+  ASSERT_THAT(aead, IsOk());
+
+  std::string message = "Some data to encrypt.";
+  std::string aad = "Some associated data.";
+
+  constexpr int kNumIterations = 2;
+  std::vector<google::crypto::tink::AesGcmKey> ciphertexts;
+  ciphertexts.reserve(kNumIterations);
+  for (int i = 0; i < kNumIterations; i++) {
+    util::StatusOr<std::string> ciphertext = (*aead)->Encrypt(message, aad);
+    ASSERT_THAT(ciphertext, IsOk());
+
+    auto enc_dek_size = absl::big_endian::Load32(
+        reinterpret_cast<const uint8_t*>(ciphertext->data()));
+    DummyAead remote_aead = DummyAead(kRemoteAeadName);
+    util::StatusOr<std::string> dek_proto_bytes = remote_aead.Decrypt(
+        ciphertext->substr(kEncryptedDekPrefixSize, enc_dek_size),
+        /*associated_data=*/"");
+    ASSERT_THAT(dek_proto_bytes, IsOk());
+
+    google::crypto::tink::AesGcmKey key;
+    ASSERT_TRUE(key.ParseFromString(dek_proto_bytes.value()));
+    ASSERT_THAT(key.key_value(), SizeIs(16));
+    ciphertexts.push_back(key);
+  }
+
+  for (int i = 0; i < ciphertexts.size() - 1; i++) {
+    for (int j = i + 1; j < ciphertexts.size(); j++) {
+      EXPECT_THAT(ciphertexts[i].SerializeAsString(),
+                  Not(Eq(ciphertexts[j].SerializeAsString())));
+    }
+  }
+}
+
+class KmsEnvelopeAeadDekTemplatesTest
+    : public testing::TestWithParam<KeyTemplate> {
+  void SetUp() override { ASSERT_THAT(AeadConfig::Register(), IsOk()); }
+};
+
+TEST_P(KmsEnvelopeAeadDekTemplatesTest, EncryptDecrypt) {
+  // Use an AES-128-GCM primitive as the remote AEAD.
+  util::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm());
+  ASSERT_THAT(keyset_handle, IsOk());
+  util::StatusOr<std::unique_ptr<Aead>> remote_aead =
+      (*keyset_handle)->GetPrimitive<Aead>();
+
+  KeyTemplate dek_template = GetParam();
+  util::StatusOr<std::unique_ptr<Aead>> envelope_aead =
+      KmsEnvelopeAead::New(dek_template, *std::move(remote_aead));
+  ASSERT_THAT(envelope_aead, IsOk());
+
+  std::string plaintext = "plaintext";
+  std::string associated_data = "associated_data";
+  util::StatusOr<std::string> ciphertext =
+      (*envelope_aead)->Encrypt(plaintext, associated_data);
+  ASSERT_THAT(ciphertext, IsOk());
+  util::StatusOr<std::string> decrypted =
+      (*envelope_aead)->Decrypt(ciphertext.value(), associated_data);
+  EXPECT_THAT(decrypted, IsOkAndHolds(plaintext));
+}
+
+std::vector<KeyTemplate> GetTestTemplates() {
+  std::vector<KeyTemplate> templates = {
+    AeadKeyTemplates::Aes128Gcm(),
+    AeadKeyTemplates::Aes256Gcm(),
+    AeadKeyTemplates::Aes128CtrHmacSha256(),
+    AeadKeyTemplates::Aes128Eax(),
+    AeadKeyTemplates::Aes128GcmNoPrefix()
+  };
+  if (internal::IsBoringSsl()) {
+    templates.push_back(AeadKeyTemplates::XChaCha20Poly1305());
+    templates.push_back(AeadKeyTemplates::Aes256GcmSiv());
+  }
+  return templates;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    KmsEnvelopeAeadDekTemplatesTest, KmsEnvelopeAeadDekTemplatesTest,
+    testing::ValuesIn(GetTestTemplates()));
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/binary_keyset_reader.h b/cc/binary_keyset_reader.h
index 01d4220..7b718c1 100644
--- a/cc/binary_keyset_reader.h
+++ b/cc/binary_keyset_reader.h
@@ -18,6 +18,7 @@
 #define TINK_BINARY_KEYSET_READER_H_
 
 #include <istream>
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
diff --git a/cc/binary_keyset_writer.h b/cc/binary_keyset_writer.h
index 802ba41..5e5a0dc 100644
--- a/cc/binary_keyset_writer.h
+++ b/cc/binary_keyset_writer.h
@@ -17,6 +17,7 @@
 #ifndef TINK_BINARY_KEYSET_WRITER_H_
 #define TINK_BINARY_KEYSET_WRITER_H_
 
+#include <memory>
 #include <ostream>
 #include <utility>
 
@@ -38,7 +39,7 @@
       std::unique_ptr<std::ostream> destination_stream);
 
   crypto::tink::util::Status
-  Write(const google::crypto::tink::Keyset& keyset) override;;
+  Write(const google::crypto::tink::Keyset& keyset) override;
 
   crypto::tink::util::Status
   Write(const google::crypto::tink::EncryptedKeyset& encrypted_keyset) override;
diff --git a/cc/catalogue.h b/cc/catalogue.h
deleted file mode 100644
index 0452c4c..0000000
--- a/cc/catalogue.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_CATALOGUE_H_
-#define TINK_CATALOGUE_H_
-
-#include <string>
-
-#include "absl/base/macros.h"
-#include "tink/key_manager.h"
-#include "tink/util/statusor.h"
-
-namespace crypto {
-namespace tink {
-
-// This class is deprecated. We don't support catalogues anymore.
-template <class P>
-class ABSL_DEPRECATED("Catalogues are not supported anymore.") Catalogue {
- public:
-  // Returns a key manager for the given 'type_url', 'primitive_name',
-  // and version at least 'min_version' (if any found).
-  // Caller owns the returned manager.
-  virtual crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<P>>>
-  GetKeyManager(const std::string& type_url, const std::string& primitive_name,
-                uint32_t min_version) const = 0;
-
-  virtual ~Catalogue() {}
-};
-
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // TINK_CATALOGUE_H_
diff --git a/cc/config.h b/cc/config.h
deleted file mode 100644
index 099a448..0000000
--- a/cc/config.h
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_CONFIG_H_
-#define TINK_CONFIG_H_
-
-#include <string>
-
-#include "absl/status/status.h"
-#include "absl/strings/ascii.h"
-#include "tink/aead/aead_config.h"
-#include "tink/catalogue.h"
-#include "tink/daead/deterministic_aead_config.h"
-#include "tink/hybrid/hybrid_config.h"
-#include "tink/key_manager.h"
-#include "tink/mac/mac_config.h"
-#include "tink/registry.h"
-#include "tink/signature/signature_config.h"
-#include "tink/streamingaead/streaming_aead_config.h"
-#include "tink/util/errors.h"
-#include "tink/util/status.h"
-#include "proto/config.pb.h"
-
-namespace crypto {
-namespace tink {
-
-// Static methods for handling of Tink configurations.
-//
-// Configurations, i.e., collections of key types and their corresponding key
-// managers supported by a specific run-time environment enable control
-// of Tink setup via JSON-formatted config files that determine which key types
-// are supported, and provide a mechanism for deprecation of obsolete/outdated
-// cryptographic schemes (see tink/proto/config.proto for more info).
-//
-// Example usage:
-//
-// RegistryConfig registry_config = ...;
-// auto status = Config::Register(registry_config);
-//
-class Config {
- public:
-  // Returns a KeyTypeEntry for Tink key types with the specified parameters.
-  static std::unique_ptr<google::crypto::tink::KeyTypeEntry>
-  GetTinkKeyTypeEntry(const std::string& catalogue_name,
-                      const std::string& primitive_name,
-                      const std::string& key_proto_name,
-                      int key_manager_version, bool new_key_allowed);
-
-  // Registers a key manager according to the specification in 'entry'.
-  template <class P>
-  static crypto::tink::util::Status Register(
-      const google::crypto::tink::KeyTypeEntry& entry);
-
-  // Registers key managers and primitive wrappers according to the
-  // specification in 'config'.
-  static crypto::tink::util::Status Register(
-      const google::crypto::tink::RegistryConfig& config);
-
- private:
-  static crypto::tink::util::Status Validate(
-      const google::crypto::tink::KeyTypeEntry& entry);
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// Implementation details of templated methods.
-
-// static
-template <class P>
-crypto::tink::util::Status Config::Register(
-    const google::crypto::tink::KeyTypeEntry& entry) {
-  util::Status status;
-  std::string primitive_name = absl::AsciiStrToLower(entry.primitive_name());
-
-  if (primitive_name == "mac") {
-    status = MacConfig::Register();
-  } else if (primitive_name == "aead") {
-    status = AeadConfig::Register();
-  } else if (primitive_name == "deterministicaead") {
-    status = DeterministicAeadConfig::Register();
-  } else if (primitive_name == "hybridencrypt" ||
-             primitive_name == "hybriddecrypt") {
-    status = HybridConfig::Register();
-  } else if (primitive_name == "publickeysign" ||
-             primitive_name == "publickeyverify") {
-    status = SignatureConfig::Register();
-  } else if (primitive_name == "streamingaead") {
-    status = StreamingAeadConfig::Register();
-  } else {
-    status = util::Status(
-        absl::StatusCode::kInvalidArgument,
-        absl::StrCat("Non-standard primitive '", entry.primitive_name(),
-                     "', call Registry::RegisterKeyManager "
-                     "and Registry::"
-                     "RegisterPrimitiveWrapper directly."));
-  }
-  if (!status.ok()) return status;
-  return util::OkStatus();
-}
-
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // TINK_CONFIG_H_
diff --git a/cc/config/BUILD.bazel b/cc/config/BUILD.bazel
index 8ec12df..8d77a3e 100644
--- a/cc/config/BUILD.bazel
+++ b/cc/config/BUILD.bazel
@@ -11,7 +11,6 @@
     include_prefix = "tink/config",
     visibility = ["//visibility:public"],
     deps = [
-        "//:config",
         "//:key_manager",
         "//:registry",
         "//daead:deterministic_aead_config",
@@ -38,28 +37,6 @@
     build_setting_default = False,
 )
 
-bool_flag(
-    name = "tink_use_absl_status",
-    build_setting_default = False,
-)
-
-config_setting(
-    name = "absl_status_enabled",
-    flag_values = {"//config:tink_use_absl_status": "True"},
-    visibility = ["//visibility:public"],
-)
-
-bool_flag(
-    name = "tink_use_absl_statusor",
-    build_setting_default = False,
-)
-
-config_setting(
-    name = "absl_statusor_enabled",
-    flag_values = {"//config:tink_use_absl_statusor": "True"},
-    visibility = ["//visibility:public"],
-)
-
 cc_library(
     name = "tink_fips",
     srcs = ["tink_fips.cc"],
@@ -75,6 +52,60 @@
     ],
 )
 
+cc_library(
+    name = "fips_140_2",
+    srcs = ["fips_140_2.cc"],
+    hdrs = ["fips_140_2.h"],
+    include_prefix = "tink/config",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//:configuration",
+        "//aead:aead_wrapper",
+        "//aead:aes_ctr_hmac_aead_key_manager",
+        "//aead:aes_gcm_key_manager",
+        "//internal:configuration_impl",
+        "//internal:fips_utils",
+        "//mac:hmac_key_manager",
+        "//mac:mac_wrapper",
+        "//mac/internal:chunked_mac_wrapper",
+        "//prf:hmac_prf_key_manager",
+        "//prf:prf_set_wrapper",
+        "//signature:ecdsa_sign_key_manager",
+        "//signature:ecdsa_verify_key_manager",
+        "//signature:public_key_sign_wrapper",
+        "//signature:public_key_verify_wrapper",
+        "//signature:rsa_ssa_pkcs1_sign_key_manager",
+        "//signature:rsa_ssa_pkcs1_verify_key_manager",
+        "//signature:rsa_ssa_pss_sign_key_manager",
+        "//signature:rsa_ssa_pss_verify_key_manager",
+        "@com_google_absl//absl/log:check",
+    ],
+)
+
+cc_library(
+    name = "key_gen_fips_140_2",
+    srcs = ["key_gen_fips_140_2.cc"],
+    hdrs = ["key_gen_fips_140_2.h"],
+    include_prefix = "tink/config",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//:key_gen_configuration",
+        "//aead:aes_ctr_hmac_aead_key_manager",
+        "//aead:aes_gcm_key_manager",
+        "//internal:fips_utils",
+        "//internal:key_gen_configuration_impl",
+        "//mac:hmac_key_manager",
+        "//prf:hmac_prf_key_manager",
+        "//signature:ecdsa_sign_key_manager",
+        "//signature:ecdsa_verify_key_manager",
+        "//signature:rsa_ssa_pkcs1_sign_key_manager",
+        "//signature:rsa_ssa_pkcs1_verify_key_manager",
+        "//signature:rsa_ssa_pss_sign_key_manager",
+        "//signature:rsa_ssa_pss_verify_key_manager",
+        "@com_google_absl//absl/log:check",
+    ],
+)
+
 # tests
 
 cc_test(
@@ -84,7 +115,6 @@
     deps = [
         ":tink_config",
         "//:aead",
-        "//:config",
         "//:deterministic_aead",
         "//:hybrid_decrypt",
         "//:hybrid_encrypt",
@@ -127,3 +157,50 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "fips_140_2_test",
+    srcs = ["fips_140_2_test.cc"],
+    deps = [
+        ":fips_140_2",
+        "//aead:aead_key_templates",
+        "//aead:aes_ctr_hmac_aead_key_manager",
+        "//aead:aes_gcm_key_manager",
+        "//internal:configuration_impl",
+        "//internal:fips_utils",
+        "//internal:key_type_info_store",
+        "//mac:aes_cmac_key_manager",
+        "//mac:hmac_key_manager",
+        "//prf:hmac_prf_key_manager",
+        "//proto:tink_cc_proto",
+        "//signature:ecdsa_verify_key_manager",
+        "//signature:rsa_ssa_pkcs1_verify_key_manager",
+        "//signature:rsa_ssa_pss_verify_key_manager",
+        "//util:test_keyset_handle",
+        "//util:test_matchers",
+        "//util:test_util",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "key_gen_fips_140_2_test",
+    srcs = ["key_gen_fips_140_2_test.cc"],
+    deps = [
+        ":key_gen_fips_140_2",
+        "//aead:aead_key_templates",
+        "//aead:aes_ctr_hmac_aead_key_manager",
+        "//aead:aes_gcm_key_manager",
+        "//internal:fips_utils",
+        "//internal:key_gen_configuration_impl",
+        "//mac:aes_cmac_key_manager",
+        "//mac:hmac_key_manager",
+        "//prf:hmac_prf_key_manager",
+        "//proto:tink_cc_proto",
+        "//signature:ecdsa_verify_key_manager",
+        "//signature:rsa_ssa_pkcs1_verify_key_manager",
+        "//signature:rsa_ssa_pss_verify_key_manager",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/config/CMakeLists.txt b/cc/config/CMakeLists.txt
index d479a98..010958c 100644
--- a/cc/config/CMakeLists.txt
+++ b/cc/config/CMakeLists.txt
@@ -1,5 +1,7 @@
 tink_module(config)
 
+add_subdirectory(internal)
+
 tink_cc_library(
   NAME tink_config
   SRCS
@@ -7,7 +9,6 @@
     tink_config.h
   DEPS
     absl::core_headers
-    tink::core::config
     tink::core::key_manager
     tink::core::registry
     tink::daead::deterministic_aead_config
@@ -41,6 +42,56 @@
     tink::util::status
 )
 
+tink_cc_library(
+  NAME fips_140_2
+  SRCS
+    fips_140_2.cc
+    fips_140_2.h
+  DEPS
+    absl::check
+    tink::core::configuration
+    tink::aead::aead_wrapper
+    tink::aead::aes_ctr_hmac_aead_key_manager
+    tink::aead::aes_gcm_key_manager
+    tink::internal::configuration_impl
+    tink::internal::fips_utils
+    tink::mac::hmac_key_manager
+    tink::mac::mac_wrapper
+    tink::mac::internal::chunked_mac_wrapper
+    tink::prf::hmac_prf_key_manager
+    tink::prf::prf_set_wrapper
+    tink::signature::ecdsa_verify_key_manager
+    tink::signature::public_key_sign_wrapper
+    tink::signature::public_key_verify_wrapper
+    tink::signature::rsa_ssa_pkcs1_sign_key_manager
+    tink::signature::rsa_ssa_pkcs1_verify_key_manager
+    tink::signature::rsa_ssa_pss_sign_key_manager
+    tink::signature::rsa_ssa_pss_verify_key_manager
+    tink::signature::ecdsa_sign_key_manager
+)
+
+tink_cc_library(
+  NAME key_gen_fips_140_2
+  SRCS
+    key_gen_fips_140_2.cc
+    key_gen_fips_140_2.h
+  DEPS
+    absl::check
+    tink::core::key_gen_configuration
+    tink::aead::aes_ctr_hmac_aead_key_manager
+    tink::aead::aes_gcm_key_manager
+    tink::internal::fips_utils
+    tink::internal::key_gen_configuration_impl
+    tink::mac::hmac_key_manager
+    tink::prf::hmac_prf_key_manager
+    tink::signature::ecdsa_verify_key_manager
+    tink::signature::rsa_ssa_pkcs1_sign_key_manager
+    tink::signature::rsa_ssa_pkcs1_verify_key_manager
+    tink::signature::rsa_ssa_pss_sign_key_manager
+    tink::signature::rsa_ssa_pss_verify_key_manager
+    tink::signature::ecdsa_sign_key_manager
+)
+
 # tests
 
 tink_cc_test(
@@ -53,7 +104,6 @@
     absl::status
     tink::core::cc
     tink::core::aead
-    tink::core::config
     tink::core::deterministic_aead
     tink::core::hybrid_decrypt
     tink::core::hybrid_encrypt
@@ -89,3 +139,50 @@
     tink::util::status
     tink::util::test_matchers
 )
+
+tink_cc_test(
+  NAME fips_140_2_test
+  SRCS
+    fips_140_2_test.cc
+  DEPS
+    tink::config::fips_140_2
+    gmock
+    tink::aead::aead_key_templates
+    tink::aead::aes_ctr_hmac_aead_key_manager
+    tink::aead::aes_gcm_key_manager
+    tink::internal::configuration_impl
+    tink::internal::fips_utils
+    tink::internal::key_type_info_store
+    tink::mac::aes_cmac_key_manager
+    tink::mac::hmac_key_manager
+    tink::prf::hmac_prf_key_manager
+    tink::signature::ecdsa_verify_key_manager
+    tink::signature::rsa_ssa_pkcs1_verify_key_manager
+    tink::signature::rsa_ssa_pss_verify_key_manager
+    tink::util::test_keyset_handle
+    tink::util::test_matchers
+    tink::util::test_util
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME key_gen_fips_140_2_test
+  SRCS
+    key_gen_fips_140_2_test.cc
+  DEPS
+    tink::config::key_gen_fips_140_2
+    gmock
+    tink::aead::aead_key_templates
+    tink::aead::aes_ctr_hmac_aead_key_manager
+    tink::aead::aes_gcm_key_manager
+    tink::internal::fips_utils
+    tink::internal::key_gen_configuration_impl
+    tink::mac::aes_cmac_key_manager
+    tink::mac::hmac_key_manager
+    tink::prf::hmac_prf_key_manager
+    tink::signature::ecdsa_verify_key_manager
+    tink::signature::rsa_ssa_pkcs1_verify_key_manager
+    tink::signature::rsa_ssa_pss_verify_key_manager
+    tink::util::test_matchers
+    tink::proto::tink_cc_proto
+)
diff --git a/cc/config/fips_140_2.cc b/cc/config/fips_140_2.cc
new file mode 100644
index 0000000..0eafff5
--- /dev/null
+++ b/cc/config/fips_140_2.cc
@@ -0,0 +1,134 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/config/fips_140_2.h"
+
+#include "absl/log/check.h"
+#include "tink/aead/aead_wrapper.h"
+#include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/configuration.h"
+#include "tink/internal/configuration_impl.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/mac/hmac_key_manager.h"
+#include "tink/mac/internal/chunked_mac_wrapper.h"
+#include "tink/mac/mac_wrapper.h"
+#include "tink/prf/hmac_prf_key_manager.h"
+#include "tink/prf/prf_set_wrapper.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/signature/public_key_sign_wrapper.h"
+#include "tink/signature/public_key_verify_wrapper.h"
+#include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/signature/ecdsa_sign_key_manager.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+util::Status AddMac(Configuration& config) {
+  util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+      absl::make_unique<MacWrapper>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+      absl::make_unique<internal::ChunkedMacWrapper>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+
+  return internal::ConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<HmacKeyManager>(), config);
+}
+
+util::Status AddAead(Configuration& config) {
+  util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+      absl::make_unique<AeadWrapper>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+
+  status = internal::ConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<AesCtrHmacAeadKeyManager>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  return internal::ConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<AesGcmKeyManager>(), config);
+}
+
+util::Status AddPrf(Configuration& config) {
+  util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+      absl::make_unique<PrfSetWrapper>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+
+  return internal::ConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<HmacPrfKeyManager>(), config);
+}
+
+util::Status AddSignature(Configuration& config) {
+  util::Status status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+      absl::make_unique<PublicKeySignWrapper>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  status = internal::ConfigurationImpl::AddPrimitiveWrapper(
+      absl::make_unique<PublicKeyVerifyWrapper>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+
+  status = internal::ConfigurationImpl::AddAsymmetricKeyManagers(
+      absl::make_unique<EcdsaSignKeyManager>(),
+      absl::make_unique<EcdsaVerifyKeyManager>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  status = internal::ConfigurationImpl::AddAsymmetricKeyManagers(
+      absl::make_unique<RsaSsaPssSignKeyManager>(),
+      absl::make_unique<RsaSsaPssVerifyKeyManager>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  return internal::ConfigurationImpl::AddAsymmetricKeyManagers(
+      absl::make_unique<RsaSsaPkcs1SignKeyManager>(),
+      absl::make_unique<RsaSsaPkcs1VerifyKeyManager>(), config);
+}
+
+}  // namespace
+
+const Configuration& ConfigFips140_2() {
+  static const Configuration* instance = [] {
+    internal::SetFipsRestricted();
+
+    static Configuration* config = new Configuration();
+    CHECK_OK(AddMac(*config));
+    CHECK_OK(AddAead(*config));
+    CHECK_OK(AddPrf(*config));
+    CHECK_OK(AddSignature(*config));
+
+    return config;
+  }();
+  return *instance;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/fips_140_2.h b/cc/config/fips_140_2.h
new file mode 100644
index 0000000..24fa9b1
--- /dev/null
+++ b/cc/config/fips_140_2.h
@@ -0,0 +1,33 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_CONFIG_FIPS_140_2_H_
+#define TINK_CONFIG_FIPS_140_2_H_
+
+#include "tink/configuration.h"
+
+namespace crypto {
+namespace tink {
+
+// Configuration used to generate primitives using FIPS 140-2-compliant key
+// types. Importing this Configuration restricts Tink to FIPS globally and
+// requires BoringSSL to be built with the BoringCrypto module.
+const Configuration& ConfigFips140_2();
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_CONFIG_FIPS_140_2_H_
diff --git a/cc/config/fips_140_2_test.cc b/cc/config/fips_140_2_test.cc
new file mode 100644
index 0000000..7f134c9
--- /dev/null
+++ b/cc/config/fips_140_2_test.cc
@@ -0,0 +1,142 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/config/fips_140_2.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/internal/configuration_impl.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/internal/key_type_info_store.h"
+#include "tink/mac/aes_cmac_key_manager.h"
+#include "tink/mac/hmac_key_manager.h"
+#include "tink/prf/hmac_prf_key_manager.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/util/test_keyset_handle.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+
+class Fips1402Test : public ::testing::Test {
+ protected:
+  void TearDown() override { internal::UnSetFipsRestricted(); }
+};
+
+TEST_F(Fips1402Test, ConfigFips1402) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  util::StatusOr<const internal::KeyTypeInfoStore*> store =
+      internal::ConfigurationImpl::GetKeyTypeInfoStore(ConfigFips140_2());
+  ASSERT_THAT(store, IsOk());
+
+  EXPECT_THAT((*store)->Get(HmacKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(AesCtrHmacAeadKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(AesGcmKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(HmacPrfKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(EcdsaVerifyKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(RsaSsaPssVerifyKeyManager().get_key_type()),
+              IsOk());
+  EXPECT_THAT((*store)->Get(RsaSsaPkcs1VerifyKeyManager().get_key_type()),
+              IsOk());
+}
+
+TEST_F(Fips1402Test, ConfigFips1402FailsInNonFipsMode) {
+  if (internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in non-FIPS mode";
+  }
+
+  EXPECT_DEATH_IF_SUPPORTED(
+      ConfigFips140_2(), "BoringSSL not built with the BoringCrypto module.");
+}
+
+TEST_F(Fips1402Test, NonFipsTypeNotPresent) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  util::StatusOr<const internal::KeyTypeInfoStore*> store =
+      internal::ConfigurationImpl::GetKeyTypeInfoStore(ConfigFips140_2());
+  ASSERT_THAT(store, IsOk());
+  EXPECT_THAT((*store)->Get(AesCmacKeyManager().get_key_type()).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST_F(Fips1402Test, NewKeyDataAndGetPrimitive) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  // TODO(b/265705174): Replace with KeysetHandle::GenerateNew once that takes a
+  // config parameter.
+  util::StatusOr<const internal::KeyTypeInfoStore*> store =
+      internal::ConfigurationImpl::GetKeyTypeInfoStore(ConfigFips140_2());
+  ASSERT_THAT(store, IsOk());
+  KeyTemplate templ = AeadKeyTemplates::Aes128Gcm();
+  util::StatusOr<internal::KeyTypeInfoStore::Info*> info =
+      (*store)->Get(templ.type_url());
+  ASSERT_THAT(info, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeyData>> key_data =
+      (*info)->key_factory().NewKeyData(templ.value());
+  ASSERT_THAT(key_data, IsOk());
+
+  Keyset keyset;
+  uint32_t key_id = 0;
+  test::AddKeyData(**key_data, key_id, OutputPrefixType::TINK,
+                   KeyStatusType::ENABLED, &keyset);
+  keyset.set_primary_key_id(key_id);
+
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      handle->GetPrimitive<Aead>(ConfigFips140_2());
+  EXPECT_THAT(aead, IsOk());
+
+  std::string plaintext = "plaintext";
+  std::string ad = "ad";
+  util::StatusOr<std::string> ciphertext = (*aead)->Encrypt(plaintext, ad);
+  ASSERT_THAT(ciphertext, IsOk());
+
+  util::StatusOr<std::string> decrypted = (*aead)->Decrypt(*ciphertext, ad);
+  EXPECT_THAT(decrypted, IsOkAndHolds(plaintext));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/internal/BUILD.bazel b/cc/config/internal/BUILD.bazel
new file mode 100644
index 0000000..6d94c3a
--- /dev/null
+++ b/cc/config/internal/BUILD.bazel
@@ -0,0 +1,29 @@
+package(default_visibility = ["//:__subpackages__"])
+
+licenses(["notice"])
+
+cc_library(
+    name = "global_registry",
+    srcs = ["global_registry.cc"],
+    hdrs = ["global_registry.h"],
+    include_prefix = "tink/config/internal",
+    deps = [
+        "//:key_gen_configuration",
+        "//internal:key_gen_configuration_impl",
+        "@com_google_absl//absl/log:check",
+    ],
+)
+
+cc_test(
+    name = "global_registry_test",
+    srcs = ["global_registry_test.cc"],
+    deps = [
+        ":global_registry",
+        "//:keyset_handle",
+        "//proto:aes_gcm_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/config/internal/CMakeLists.txt b/cc/config/internal/CMakeLists.txt
new file mode 100644
index 0000000..3e19a05
--- /dev/null
+++ b/cc/config/internal/CMakeLists.txt
@@ -0,0 +1,26 @@
+tink_module(config::internal)
+
+tink_cc_library(
+  NAME global_registry
+  SRCS
+    global_registry.cc
+    global_registry.h
+  DEPS
+    absl::check
+    tink::core::key_gen_configuration
+    tink::internal::key_gen_configuration_impl
+)
+
+tink_cc_test(
+  NAME global_registry_test
+  SRCS
+    global_registry_test.cc
+  DEPS
+    tink::config::internal::global_registry
+    gmock
+    absl::status
+    tink::core::keyset_handle
+    tink::util::test_matchers
+    tink::proto::aes_gcm_cc_proto
+    tink::proto::tink_cc_proto
+)
diff --git a/cc/config/internal/global_registry.cc b/cc/config/internal/global_registry.cc
new file mode 100644
index 0000000..e1e6907
--- /dev/null
+++ b/cc/config/internal/global_registry.cc
@@ -0,0 +1,38 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/config/internal/global_registry.h"
+
+#include "absl/log/check.h"
+#include "tink/internal/key_gen_configuration_impl.h"
+#include "tink/key_gen_configuration.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+const KeyGenConfiguration& KeyGenConfigGlobalRegistry() {
+  static const KeyGenConfiguration* instance = [] {
+    static KeyGenConfiguration* config = new KeyGenConfiguration();
+    CHECK_OK(KeyGenConfigurationImpl::SetGlobalRegistryMode(*config));
+    return config;
+  }();
+  return *instance;
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/internal/global_registry.h b/cc/config/internal/global_registry.h
new file mode 100644
index 0000000..e1c8996
--- /dev/null
+++ b/cc/config/internal/global_registry.h
@@ -0,0 +1,34 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_CONFIG_INTERNAL_GLOBAL_REGISTRY_H_
+#define TINK_CONFIG_INTERNAL_GLOBAL_REGISTRY_H_
+
+#include "tink/key_gen_configuration.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// TODO(b/265705174): Move to public API after API review.
+// Used to generate keys using the global crypto::tink::Registry.
+const crypto::tink::KeyGenConfiguration& KeyGenConfigGlobalRegistry();
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_CONFIG_INTERNAL_GLOBAL_REGISTRY_H_
diff --git a/cc/config/internal/global_registry_test.cc b/cc/config/internal/global_registry_test.cc
new file mode 100644
index 0000000..b5196f3
--- /dev/null
+++ b/cc/config/internal/global_registry_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/config/internal/global_registry.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/test_matchers.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+
+class FakePrimitive {
+ public:
+  explicit FakePrimitive(std::string s) : s_(s) {}
+  std::string get() { return s_; }
+
+ private:
+  std::string s_;
+};
+
+class FakeKeyTypeManager
+    : public KeyTypeManager<AesGcmKey, AesGcmKeyFormat, List<FakePrimitive>> {
+ public:
+  class FakePrimitiveFactory : public PrimitiveFactory<FakePrimitive> {
+   public:
+    util::StatusOr<std::unique_ptr<FakePrimitive>> Create(
+        const AesGcmKey& key) const override {
+      return absl::make_unique<FakePrimitive>(key.key_value());
+    }
+  };
+
+  FakeKeyTypeManager()
+      : KeyTypeManager(absl::make_unique<FakePrimitiveFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const AesGcmKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateKeyFormat(
+      const AesGcmKeyFormat& key_format) const override {
+    return util::OkStatus();
+  }
+
+  util::StatusOr<AesGcmKey> CreateKey(
+      const AesGcmKeyFormat& key_format) const override {
+    return AesGcmKey();
+  }
+
+  util::StatusOr<AesGcmKey> DeriveKey(
+      const AesGcmKeyFormat& key_format,
+      InputStream* input_stream) const override {
+    return AesGcmKey();
+  }
+
+ private:
+  const std::string key_type_ =
+      "type.googleapis.com/google.crypto.tink.AesGcmKey";
+};
+
+TEST(GlobalRegistryTest, KeyGenConfigGlobalRegistry) {
+  Registry::Reset();
+
+  KeyTemplate templ;
+  templ.set_type_url("type.googleapis.com/google.crypto.tink.AesGcmKey");
+  templ.set_output_prefix_type(OutputPrefixType::TINK);
+
+  EXPECT_THAT(
+      KeysetHandle::GenerateNew(templ, KeyGenConfigGlobalRegistry()).status(),
+      StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<FakeKeyTypeManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+  EXPECT_THAT(
+      KeysetHandle::GenerateNew(templ, KeyGenConfigGlobalRegistry()).status(),
+      IsOk());
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/key_gen_fips_140_2.cc b/cc/config/key_gen_fips_140_2.cc
new file mode 100644
index 0000000..1c260be
--- /dev/null
+++ b/cc/config/key_gen_fips_140_2.cc
@@ -0,0 +1,95 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/config/key_gen_fips_140_2.h"
+
+#include "absl/log/check.h"
+#include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/internal/key_gen_configuration_impl.h"
+#include "tink/key_gen_configuration.h"
+#include "tink/mac/hmac_key_manager.h"
+#include "tink/prf/hmac_prf_key_manager.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/signature/ecdsa_sign_key_manager.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+util::Status AddMac(KeyGenConfiguration& config) {
+  return internal::KeyGenConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<HmacKeyManager>(), config);
+}
+
+util::Status AddAead(KeyGenConfiguration& config) {
+  util::Status status = internal::KeyGenConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<AesCtrHmacAeadKeyManager>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  return internal::KeyGenConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<AesGcmKeyManager>(), config);
+}
+
+util::Status AddPrf(KeyGenConfiguration& config) {
+  return internal::KeyGenConfigurationImpl::AddKeyTypeManager(
+      absl::make_unique<HmacPrfKeyManager>(), config);
+}
+
+util::Status AddSignature(KeyGenConfiguration& config) {
+  util::Status status =
+      internal::KeyGenConfigurationImpl::AddAsymmetricKeyManagers(
+          absl::make_unique<EcdsaSignKeyManager>(),
+          absl::make_unique<EcdsaVerifyKeyManager>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  status = internal::KeyGenConfigurationImpl::AddAsymmetricKeyManagers(
+      absl::make_unique<RsaSsaPssSignKeyManager>(),
+      absl::make_unique<RsaSsaPssVerifyKeyManager>(), config);
+  if (!status.ok()) {
+    return status;
+  }
+  return internal::KeyGenConfigurationImpl::AddAsymmetricKeyManagers(
+      absl::make_unique<RsaSsaPkcs1SignKeyManager>(),
+      absl::make_unique<RsaSsaPkcs1VerifyKeyManager>(), config);
+}
+
+}  // namespace
+
+const KeyGenConfiguration& KeyGenConfigFips140_2() {
+  static const KeyGenConfiguration* instance = [] {
+    internal::SetFipsRestricted();
+
+    static KeyGenConfiguration* config = new KeyGenConfiguration();
+    CHECK_OK(AddMac(*config));
+    CHECK_OK(AddAead(*config));
+    CHECK_OK(AddPrf(*config));
+    CHECK_OK(AddSignature(*config));
+
+    return config;
+  }();
+  return *instance;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/key_gen_fips_140_2.h b/cc/config/key_gen_fips_140_2.h
new file mode 100644
index 0000000..a22e7ed
--- /dev/null
+++ b/cc/config/key_gen_fips_140_2.h
@@ -0,0 +1,33 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_CONFIG_KEY_GEN_FIPS_140_2_H_
+#define TINK_CONFIG_KEY_GEN_FIPS_140_2_H_
+
+#include "tink/key_gen_configuration.h"
+
+namespace crypto {
+namespace tink {
+
+// KeyGenConfiguration used to generate keys using FIPS 140-2-compliant key
+// types. Importing this KeyGenConfiguration restricts Tink to FIPS globally and
+// requires BoringSSL to be built with the BoringCrypto module.
+const KeyGenConfiguration& KeyGenConfigFips140_2();
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_CONFIG_KEY_GEN_FIPS_140_2_H_
diff --git a/cc/config/key_gen_fips_140_2_test.cc b/cc/config/key_gen_fips_140_2_test.cc
new file mode 100644
index 0000000..3b9994c
--- /dev/null
+++ b/cc/config/key_gen_fips_140_2_test.cc
@@ -0,0 +1,118 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/config/key_gen_fips_140_2.h"
+
+#include <memory>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/internal/key_gen_configuration_impl.h"
+#include "tink/mac/aes_cmac_key_manager.h"
+#include "tink/mac/hmac_key_manager.h"
+#include "tink/prf/hmac_prf_key_manager.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/util/test_matchers.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::KeyTemplate;
+
+class KeyGenFips1402Test : public testing::Test {
+ protected:
+  void TearDown() override { internal::UnSetFipsRestricted(); }
+};
+
+TEST_F(KeyGenFips1402Test, KeyGenConfigFips1402) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  util::StatusOr<const internal::KeyTypeInfoStore*> store =
+      internal::KeyGenConfigurationImpl::GetKeyTypeInfoStore(
+          KeyGenConfigFips140_2());
+  ASSERT_THAT(store, IsOk());
+
+  EXPECT_THAT((*store)->Get(HmacKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(AesCtrHmacAeadKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(AesGcmKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(HmacPrfKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(EcdsaVerifyKeyManager().get_key_type()), IsOk());
+  EXPECT_THAT((*store)->Get(RsaSsaPssVerifyKeyManager().get_key_type()),
+              IsOk());
+  EXPECT_THAT((*store)->Get(RsaSsaPkcs1VerifyKeyManager().get_key_type()),
+              IsOk());
+}
+
+TEST_F(KeyGenFips1402Test, KeyGenConfigFips1402FailsInNonFipsMode) {
+  if (internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in non-FIPS mode";
+  }
+
+  EXPECT_DEATH_IF_SUPPORTED(
+      KeyGenConfigFips140_2(),
+      "BoringSSL not built with the BoringCrypto module.");
+}
+
+TEST_F(KeyGenFips1402Test, NonFipsTypeNotPresent) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  util::StatusOr<const internal::KeyTypeInfoStore*> store =
+      internal::KeyGenConfigurationImpl::GetKeyTypeInfoStore(
+          KeyGenConfigFips140_2());
+  ASSERT_THAT(store, IsOk());
+  EXPECT_THAT((*store)->Get(AesCmacKeyManager().get_key_type()).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST_F(KeyGenFips1402Test, NewKeyData) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  // TODO(b/265705174): Replace with KeysetHandle::GenerateNew once that takes a
+  // config parameter.
+  util::StatusOr<const internal::KeyTypeInfoStore*> store =
+      internal::KeyGenConfigurationImpl::GetKeyTypeInfoStore(
+          KeyGenConfigFips140_2());
+  ASSERT_THAT(store, IsOk());
+  KeyTemplate templ = AeadKeyTemplates::Aes128Gcm();
+  util::StatusOr<internal::KeyTypeInfoStore::Info*> info =
+      (*store)->Get(templ.type_url());
+  ASSERT_THAT(info, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeyData>> key_data =
+      (*info)->key_factory().NewKeyData(templ.value());
+  EXPECT_THAT(key_data, IsOk());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/tink_config.cc b/cc/config/tink_config.cc
index d909ab8..5210bfa 100644
--- a/cc/config/tink_config.cc
+++ b/cc/config/tink_config.cc
@@ -16,7 +16,6 @@
 
 #include "tink/config/tink_config.h"
 
-#include "tink/config.h"
 #include "tink/daead/deterministic_aead_config.h"
 #include "tink/hybrid/hybrid_config.h"
 #include "tink/key_manager.h"
diff --git a/cc/config/tink_config_test.cc b/cc/config/tink_config_test.cc
index 3cd046d..e1cd175 100644
--- a/cc/config/tink_config_test.cc
+++ b/cc/config/tink_config_test.cc
@@ -20,7 +20,6 @@
 #include "absl/status/status.h"
 #include "tink/aead.h"
 #include "tink/aead/aes_gcm_key_manager.h"
-#include "tink/config.h"
 #include "tink/deterministic_aead.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/hybrid_encrypt.h"
diff --git a/cc/config/tink_fips_test.cc b/cc/config/tink_fips_test.cc
index 61dc06d..ec15ca7 100644
--- a/cc/config/tink_fips_test.cc
+++ b/cc/config/tink_fips_test.cc
@@ -65,7 +65,7 @@
 }
 
 TEST(TinkFipsTest, CompatibilityChecksWithBoringCrypto) {
-  if (!FIPS_mode()) {
+  if (!internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test only run if BoringCrypto module is available.";
   }
 
@@ -88,7 +88,7 @@
 }
 
 TEST(TinkFipsTest, CompatibilityChecksWithoutBoringCrypto) {
-  if (FIPS_mode()) {
+  if (internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test only run if BoringCrypto module is not available.";
   }
 
diff --git a/cc/configuration.h b/cc/configuration.h
new file mode 100644
index 0000000..a087737
--- /dev/null
+++ b/cc/configuration.h
@@ -0,0 +1,55 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_CONFIGURATION_H_
+#define TINK_CONFIGURATION_H_
+
+#include "tink/internal/key_type_info_store.h"
+#include "tink/internal/keyset_wrapper_store.h"
+
+namespace crypto {
+namespace tink {
+
+namespace internal {
+class ConfigurationImpl;
+}
+
+// Configuration used to generate primitives using stored primitive wrappers and
+// key type managers.
+class Configuration {
+ public:
+  Configuration() = default;
+
+  // Not copyable or movable.
+  Configuration(const Configuration&) = delete;
+  Configuration& operator=(const Configuration&) = delete;
+
+ private:
+  friend class internal::ConfigurationImpl;
+
+  // When true, Configuration is in global registry mode. For `some_fn(config)`
+  // with a `config` parameter, this indicates to `some_fn` to use
+  // crypto::tink::Registry directly.
+  bool global_registry_mode_ = false;
+
+  crypto::tink::internal::KeyTypeInfoStore key_type_info_store_;
+  crypto::tink::internal::KeysetWrapperStore keyset_wrapper_store_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_CONFIGURATION_H_
diff --git a/cc/core/cleartext_keyset_handle.cc b/cc/core/cleartext_keyset_handle.cc
index 989f95e..9e000ff 100644
--- a/cc/core/cleartext_keyset_handle.cc
+++ b/cc/core/cleartext_keyset_handle.cc
@@ -20,6 +20,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/container/flat_hash_map.h"
 #include "absl/status/status.h"
@@ -41,14 +42,23 @@
     std::unique_ptr<KeysetReader> reader,
     const absl::flat_hash_map<std::string, std::string>&
         monitoring_annotations) {
-  auto keyset_result = reader->Read();
+  util::StatusOr<std::unique_ptr<Keyset>> keyset_result = reader->Read();
   if (!keyset_result.ok()) {
     return ToStatusF(absl::StatusCode::kInvalidArgument,
                      "Error reading keyset data: %s",
                      keyset_result.status().message());
   }
+  util::StatusOr<std::vector<std::shared_ptr<const KeysetHandle::Entry>>>
+      entries = KeysetHandle::GetEntriesFromKeyset(**keyset_result);
+  if (!entries.ok()) {
+    return entries.status();
+  }
+  if (entries->size() != (*keyset_result)->key_size()) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Error converting keyset proto into key entries.");
+  }
   std::unique_ptr<KeysetHandle> handle(new KeysetHandle(
-      std::move(keyset_result.value()), monitoring_annotations));
+      std::move(keyset_result.value()), *entries, monitoring_annotations));
   return std::move(handle);
 }
 
diff --git a/cc/core/cleartext_keyset_handle_test.cc b/cc/core/cleartext_keyset_handle_test.cc
index d172ccf..0ff416c 100644
--- a/cc/core/cleartext_keyset_handle_test.cc
+++ b/cc/core/cleartext_keyset_handle_test.cc
@@ -29,7 +29,6 @@
 #include "tink/util/test_util.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::TestKeysetHandle;
 using crypto::tink::test::AddRawKey;
 using crypto::tink::test::AddTinkKey;
 
@@ -49,9 +48,9 @@
 TEST_F(CleartextKeysetHandleTest, testRead) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
   {  // Reader that reads a valid keyset.
@@ -76,9 +75,9 @@
 TEST_F(CleartextKeysetHandleTest, testWrite) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
 
diff --git a/cc/core/config.cc b/cc/core/config.cc
deleted file mode 100644
index fe5cae2..0000000
--- a/cc/core/config.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//     http://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 "tink/config.h"
-
-#include <memory>
-#include <string>
-
-#include "absl/status/status.h"
-#include "absl/strings/ascii.h"
-#include "absl/strings/str_cat.h"
-#include "tink/util/errors.h"
-#include "tink/util/status.h"
-#include "tink/util/statusor.h"
-#include "proto/config.pb.h"
-
-using google::crypto::tink::KeyTypeEntry;
-
-namespace crypto {
-namespace tink {
-
-// static
-std::unique_ptr<google::crypto::tink::KeyTypeEntry> Config::GetTinkKeyTypeEntry(
-    const std::string& catalogue_name, const std::string& primitive_name,
-    const std::string& key_proto_name, int key_manager_version,
-    bool new_key_allowed) {
-  std::string prefix = "type.googleapis.com/google.crypto.tink.";
-  std::unique_ptr<KeyTypeEntry> entry(new KeyTypeEntry());
-  entry->set_catalogue_name(catalogue_name);
-  entry->set_primitive_name(primitive_name);
-  entry->set_type_url(prefix.append(key_proto_name));
-  entry->set_key_manager_version(key_manager_version);
-  entry->set_new_key_allowed(new_key_allowed);
-  return entry;
-}
-
-// static
-crypto::tink::util::Status Config::Validate(const KeyTypeEntry& entry) {
-  if (entry.type_url().empty()) {
-    return util::Status(absl::StatusCode::kInvalidArgument,
-                        "Missing type_url.");
-  }
-  if (entry.primitive_name().empty()) {
-    return util::Status(absl::StatusCode::kInvalidArgument,
-                        "Missing primitive_name.");
-  }
-  if (entry.catalogue_name().empty()) {
-    return util::Status(absl::StatusCode::kInvalidArgument,
-                        "Missing catalogue_name.");
-  }
-  return util::OkStatus();
-}
-
-// static
-util::Status Config::Register(
-    const google::crypto::tink::RegistryConfig& config) {
-  util::Status status;
-  status = MacConfig::Register();
-  if (!status.ok()) return status;
-  status = AeadConfig::Register();
-  if (!status.ok()) return status;
-  status = DeterministicAeadConfig::Register();
-  if (!status.ok()) return status;
-  status = HybridConfig::Register();
-  if (!status.ok()) return status;
-  status = SignatureConfig::Register();
-  if (!status.ok()) return status;
-  status = StreamingAeadConfig::Register();
-  if (!status.ok()) return status;
-  return util::OkStatus();
-}
-
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/core/config_test.cc b/cc/core/config_test.cc
deleted file mode 100644
index 2633475..0000000
--- a/cc/core/config_test.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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 "tink/config.h"
-
-#include "gtest/gtest.h"
-#include "tink/mac.h"
-#include "proto/config.pb.h"
-
-using google::crypto::tink::KeyTypeEntry;
-
-namespace crypto {
-namespace tink {
-namespace {
-
-class ConfigTest : public ::testing::Test {
-};
-
-TEST_F(ConfigTest, testValidation) {
-  KeyTypeEntry entry;
-
-  auto status = Config::Register<Mac>(entry);
-  EXPECT_FALSE(status.ok());
-  EXPECT_EQ(absl::StatusCode::kInvalidArgument, status.code());
-
-  entry.set_type_url("some key type");
-  entry.set_catalogue_name("some catalogue");
-  status = Config::Register<Mac>(entry);
-  EXPECT_FALSE(status.ok());
-  EXPECT_EQ(absl::StatusCode::kInvalidArgument, status.code());
-
-  entry.set_primitive_name("some primitive");
-  status = Config::Register<Mac>(entry);
-  EXPECT_FALSE(status.ok());
-  EXPECT_EQ(absl::StatusCode::kInvalidArgument, status.code());
-}
-
-
-// TODO(przydatek): add more tests.
-
-}  // namespace
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/core/json_keyset_reader_test.cc b/cc/core/json_keyset_reader_test.cc
index dfd5048..5a3702a 100644
--- a/cc/core/json_keyset_reader_test.cc
+++ b/cc/core/json_keyset_reader_test.cc
@@ -27,7 +27,6 @@
 #include "gtest/gtest.h"
 #include "absl/strings/escaping.h"
 #include "absl/strings/substitute.h"
-#include "tink/util/protobuf_helper.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 #include "proto/aes_eax.pb.h"
@@ -312,11 +311,11 @@
   EXPECT_THAT(keyset->primary_key_id(), Eq(4294967275));
 }
 
-TEST_F(JsonKeysetReaderTest, ReadNegativeKeyId) {
+TEST_F(JsonKeysetReaderTest, RejectsNegativeKeyIds) {
   std::string json_serialization =
       absl::Substitute(R"(
       {
-         "primaryKeyId": -21,
+         "primaryKeyId": 711,
          "key":[
             {
                "keyData":{
@@ -349,6 +348,44 @@
   EXPECT_THAT(read_result, Not(IsOk()));
 }
 
+TEST_F(JsonKeysetReaderTest, RejectsKeyIdLargerThanUint32) {
+  // 4294967296 = 2^32, which is too large for uint32.
+  std::string json_serialization =
+      absl::Substitute(R"(
+      {
+         "primaryKeyId": 711,
+         "key":[
+            {
+               "keyData":{
+                  "typeUrl":"type.googleapis.com/google.crypto.tink.AesGcmKey",
+                  "keyMaterialType":"SYMMETRIC",
+                  "value": "$0"
+               },
+               "outputPrefixType":"TINK",
+               "keyId": 4294967296,
+               "status":"ENABLED"
+            },
+            {
+               "keyData":{
+                  "typeUrl":"type.googleapis.com/google.crypto.tink.AesEaxKey",
+                  "keyMaterialType":"SYMMETRIC",
+                  "value":"$1"
+               },
+               "outputPrefixType":"RAW",
+               "keyId":711,
+               "status":"ENABLED"
+            }
+         ]
+      })",
+                       absl::Base64Escape(gcm_key_.SerializeAsString()),
+                       absl::Base64Escape(eax_key_.SerializeAsString()));
+  auto reader_result = JsonKeysetReader::New(json_serialization);
+  ASSERT_THAT(reader_result, IsOk());
+  auto reader = std::move(reader_result.value());
+  auto read_result = reader->Read();
+  EXPECT_THAT(read_result, Not(IsOk()));
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/core/key_manager_impl.h b/cc/core/key_manager_impl.h
index ae0de69..c47c951 100644
--- a/cc/core/key_manager_impl.h
+++ b/cc/core/key_manager_impl.h
@@ -17,6 +17,7 @@
 #define TINK_CORE_KEY_MANAGER_IMPL_H_
 
 #include <functional>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/core/key_type_manager.h b/cc/core/key_type_manager.h
index a2a8c6f..0ca2836 100644
--- a/cc/core/key_type_manager.h
+++ b/cc/core/key_type_manager.h
@@ -42,7 +42,7 @@
 template <typename KeyProto, typename KeyFormatProto>
 class InternalKeyFactory {
  public:
-  virtual ~InternalKeyFactory() {}
+  virtual ~InternalKeyFactory() = default;
 
   // Validates a key format proto.  KeyFormatProtos
   // on which this function returns a non-ok status will not be passed to
@@ -69,7 +69,7 @@
 template <typename KeyProto>
 class InternalKeyFactory<KeyProto, void> {
  public:
-  virtual ~InternalKeyFactory() {}
+  virtual ~InternalKeyFactory() = default;
 };
 
 }  // namespace internal
@@ -112,7 +112,7 @@
   template <typename Primitive>
   class PrimitiveFactory {
    public:
-    virtual ~PrimitiveFactory() {}
+    virtual ~PrimitiveFactory() = default;
     virtual crypto::tink::util::StatusOr<std::unique_ptr<Primitive>> Create(
         const KeyProto& key) const = 0;
   };
diff --git a/cc/core/key_type_manager_test.cc b/cc/core/key_type_manager_test.cc
index 921aea5..f5f8dfb 100644
--- a/cc/core/key_type_manager_test.cc
+++ b/cc/core/key_type_manager_test.cc
@@ -38,7 +38,6 @@
 
 namespace {
 
-using ::crypto::tink::test::StatusIs;
 using ::google::crypto::tink::AesGcmKey;
 using ::google::crypto::tink::AesGcmKeyFormat;
 using ::testing::Eq;
diff --git a/cc/core/keyset_handle.cc b/cc/core/keyset_handle.cc
index 3b75cfb..eded473 100644
--- a/cc/core/keyset_handle.cc
+++ b/cc/core/keyset_handle.cc
@@ -12,19 +12,32 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-///////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
 #include "tink/keyset_handle.h"
 
+#include <cstdint>
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/container/flat_hash_map.h"
+#include "absl/log/check.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
 #include "tink/aead.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/key_gen_configuration_impl.h"
 #include "tink/internal/key_info.h"
+#include "tink/internal/key_status_util.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/util.h"
+#include "tink/key_gen_configuration.h"
+#include "tink/key_status.h"
 #include "tink/keyset_reader.h"
 #include "tink/keyset_writer.h"
 #include "tink/registry.h"
@@ -36,7 +49,9 @@
 using google::crypto::tink::KeyData;
 using google::crypto::tink::Keyset;
 using google::crypto::tink::KeysetInfo;
+using google::crypto::tink::KeyStatusType;
 using google::crypto::tink::KeyTemplate;
+using google::crypto::tink::OutputPrefixType;
 
 namespace crypto {
 namespace tink {
@@ -83,8 +98,145 @@
   return util::OkStatus();
 }
 
+util::StatusOr<internal::ProtoKeySerialization> ToProtoKeySerialization(
+    Keyset::Key key) {
+  absl::optional<int> id_requirement = absl::nullopt;
+  if (key.output_prefix_type() != OutputPrefixType::RAW) {
+    id_requirement = key.key_id();
+  }
+
+  return internal::ProtoKeySerialization::Create(
+      key.key_data().type_url(),
+      RestrictedData(key.key_data().value(), InsecureSecretKeyAccess::Get()),
+      key.key_data().key_material_type(), key.output_prefix_type(),
+      id_requirement);
+}
+
 }  // anonymous namespace
 
+util::Status KeysetHandle::ValidateAt(int index) const {
+  const Keyset::Key& proto_key = get_keyset().key(index);
+  OutputPrefixType output_prefix_type = proto_key.output_prefix_type();
+  absl::optional<int> id_requirement = absl::nullopt;
+  if (output_prefix_type != OutputPrefixType::RAW) {
+    id_requirement = proto_key.key_id();
+  }
+
+  if (!internal::IsPrintableAscii(proto_key.key_data().type_url())) {
+    return util::Status(absl::StatusCode::kFailedPrecondition,
+                        "Non-printable ASCII character in type URL.");
+  }
+
+  util::StatusOr<KeyStatus> key_status =
+      internal::FromKeyStatusType(proto_key.status());
+  if (!key_status.ok()) return key_status.status();
+
+  return util::OkStatus();
+}
+
+util::Status KeysetHandle::Validate() const {
+  int num_primary = 0;
+  const Keyset& keyset = get_keyset();
+
+  for (int i = 0; i < size(); ++i) {
+    util::Status status = ValidateAt(i);
+    if (!status.ok()) return status;
+
+    Keyset::Key proto_key = keyset.key(i);
+    if (proto_key.key_id() == keyset.primary_key_id()) {
+      ++num_primary;
+      if (proto_key.status() != KeyStatusType::ENABLED) {
+        return util::Status(absl::StatusCode::kFailedPrecondition,
+                            "Keyset has primary that is not enabled");
+      }
+    }
+  }
+
+  if (num_primary < 1) {
+    return util::Status(absl::StatusCode::kFailedPrecondition,
+                        "Keyset has no primary");
+  }
+  if (num_primary > 1) {
+    return util::Status(absl::StatusCode::kFailedPrecondition,
+                        "Keyset has more than one primary");
+  }
+
+  return util::OkStatus();
+}
+
+KeysetHandle::Entry KeysetHandle::GetPrimary() const {
+  util::Status validation = Validate();
+  CHECK_OK(validation);
+
+  const Keyset& keyset = get_keyset();
+  for (int i = 0; i < keyset.key_size(); ++i) {
+    if (keyset.key(i).key_id() == keyset.primary_key_id()) {
+      return (*this)[i];
+    }
+  }
+
+  // Since keyset handle was validated, it should have a valid primary key.
+  internal::LogFatal("Keyset handle should have a valid primary key.");
+}
+
+KeysetHandle::Entry KeysetHandle::operator[](int index) const {
+  CHECK(index >= 0 && index < size())
+      << "Invalid index " << index << " for keyset of size " << size();
+
+  if (!entries_.empty() && entries_.size() > index) {
+    return *entries_[index];
+  }
+  // Since `entries_` has not been populated, the entry must be created on
+  // demand from the key proto entry at `index` in `keyset_`. This special
+  // case will no longer be necessary after `keyset_` has been removed from the
+  // `KeysetHandle` class.
+  //
+  // TODO(b/277792846): Remove after transition to rely solely on
+  // `KeysetHandle::Entry`.
+  return CreateEntryAt(index);
+}
+
+KeysetHandle::Entry KeysetHandle::CreateEntryAt(int index) const {
+  CHECK(index >= 0 && index < size())
+      << "Invalid index " << index << " for keyset of size " << size();
+
+  util::Status validation = ValidateAt(index);
+  CHECK_OK(validation);
+
+  Keyset keyset = get_keyset();
+  util::StatusOr<Entry> entry =
+      CreateEntry(keyset.key(index), keyset.primary_key_id());
+  // Status should be OK since this keyset handle has been validated.
+  CHECK_OK(entry.status());
+  return *entry;
+}
+
+util::StatusOr<KeysetHandle::Entry> KeysetHandle::CreateEntry(
+    const Keyset::Key& proto_key, uint32_t primary_key_id) {
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      ToProtoKeySerialization(proto_key);
+  if (!serialization.ok()) {
+    return serialization.status();
+  }
+
+  util::StatusOr<std::shared_ptr<const Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .ParseKeyWithLegacyFallback(*serialization,
+                                      InsecureSecretKeyAccess::Get());
+  if (!key.ok()) {
+    return key.status();
+  }
+
+  util::StatusOr<KeyStatus> key_status =
+      internal::FromKeyStatusType(proto_key.status());
+  if (!key_status.ok()) {
+    return key_status.status();
+  }
+
+  return Entry(*std::move(key), *key_status, proto_key.key_id(),
+               proto_key.key_id() == primary_key_id);
+}
+
 util::StatusOr<std::unique_ptr<KeysetHandle>> KeysetHandle::Read(
     std::unique_ptr<KeysetReader> reader, const Aead& master_key_aead,
     const absl::flat_hash_map<std::string, std::string>&
@@ -114,8 +266,17 @@
                      "Error decrypting encrypted keyset: %s",
                      keyset_result.status().message());
   }
-  return absl::WrapUnique(
-      new KeysetHandle(*std::move(keyset_result), monitoring_annotations));
+  util::StatusOr<std::vector<std::shared_ptr<const Entry>>> entries =
+      GetEntriesFromKeyset(**keyset_result);
+  if (!entries.ok()) {
+    return entries.status();
+  }
+  if (entries->size() != (*keyset_result)->key_size()) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Error converting keyset proto into key entries.");
+  }
+  return absl::WrapUnique(new KeysetHandle(*std::move(keyset_result), *entries,
+                                           monitoring_annotations));
 }
 
 util::StatusOr<std::unique_ptr<KeysetHandle>> KeysetHandle::ReadNoSecret(
@@ -131,8 +292,17 @@
   if (!validation.ok()) {
     return validation;
   }
+  util::StatusOr<std::vector<std::shared_ptr<const Entry>>> entries =
+      GetEntriesFromKeyset(keyset);
+  if (!entries.ok()) {
+    return entries.status();
+  }
+  if (entries->size() != keyset.key_size()) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Error converting keyset proto into key entries.");
+  }
   return absl::WrapUnique(
-      new KeysetHandle(std::move(keyset), monitoring_annotations));
+      new KeysetHandle(std::move(keyset), *entries, monitoring_annotations));
 }
 
 util::Status KeysetHandle::Write(KeysetWriter* writer,
@@ -169,17 +339,30 @@
 }
 
 util::StatusOr<std::unique_ptr<KeysetHandle>> KeysetHandle::GenerateNew(
-    const KeyTemplate& key_template,
+    const KeyTemplate& key_template, const KeyGenConfiguration& config,
     const absl::flat_hash_map<std::string, std::string>&
         monitoring_annotations) {
-  Keyset keyset;
+  auto handle =
+      absl::WrapUnique(new KeysetHandle(Keyset(), monitoring_annotations));
   util::StatusOr<uint32_t> const result =
-      AddToKeyset(key_template, /*as_primary=*/true, &keyset);
+      handle->AddKey(key_template, /*as_primary=*/true, config);
   if (!result.ok()) {
     return result.status();
   }
-  return absl::WrapUnique(
-      new KeysetHandle(std::move(keyset), monitoring_annotations));
+  return std::move(handle);
+}
+
+util::StatusOr<std::unique_ptr<KeysetHandle>> KeysetHandle::GenerateNew(
+    const KeyTemplate& key_template,
+    const absl::flat_hash_map<std::string, std::string>&
+        monitoring_annotations) {
+  KeyGenConfiguration config;
+  util::Status status =
+      internal::KeyGenConfigurationImpl::SetGlobalRegistryMode(config);
+  if (!status.ok()) {
+    return status;
+  }
+  return GenerateNew(key_template, config, monitoring_annotations);
 }
 
 util::StatusOr<std::unique_ptr<Keyset::Key>> ExtractPublicKey(
@@ -206,44 +389,98 @@
     public_keyset->add_key()->Swap(public_key_result.value().get());
   }
   public_keyset->set_primary_key_id(get_keyset().primary_key_id());
+  util::StatusOr<std::vector<std::shared_ptr<const Entry>>> entries =
+      GetEntriesFromKeyset(*public_keyset);
+  if (!entries.ok()) {
+    return entries.status();
+  }
+  if (entries->size() != public_keyset->key_size()) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Error converting keyset proto into key entries.");
+  }
   std::unique_ptr<KeysetHandle> handle(
-      new KeysetHandle(std::move(public_keyset)));
+      new KeysetHandle(std::move(public_keyset), *entries));
   return std::move(handle);
 }
 
 crypto::tink::util::StatusOr<uint32_t> KeysetHandle::AddToKeyset(
     const google::crypto::tink::KeyTemplate& key_template, bool as_primary,
-    Keyset* keyset) {
+    const KeyGenConfiguration& config, Keyset* keyset) {
   if (key_template.output_prefix_type() ==
       google::crypto::tink::OutputPrefixType::UNKNOWN_PREFIX) {
     return util::Status(absl::StatusCode::kInvalidArgument,
                         "key template has unknown prefix");
   }
-  auto key_data_result = Registry::NewKeyData(key_template);
-  if (!key_data_result.ok()) return key_data_result.status();
-  auto key_data = std::move(key_data_result.value());
+
+  // Generate new key data.
+  util::StatusOr<std::unique_ptr<KeyData>> key_data;
+  if (internal::KeyGenConfigurationImpl::GetGlobalRegistryMode(config)) {
+    key_data = Registry::NewKeyData(key_template);
+  } else {
+    util::StatusOr<const internal::KeyTypeInfoStore*> key_type_info_store =
+        internal::KeyGenConfigurationImpl::GetKeyTypeInfoStore(config);
+    if (!key_type_info_store.ok()) {
+      return key_type_info_store.status();
+    }
+    util::StatusOr<const internal::KeyTypeInfoStore::Info*> key_type_info =
+        (*key_type_info_store)->Get(key_template.type_url());
+    if (!key_type_info.ok()) {
+      return key_type_info.status();
+    }
+    key_data = (*key_type_info)->key_factory().NewKeyData(key_template.value());
+  }
+  if (!key_data.ok()) {
+    return key_data.status();
+  }
+
+  // Add and fill in new key in `keyset`.
   Keyset::Key* key = keyset->add_key();
-  uint32_t key_id = GenerateUnusedKeyId(*keyset);
-  *(key->mutable_key_data()) = *key_data;
-  key->set_status(google::crypto::tink::KeyStatusType::ENABLED);
-  key->set_key_id(key_id);
+  *(key->mutable_key_data()) = *std::move(key_data).value();
+  key->set_status(KeyStatusType::ENABLED);
   key->set_output_prefix_type(key_template.output_prefix_type());
+
+  uint32_t key_id = GenerateUnusedKeyId(*keyset);
+  key->set_key_id(key_id);
   if (as_primary) {
     keyset->set_primary_key_id(key_id);
   }
   return key_id;
 }
 
+crypto::tink::util::StatusOr<uint32_t> KeysetHandle::AddKey(
+    const google::crypto::tink::KeyTemplate& key_template, bool as_primary,
+    const KeyGenConfiguration& config) {
+  util::StatusOr<uint32_t> id =
+      AddToKeyset(key_template, as_primary, config, &keyset_);
+  if (!id.ok()) {
+    return id.status();
+  }
+  util::StatusOr<const Entry> entry = CreateEntry(
+      keyset_.key(keyset_.key_size() - 1), keyset_.primary_key_id());
+  if (!entry.ok()) {
+    return entry.status();
+  }
+  entries_.push_back(std::make_shared<const Entry>(*entry));
+  return *id;
+}
+
 KeysetInfo KeysetHandle::GetKeysetInfo() const {
   return KeysetInfoFromKeyset(get_keyset());
 }
 
-KeysetHandle::KeysetHandle(Keyset keyset) : keyset_(std::move(keyset)) {}
-
-KeysetHandle::KeysetHandle(std::unique_ptr<Keyset> keyset)
-    : keyset_(std::move(*keyset)) {}
-
-const Keyset& KeysetHandle::get_keyset() const { return keyset_; }
+util::StatusOr<std::vector<std::shared_ptr<const KeysetHandle::Entry>>>
+KeysetHandle::GetEntriesFromKeyset(const Keyset& keyset) {
+  std::vector<std::shared_ptr<const Entry>> entries;
+  for (const Keyset::Key& key : keyset.key()) {
+    util::StatusOr<const Entry> entry =
+        CreateEntry(key, keyset.primary_key_id());
+    if (!entry.ok()) {
+      return entry.status();
+    }
+    entries.push_back(std::make_shared<const Entry>(*entry));
+  }
+  return entries;
+}
 
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/core/keyset_handle_builder.cc b/cc/core/keyset_handle_builder.cc
new file mode 100644
index 0000000..223321b
--- /dev/null
+++ b/cc/core/keyset_handle_builder.cc
@@ -0,0 +1,198 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/keyset_handle_builder.h"
+
+#include <iostream>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/log/check.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/key_status.h"
+#include "tink/keyset_handle.h"
+#include "tink/subtle/random.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::google::crypto::tink::Keyset;
+
+void SetBuilderEntryAttributes(KeyStatus status, bool is_primary,
+                               absl::optional<int> id,
+                               KeysetHandleBuilder::Entry* entry) {
+  entry->SetStatus(status);
+  if (is_primary) {
+    entry->SetPrimary();
+  } else {
+    entry->UnsetPrimary();
+  }
+  if (id.has_value()) {
+    entry->SetFixedId(*id);
+  } else {
+    entry->SetRandomId();
+  }
+}
+
+}  // namespace
+
+KeysetHandleBuilder::KeysetHandleBuilder(const KeysetHandle& handle) {
+  for (int i = 0; i < handle.size(); ++i) {
+    KeysetHandle::Entry entry = handle[i];
+    KeysetHandleBuilder::Entry builder_entry =
+        KeysetHandleBuilder::Entry::CreateFromKey(
+            std::move(entry.key_), entry.GetStatus(), entry.IsPrimary());
+    AddEntry(std::move(builder_entry));
+  }
+}
+
+KeysetHandleBuilder::Entry KeysetHandleBuilder::Entry::CreateFromKey(
+    std::shared_ptr<const Key> key, KeyStatus status, bool is_primary) {
+  absl::optional<int> id_requirement = key->GetIdRequirement();
+  auto imported_entry = absl::make_unique<internal::KeyEntry>(std::move(key));
+  KeysetHandleBuilder::Entry entry(std::move(imported_entry));
+  SetBuilderEntryAttributes(status, is_primary, id_requirement, &entry);
+  return entry;
+}
+
+KeysetHandleBuilder::Entry KeysetHandleBuilder::Entry::CreateFromParams(
+    std::shared_ptr<const Parameters> parameters, KeyStatus status,
+    bool is_primary, absl::optional<int> id) {
+  auto generated_entry =
+      absl::make_unique<internal::ParametersEntry>(std::move(parameters));
+  KeysetHandleBuilder::Entry entry(std::move(generated_entry));
+  SetBuilderEntryAttributes(status, is_primary, id, &entry);
+  return entry;
+}
+
+util::StatusOr<int> KeysetHandleBuilder::NextIdFromKeyIdStrategy(
+    internal::KeyIdStrategy strategy, const std::set<int>& ids_so_far) {
+  if (strategy.strategy == internal::KeyIdStrategyEnum::kFixedId) {
+    if (!strategy.id_requirement.has_value()) {
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Missing fixed id with fixed id strategy.");
+    }
+    return *strategy.id_requirement;
+  }
+  if (strategy.strategy == internal::KeyIdStrategyEnum::kRandomId) {
+    int id = 0;
+    while (id == 0 || ids_so_far.find(id) != ids_so_far.end()) {
+      id = subtle::Random::GetRandomUInt32();
+    }
+    return id;
+  }
+  return util::Status(absl::StatusCode::kInvalidArgument,
+                      "Invalid key id strategy.");
+}
+
+void KeysetHandleBuilder::ClearPrimary() {
+  for (KeysetHandleBuilder::Entry& entry : entries_) {
+    entry.UnsetPrimary();
+  }
+}
+
+KeysetHandleBuilder& KeysetHandleBuilder::AddEntry(
+    KeysetHandleBuilder::Entry entry) {
+  CHECK(!entry.added_to_builder_)
+      << "Keyset handle builder entry already added to a builder.";
+  entry.added_to_builder_ = true;
+  if (entry.IsPrimary()) {
+    ClearPrimary();
+  }
+  entries_.push_back(std::move(entry));
+  return *this;
+}
+
+KeysetHandleBuilder& KeysetHandleBuilder::RemoveEntry(int index) {
+  CHECK(index >= 0 && index < entries_.size())
+      << "Keyset handle builder entry removal index out of range.";
+  entries_.erase(entries_.begin() + index);
+  return *this;
+}
+
+util::Status KeysetHandleBuilder::CheckIdAssignments() {
+  // We only want random id entries after fixed id entries. Otherwise, we might
+  // randomly pick an id that is later specified as a fixed id.
+  for (int i = 0; i < entries_.size() - 1; ++i) {
+    if (entries_[i].HasRandomId() && !entries_[i + 1].HasRandomId()) {
+      return util::Status(absl::StatusCode::kFailedPrecondition,
+                          "Entries with random ids may only be followed "
+                          "by other entries with random ids.");
+    }
+  }
+  return util::OkStatus();
+}
+
+util::StatusOr<KeysetHandle> KeysetHandleBuilder::Build() {
+  if (build_called_) {
+      return util::Status(
+          absl::StatusCode::kFailedPrecondition,
+          "KeysetHandleBuilder::Build may only be called once");
+  }
+  build_called_ = true;
+  Keyset keyset;
+  absl::optional<int> primary_id = absl::nullopt;
+
+  util::Status assigned_ids_status = CheckIdAssignments();
+  if (!assigned_ids_status.ok()) return assigned_ids_status;
+
+  std::set<int> ids_so_far;
+  for (KeysetHandleBuilder::Entry& entry : entries_) {
+    util::StatusOr<int> id =
+        NextIdFromKeyIdStrategy(entry.GetKeyIdStrategy(), ids_so_far);
+    if (!id.ok()) return id.status();
+
+    if (ids_so_far.find(*id) != ids_so_far.end()) {
+      return util::Status(
+          absl::StatusCode::kAlreadyExists,
+          absl::StrFormat("Next id %d is already used in the keyset.", *id));
+    }
+    ids_so_far.insert(*id);
+
+    util::StatusOr<Keyset::Key> key = entry.CreateKeysetKey(*id);
+    if (!key.ok()) return key.status();
+
+    *keyset.add_key() = *key;
+    if (entry.IsPrimary()) {
+      if (primary_id.has_value()) {
+        return util::Status(
+            absl::StatusCode::kInternal,
+            "Primary is already set in this keyset (should never happen since "
+            "primary is cleared when a new primary is added).");
+      }
+      primary_id = *id;
+    }
+  }
+
+  if (!primary_id.has_value()) {
+    return util::Status(absl::StatusCode::kFailedPrecondition,
+                        "No primary set in this keyset.");
+  }
+  keyset.set_primary_key_id(*primary_id);
+  util::StatusOr<std::vector<std::shared_ptr<const KeysetHandle::Entry>>>
+      entries = KeysetHandle::GetEntriesFromKeyset(keyset);
+  return KeysetHandle(keyset, *std::move(entries));
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/keyset_handle_builder_test.cc b/cc/core/keyset_handle_builder_test.cc
new file mode 100644
index 0000000..3d29593
--- /dev/null
+++ b/cc/core/keyset_handle_builder_test.cc
@@ -0,0 +1,838 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/keyset_handle_builder.h"
+
+#include <memory>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/config/tink_config.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/legacy_proto_key.h"
+#include "tink/internal/legacy_proto_parameters.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/key_status.h"
+#include "tink/mac/aes_cmac_key.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/mac/mac_key_templates.h"
+#include "tink/partial_key_access.h"
+#include "tink/subtle/random.h"
+#include "tink/util/status.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_cmac.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::AddTinkKey;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesCmacParams;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+using ::testing::SizeIs;
+using ::testing::Test;
+
+class KeysetHandleBuilderTest : public Test {
+ protected:
+  void SetUp() override {
+    util::Status status = TinkConfig::Register();
+    ASSERT_TRUE(status.ok()) << status;
+  }
+};
+
+using KeysetHandleBuilderDeathTest = KeysetHandleBuilderTest;
+
+util::StatusOr<internal::LegacyProtoParameters> CreateLegacyProtoParameters(
+    KeyTemplate key_template) {
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(key_template);
+  if (!serialization.ok()) return serialization.status();
+
+  return internal::LegacyProtoParameters(*serialization);
+}
+
+TEST_F(KeysetHandleBuilderTest, BuildWithSingleKey) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+          /*id=*/123);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+  EXPECT_THAT(*handle, SizeIs(1));
+
+  EXPECT_THAT((*handle)[0].GetStatus(), Eq(KeyStatus::kEnabled));
+  EXPECT_THAT((*handle)[0].GetId(), Eq(123));
+  EXPECT_THAT((*handle)[0].IsPrimary(), IsTrue());
+  EXPECT_THAT((*handle)[0].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+}
+
+TEST_F(KeysetHandleBuilderTest, BuildWithMultipleKeys) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry0 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kDestroyed,
+          /*is_primary=*/false,
+          /*id=*/123);
+
+  KeysetHandleBuilder::Entry entry1 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+          /*id=*/456);
+
+  KeysetHandleBuilder::Entry entry2 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kDisabled,
+          /*is_primary=*/false, /*id=*/789);
+
+  util::StatusOr<KeysetHandle> handle = KeysetHandleBuilder()
+                                            .AddEntry(std::move(entry0))
+                                            .AddEntry(std::move(entry1))
+                                            .AddEntry(std::move(entry2))
+                                            .Build();
+  ASSERT_THAT(handle.status(), IsOk());
+  EXPECT_THAT(*handle, SizeIs(3));
+
+  EXPECT_THAT((*handle)[0].GetStatus(), Eq(KeyStatus::kDestroyed));
+  EXPECT_THAT((*handle)[0].GetId(), Eq(123));
+  EXPECT_THAT((*handle)[0].IsPrimary(), IsFalse());
+  EXPECT_THAT((*handle)[0].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+
+  EXPECT_THAT((*handle)[1].GetStatus(), Eq(KeyStatus::kEnabled));
+  EXPECT_THAT((*handle)[1].GetId(), Eq(456));
+  EXPECT_THAT((*handle)[1].IsPrimary(), IsTrue());
+  EXPECT_THAT((*handle)[1].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+
+  EXPECT_THAT((*handle)[2].GetStatus(), Eq(KeyStatus::kDisabled));
+  EXPECT_THAT((*handle)[2].GetId(), Eq(789));
+  EXPECT_THAT((*handle)[2].IsPrimary(), IsFalse());
+  EXPECT_THAT((*handle)[2].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+}
+
+TEST_F(KeysetHandleBuilderTest, BuildCopy) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry0 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kDestroyed,
+          /*is_primary=*/false,
+          /*id=*/123);
+
+  KeysetHandleBuilder::Entry entry1 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+          /*id=*/456);
+
+  KeysetHandleBuilder::Entry entry2 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kDisabled,
+          /*is_primary=*/false, /*id=*/789);
+
+  util::StatusOr<KeysetHandle> handle = KeysetHandleBuilder()
+                                            .AddEntry(std::move(entry0))
+                                            .AddEntry(std::move(entry1))
+                                            .AddEntry(std::move(entry2))
+                                            .Build();
+  ASSERT_THAT(handle.status(), IsOk());
+
+  util::StatusOr<KeysetHandle> copy = KeysetHandleBuilder(*handle).Build();
+  ASSERT_THAT(copy.status(), IsOk());
+  EXPECT_THAT(copy->size(), Eq(3));
+
+  EXPECT_THAT((*copy)[0].GetStatus(), Eq(KeyStatus::kDestroyed));
+  EXPECT_THAT((*copy)[0].GetId(), Eq(123));
+  EXPECT_THAT((*copy)[0].IsPrimary(), IsFalse());
+  EXPECT_THAT((*copy)[0].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+
+  EXPECT_THAT((*copy)[1].GetStatus(), Eq(KeyStatus::kEnabled));
+  EXPECT_THAT((*copy)[1].GetId(), Eq(456));
+  EXPECT_THAT((*copy)[1].IsPrimary(), IsTrue());
+  EXPECT_THAT((*copy)[1].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+
+  EXPECT_THAT((*copy)[2].GetStatus(), Eq(KeyStatus::kDisabled));
+  EXPECT_THAT((*copy)[2].GetId(), Eq(789));
+  EXPECT_THAT((*copy)[2].IsPrimary(), IsFalse());
+  EXPECT_THAT((*copy)[2].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+}
+
+TEST_F(KeysetHandleBuilderTest, IsPrimary) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(*parameters,
+                                                           KeyStatus::kEnabled,
+                                                           /*is_primary=*/false,
+                                                           /*id=*/123);
+  EXPECT_THAT(entry.IsPrimary(), IsFalse());
+
+  entry.SetPrimary();
+  EXPECT_THAT(entry.IsPrimary(), IsTrue());
+}
+
+TEST_F(KeysetHandleBuilderTest, SetAndGetStatus) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/false,
+          /*id=*/123);
+
+  entry.SetStatus(KeyStatus::kDisabled);
+  EXPECT_THAT(entry.GetStatus(), Eq(KeyStatus::kDisabled));
+  entry.SetStatus(KeyStatus::kEnabled);
+  EXPECT_THAT(entry.GetStatus(), Eq(KeyStatus::kEnabled));
+  entry.SetStatus(KeyStatus::kDestroyed);
+  EXPECT_THAT(entry.GetStatus(), Eq(KeyStatus::kDestroyed));
+}
+
+TEST_F(KeysetHandleBuilderTest, BuildWithRandomId) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry primary =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true);
+
+  KeysetHandleBuilder builder;
+  builder.AddEntry(std::move(primary));
+
+  int num_non_primary_entries = 1 << 16;
+  for (int i = 0; i < num_non_primary_entries; ++i) {
+    KeysetHandleBuilder::Entry non_primary =
+        KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+            *parameters, KeyStatus::kEnabled, /*is_primary=*/false);
+    builder.AddEntry(std::move(non_primary));
+  }
+
+  util::StatusOr<KeysetHandle> handle = builder.Build();
+  ASSERT_THAT(handle.status(), IsOk());
+
+  std::set<int> ids;
+  for (int i = 0; i < handle->size(); ++i) {
+    ids.insert((*handle)[i].GetId());
+  }
+  EXPECT_THAT(ids, SizeIs(num_non_primary_entries + 1));
+}
+
+TEST_F(KeysetHandleBuilderTest, BuildWithRandomIdAfterFixedId) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry fixed =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true, /*id=*/123);
+
+  KeysetHandleBuilder::Entry random =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/false);
+
+  util::StatusOr<KeysetHandle> handle = KeysetHandleBuilder()
+                                            .AddEntry(std::move(fixed))
+                                            .AddEntry(std::move(random))
+                                            .Build();
+  ASSERT_THAT(handle.status(), IsOk());
+
+  EXPECT_THAT(*handle, SizeIs(2));
+  EXPECT_THAT((*handle)[0].GetId(), Eq(123));
+}
+
+TEST_F(KeysetHandleBuilderTest, BuildWithFixedIdAfterRandomIdFails) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry random =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/false);
+
+  KeysetHandleBuilder::Entry fixed =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true, /*id=*/123);
+
+  util::StatusOr<KeysetHandle> handle = KeysetHandleBuilder()
+                                            .AddEntry(std::move(random))
+                                            .AddEntry(std::move(fixed))
+                                            .Build();
+  ASSERT_THAT(handle.status(), StatusIs(absl::StatusCode::kFailedPrecondition));
+}
+
+TEST_F(KeysetHandleBuilderDeathTest, AddEntryToAnotherBuilderCrashes) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true, /*id=*/123);
+
+  KeysetHandleBuilder builder0;
+  builder0.AddEntry(std::move(entry));
+  KeysetHandleBuilder builder1;
+  EXPECT_DEATH_IF_SUPPORTED(
+      builder1.AddEntry(std::move(builder0[0])),
+      "Keyset handle builder entry already added to a builder.");
+}
+
+TEST_F(KeysetHandleBuilderDeathTest, ReAddEntryToSameBuilderCrashes) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true, /*id=*/123);
+
+  KeysetHandleBuilder builder;
+  builder.AddEntry(std::move(entry));
+  EXPECT_DEATH_IF_SUPPORTED(
+      builder.AddEntry(std::move(builder[0])),
+      "Keyset handle builder entry already added to a builder.");
+}
+
+TEST_F(KeysetHandleBuilderDeathTest,
+       AddDereferencedEntryToAnotherBuilderCrashes) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true, /*id=*/123);
+
+  KeysetHandleBuilder builder0;
+  builder0.AddEntry(std::move(entry));
+  KeysetHandleBuilder builder1;
+  EXPECT_DEATH_IF_SUPPORTED(
+      builder1.AddEntry(std::move(*&(builder0[0]))),
+      "Keyset handle builder entry already added to a builder.");
+}
+
+TEST_F(KeysetHandleBuilderTest, RemoveEntry) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry0 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/false, /*id=*/123);
+
+  KeysetHandleBuilder::Entry entry1 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true, /*id=*/456);
+
+  util::StatusOr<KeysetHandle> handle0 = KeysetHandleBuilder()
+                                             .AddEntry(std::move(entry0))
+                                             .AddEntry(std::move(entry1))
+                                             .Build();
+  ASSERT_THAT(handle0.status(), IsOk());
+  ASSERT_THAT(*handle0, SizeIs(2));
+
+  util::StatusOr<KeysetHandle> handle1 =
+      KeysetHandleBuilder(*handle0).RemoveEntry(0).Build();
+  ASSERT_THAT(handle1.status(), IsOk());
+  ASSERT_THAT(*handle1, SizeIs(1));
+
+  EXPECT_THAT((*handle1)[0].GetStatus(), Eq(KeyStatus::kEnabled));
+  EXPECT_THAT((*handle1)[0].GetId(), Eq(456));
+  EXPECT_THAT((*handle1)[0].IsPrimary(), IsTrue());
+  EXPECT_THAT((*handle1)[0].GetKey()->GetParameters().HasIdRequirement(),
+              IsTrue());
+}
+
+TEST_F(KeysetHandleBuilderDeathTest, RemoveOutofRangeIndexEntryCrashes) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true, /*id=*/123);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+  ASSERT_THAT(*handle, SizeIs(1));
+
+  EXPECT_DEATH_IF_SUPPORTED(
+      KeysetHandleBuilder(*handle).RemoveEntry(1),
+      "Keyset handle builder entry removal index out of range.");
+}
+
+TEST_F(KeysetHandleBuilderTest, Size) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry0 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kDestroyed,
+          /*is_primary=*/false,
+          /*id=*/123);
+
+  KeysetHandleBuilder::Entry entry1 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+          /*id=*/456);
+
+  KeysetHandleBuilder builder;
+  ASSERT_THAT(builder, SizeIs(0));
+  builder.AddEntry(std::move(entry0));
+  ASSERT_THAT(builder, SizeIs(1));
+  builder.AddEntry(std::move(entry1));
+  EXPECT_THAT(builder, SizeIs(2));
+}
+
+TEST_F(KeysetHandleBuilderTest, NoPrimaryFails) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry0 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/false,
+          /*id=*/123);
+
+  KeysetHandleBuilder::Entry entry1 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/false,
+          /*id=*/456);
+
+  util::StatusOr<KeysetHandle> handle = KeysetHandleBuilder()
+                                            .AddEntry(std::move(entry0))
+                                            .AddEntry(std::move(entry1))
+                                            .Build();
+  ASSERT_THAT(handle.status(), StatusIs(absl::StatusCode::kFailedPrecondition));
+}
+
+TEST_F(KeysetHandleBuilderTest, RemovePrimaryFails) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry0 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+          /*id=*/123);
+
+  KeysetHandleBuilder::Entry entry1 =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/false,
+          /*id=*/456);
+
+  util::StatusOr<KeysetHandle> handle = KeysetHandleBuilder()
+                                            .AddEntry(std::move(entry0))
+                                            .AddEntry(std::move(entry1))
+                                            .RemoveEntry(0)
+                                            .Build();
+  ASSERT_THAT(handle.status(), StatusIs(absl::StatusCode::kFailedPrecondition));
+}
+
+TEST_F(KeysetHandleBuilderTest, AddPrimaryClearsOtherPrimary) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder builder;
+  builder.AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+      *parameters, KeyStatus::kEnabled,
+      /*is_primary=*/true,
+      /*id=*/123));
+  builder.AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+      *parameters, KeyStatus::kEnabled,
+      /*is_primary=*/true,
+      /*id=*/456));
+
+  ASSERT_THAT(builder[0].IsPrimary(), IsFalse());
+  ASSERT_THAT(builder[1].IsPrimary(), IsTrue());
+}
+
+TEST_F(KeysetHandleBuilderTest, NoIdStrategySucceeds) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle, IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, DuplicateId) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled,
+              /*is_primary=*/true,
+              /*id=*/123))
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled,
+              /*is_primary=*/false,
+              /*id=*/123))
+          .Build();
+  ASSERT_THAT(handle.status(), StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST_F(KeysetHandleBuilderTest, CreateBuilderEntryFromParams) {
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromParams(
+          absl::make_unique<AesCmacParameters>(std::move(*params)),
+          KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, CreateBuilderEntryFromLegacyKey) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::DISABLED,
+             KeyData::SYMMETRIC, &keyset);
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          key.key_data().type_url(),
+          RestrictedData(key.SerializeAsString(),
+                         InsecureSecretKeyAccess::Get()),
+          key.key_data().key_material_type(), key.output_prefix_type(),
+          key.key_id());
+
+  util::StatusOr<internal::LegacyProtoKey> proto_key =
+      internal::LegacyProtoKey::Create(*serialization,
+                                       InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(proto_key.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry = KeysetHandleBuilder::Entry::CreateFromKey(
+      absl::make_unique<internal::LegacyProtoKey>(std::move(*proto_key)),
+      KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, CreateBuilderEntryFromKey) {
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(32);
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *params, secret, /*id_requirement=*/123, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry = KeysetHandleBuilder::Entry::CreateFromKey(
+      absl::make_unique<AesCmacKey>(std::move(*key)), KeyStatus::kEnabled,
+      /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, CreateBuilderEntryFromCopyableKey) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::DISABLED,
+             KeyData::SYMMETRIC, &keyset);
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          key.key_data().type_url(),
+          RestrictedData(key.SerializeAsString(),
+                         InsecureSecretKeyAccess::Get()),
+          key.key_data().key_material_type(), key.output_prefix_type(),
+          key.key_id());
+
+  util::StatusOr<internal::LegacyProtoKey> proto_key =
+      internal::LegacyProtoKey::Create(*serialization,
+                                       InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(proto_key.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableKey(
+          *proto_key, KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, CreateBuilderEntryFromParameters) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromParams(
+          absl::make_unique<internal::LegacyProtoParameters>(*parameters),
+          KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, CreateBuilderEntryFromCopyableParameters) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, UsePrimitiveFromLegacyProtoParams) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Mac>> mac = handle->GetPrimitive<Mac>();
+  ASSERT_THAT(mac.status(), IsOk());
+  util::StatusOr<std::string> tag = (*mac)->ComputeMac("some input");
+  ASSERT_THAT(tag.status(), IsOk());
+  util::Status verified = (*mac)->VerifyMac(*tag, "some input");
+  EXPECT_THAT(verified, IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, UsePrimitiveFromParams) {
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromParams(
+          absl::make_unique<AesCmacParameters>(std::move(*params)),
+          KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Mac>> mac = handle->GetPrimitive<Mac>();
+  ASSERT_THAT(mac.status(), IsOk());
+  util::StatusOr<std::string> tag = (*mac)->ComputeMac("some input");
+  ASSERT_THAT(tag.status(), IsOk());
+  util::Status verified = (*mac)->VerifyMac(*tag, "some input");
+  EXPECT_THAT(verified, IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, UsePrimitiveFromLegacyProtoKey) {
+  AesCmacParams params;
+  params.set_tag_size(16);
+  google::crypto::tink::AesCmacKey key;
+  *key.mutable_params() = params;
+  key.set_version(0);
+  key.set_key_value(subtle::Random::GetRandomBytes(32));
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey",
+          RestrictedData(key.SerializeAsString(),
+                         InsecureSecretKeyAccess::Get()),
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/123);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<internal::LegacyProtoKey> proto_key =
+      internal::LegacyProtoKey::Create(*serialization,
+                                       InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(proto_key.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableKey(
+          *proto_key, KeyStatus::kEnabled, /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Mac>> mac = handle->GetPrimitive<Mac>();
+  ASSERT_THAT(mac.status(), IsOk());
+  util::StatusOr<std::string> tag = (*mac)->ComputeMac("some input");
+  ASSERT_THAT(tag.status(), IsOk());
+  util::Status verified = (*mac)->VerifyMac(*tag, "some input");
+  EXPECT_THAT(verified, IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, UsePrimitiveFromKey) {
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(32);
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *params, secret, /*id_requirement=*/123, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry = KeysetHandleBuilder::Entry::CreateFromKey(
+      absl::make_unique<AesCmacKey>(std::move(*key)), KeyStatus::kEnabled,
+      /*is_primary=*/true);
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder().AddEntry(std::move(entry)).Build();
+  ASSERT_THAT(handle.status(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Mac>> mac = handle->GetPrimitive<Mac>();
+  ASSERT_THAT(mac.status(), IsOk());
+  util::StatusOr<std::string> tag = (*mac)->ComputeMac("some input");
+  ASSERT_THAT(tag.status(), IsOk());
+  util::Status verified = (*mac)->VerifyMac(*tag, "some input");
+  EXPECT_THAT(verified, IsOk());
+}
+
+TEST_F(KeysetHandleBuilderTest, BuildTwiceFails) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters(MacKeyTemplates::AesCmac());
+  ASSERT_THAT(parameters.status(), IsOk());
+
+  KeysetHandleBuilder::Entry entry =
+      KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+          *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+          /*id=*/123);
+
+  KeysetHandleBuilder builder;
+  builder.AddEntry(std::move(entry));
+
+  EXPECT_THAT(builder.Build(), IsOk());
+  EXPECT_THAT(builder.Build().status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+}
+
+TEST_F(KeysetHandleBuilderTest, UsePrimitivesFromSplitKeyset) {
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *params, KeyStatus::kEnabled, /*is_primary=*/false))
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *params, KeyStatus::kEnabled, /*is_primary=*/true))
+          .Build();
+  ASSERT_THAT(handle, IsOkAndHolds(SizeIs(2)));
+
+  util::StatusOr<KeysetHandle> handle0 =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromKey(
+              (*handle)[0].GetKey(), KeyStatus::kEnabled,
+              /*is_primary=*/true))
+          .Build();
+  ASSERT_THAT(handle0, IsOkAndHolds(SizeIs(1)));
+  ASSERT_THAT((*handle)[0].GetId(), Eq((*handle0)[0].GetId()));
+
+  util::StatusOr<KeysetHandle> handle1 =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromKey(
+              (*handle)[1].GetKey(), KeyStatus::kEnabled,
+              /*is_primary=*/true))
+          .Build();
+  ASSERT_THAT(handle1, IsOkAndHolds(SizeIs(1)));
+  ASSERT_THAT((*handle)[1].GetId(), Eq((*handle1)[0].GetId()));
+
+  util::StatusOr<std::unique_ptr<Mac>> mac0 = handle0->GetPrimitive<Mac>();
+  ASSERT_THAT(mac0.status(), IsOk());
+  util::StatusOr<std::string> tag0 = (*mac0)->ComputeMac("some input");
+  ASSERT_THAT(tag0.status(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Mac>> mac1 = handle1->GetPrimitive<Mac>();
+  ASSERT_THAT(mac1.status(), IsOk());
+  util::StatusOr<std::string> tag1 = (*mac1)->ComputeMac("some other input");
+  ASSERT_THAT(tag1.status(), IsOk());
+
+  // Use original keyset to verify tags computed from new keysets.
+  util::StatusOr<std::unique_ptr<Mac>> mac = handle->GetPrimitive<Mac>();
+  ASSERT_THAT(mac.status(), IsOk());
+  EXPECT_THAT((*mac)->VerifyMac(*tag0, "some input"), IsOk());
+  EXPECT_THAT((*mac)->VerifyMac(*tag1, "some other input"), IsOk());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/keyset_handle_test.cc b/cc/core/keyset_handle_test.cc
index ae81673..539dc2c 100644
--- a/cc/core/keyset_handle_test.cc
+++ b/cc/core/keyset_handle_test.cc
@@ -33,105 +33,123 @@
 #include "tink/binary_keyset_reader.h"
 #include "tink/binary_keyset_writer.h"
 #include "tink/cleartext_keyset_handle.h"
+#include "tink/config/fips_140_2.h"
+#include "tink/config/internal/global_registry.h"
+#include "tink/config/key_gen_fips_140_2.h"
 #include "tink/config/tink_config.h"
 #include "tink/core/key_manager_impl.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/internal/key_gen_configuration_impl.h"
 #include "tink/json_keyset_reader.h"
 #include "tink/json_keyset_writer.h"
+#include "tink/key_gen_configuration.h"
+#include "tink/key_status.h"
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
 #include "tink/signature/ecdsa_sign_key_manager.h"
 #include "tink/signature/signature_key_templates.h"
-#include "tink/util/protobuf_helper.h"
 #include "tink/util/status.h"
 #include "tink/util/test_keyset_handle.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
+#include "proto/aes_gcm_siv.pb.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
 namespace tink {
 
-using crypto::tink::TestKeysetHandle;
-using crypto::tink::test::AddKeyData;
-using crypto::tink::test::AddLegacyKey;
-using crypto::tink::test::AddRawKey;
-using crypto::tink::test::AddTinkKey;
-using crypto::tink::test::DummyAead;
-using crypto::tink::test::IsOk;
-using crypto::tink::test::StatusIs;
-using google::crypto::tink::EcdsaKeyFormat;
-using google::crypto::tink::EncryptedKeyset;
-using google::crypto::tink::KeyData;
-using google::crypto::tink::Keyset;
-using google::crypto::tink::KeyStatusType;
-using google::crypto::tink::KeyTemplate;
-using google::crypto::tink::OutputPrefixType;
+using ::crypto::tink::TestKeysetHandle;
+using ::crypto::tink::test::AddKeyData;
+using ::crypto::tink::test::AddLegacyKey;
+using ::crypto::tink::test::AddRawKey;
+using ::crypto::tink::test::AddTinkKey;
+using ::crypto::tink::test::DummyAead;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::AesGcmSivKey;
+using ::google::crypto::tink::EcdsaKeyFormat;
+using ::google::crypto::tink::EncryptedKeyset;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
 using ::testing::_;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
 using ::testing::Not;
+using ::testing::SizeIs;
 
 namespace {
 
 class KeysetHandleTest : public ::testing::Test {
  protected:
   void SetUp() override {
+    Registry::Reset();
     auto status = TinkConfig::Register();
     ASSERT_TRUE(status.ok()) << status;
+
+    internal::UnSetFipsRestricted();
   }
 };
 
-// Dummy key factory that is required to create a key manager.
-class DummyAeadKeyFactory : public KeyFactory {
+using KeysetHandleDeathTest = KeysetHandleTest;
+
+// Fake AEAD key type manager for testing.
+class FakeAeadKeyManager
+    : public KeyTypeManager<AesGcmKey, AesGcmKeyFormat, List<Aead>> {
  public:
-  explicit DummyAeadKeyFactory(absl::string_view key_type)
-      : key_type_(key_type) {}
+  class AeadFactory : public PrimitiveFactory<Aead> {
+   public:
+    explicit AeadFactory(absl::string_view key_type) : key_type_(key_type) {}
 
-  util::StatusOr<std::unique_ptr<portable_proto::MessageLite>> NewKey(
-      const portable_proto::MessageLite& key_format) const override {
-    return util::Status(absl::StatusCode::kUnimplemented, "Unimplemented");
-  }
+    util::StatusOr<std::unique_ptr<Aead>> Create(
+        const AesGcmKey& key) const override {
+      return {absl::make_unique<DummyAead>(key_type_)};
+    }
 
-  util::StatusOr<std::unique_ptr<portable_proto::MessageLite>> NewKey(
-      absl::string_view serialized_key_format) const override {
-    return util::Status(absl::StatusCode::kUnimplemented, "Unimplemented");
-  }
+   private:
+    const std::string key_type_;
+  };
 
-  util::StatusOr<std::unique_ptr<KeyData>> NewKeyData(
-      absl::string_view serialized_key_format) const override {
-    auto key_data = absl::make_unique<KeyData>();
-    key_data->set_type_url(key_type_);
-    std::string serialized_key_format_str(serialized_key_format);
-    key_data->set_value(serialized_key_format_str);
-    return std::move(key_data);
-  }
-
- private:
-  const std::string key_type_;
-};
-
-// Fake Aead key manager for testing.
-class FakeAeadKeyManager : public KeyManager<Aead> {
- public:
   explicit FakeAeadKeyManager(absl::string_view key_type)
-      : key_type_(key_type), key_factory_(key_type) {}
+      : KeyTypeManager(absl::make_unique<AeadFactory>(key_type)),
+        key_type_(key_type) {}
 
-  util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
-      const KeyData& key) const override {
-    return {absl::make_unique<DummyAead>(key_type_)};
-  }
-
-  util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
-      const portable_proto::MessageLite& key) const override {
-    return util::Status(absl::StatusCode::kUnknown,
-                        "DummyAeadKeyFactory cannot construct an aead");
+  google::crypto::tink::KeyData::KeyMaterialType key_material_type()
+      const override {
+    return google::crypto::tink::KeyData::SYMMETRIC;
   }
 
   uint32_t get_version() const override { return 0; }
+
   const std::string& get_key_type() const override { return key_type_; }
-  const KeyFactory& get_key_factory() const override { return key_factory_; }
+
+  crypto::tink::util::Status ValidateKey(const AesGcmKey& key) const override {
+    return util::OkStatus();
+  }
+
+  crypto::tink::util::Status ValidateKeyFormat(
+      const AesGcmKeyFormat& key_format) const override {
+    return util::OkStatus();
+  }
+
+  crypto::tink::util::StatusOr<AesGcmKey> CreateKey(
+      const AesGcmKeyFormat& key_format) const override {
+    return AesGcmKey();
+  }
+
+  crypto::tink::util::StatusOr<AesGcmKey> DeriveKey(
+      const AesGcmKeyFormat& key_format,
+      InputStream* input_stream) const override {
+    return AesGcmKey();
+  }
 
  private:
   const std::string key_type_;
-  const DummyAeadKeyFactory key_factory_;
 };
 
 class MockAeadPrimitiveWrapper : public PrimitiveWrapper<Aead, Aead> {
@@ -145,9 +163,9 @@
 Keyset GetTestKeyset() {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
   return keyset;
@@ -157,9 +175,9 @@
 Keyset GetPublicTestKeyset() {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PUBLIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::REMOTE, &keyset);
   keyset.set_primary_key_id(42);
   return keyset;
@@ -168,9 +186,9 @@
 TEST_F(KeysetHandleTest, ReadEncryptedKeysetBinary) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
 
@@ -265,12 +283,12 @@
   Registry::Reset();
   ASSERT_THAT(Registry::RegisterPrimitiveWrapper(std::move(primitive_wrapper)),
               IsOk());
-  ASSERT_THAT(Registry::RegisterKeyManager(
-                  absl::make_unique<FakeAeadKeyManager>("some key type"),
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<FakeAeadKeyManager>("some_key_type"),
                   /*new_key_allowed=*/true),
               IsOk());
-  ASSERT_THAT(Registry::RegisterKeyManager(
-                  absl::make_unique<FakeAeadKeyManager>("some other key type"),
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<FakeAeadKeyManager>("some_other_key_type"),
                   /*new_key_allowed=*/true),
               IsOk());
 
@@ -283,9 +301,9 @@
 TEST_F(KeysetHandleTest, ReadEncryptedKeysetJson) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
 
@@ -366,9 +384,9 @@
   // Prepare a valid keyset handle
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
   auto reader =
@@ -406,9 +424,9 @@
 TEST_F(KeysetHandleTest, ReadEncryptedKeysetWithAssociatedDataGoodKeyset) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
 
@@ -458,12 +476,12 @@
   Registry::Reset();
   ASSERT_THAT(Registry::RegisterPrimitiveWrapper(std::move(primitive_wrapper)),
               IsOk());
-  ASSERT_THAT(Registry::RegisterKeyManager(
-                  absl::make_unique<FakeAeadKeyManager>("some key type"),
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<FakeAeadKeyManager>("some_key_type"),
                   /*new_key_allowed=*/true),
               IsOk());
-  ASSERT_THAT(Registry::RegisterKeyManager(
-                  absl::make_unique<FakeAeadKeyManager>("some other key type"),
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<FakeAeadKeyManager>("some_other_key_type"),
                   /*new_key_allowed=*/true),
               IsOk());
 
@@ -476,9 +494,9 @@
 TEST_F(KeysetHandleTest, ReadEncryptedKeysetWithAssociatedDataWrongAad) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
   DummyAead aead("dummy aead 42");
@@ -497,9 +515,9 @@
 TEST_F(KeysetHandleTest, ReadEncryptedKeysetWithAssociatedDataEmptyAad) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
   DummyAead aead("dummy aead 42");
@@ -518,9 +536,9 @@
   // Prepare a valid keyset handle
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
   auto reader =
@@ -556,55 +574,94 @@
   EXPECT_EQ(absl::StatusCode::kInvalidArgument, status.code());
 }
 
-TEST_F(KeysetHandleTest, GenerateNewKeysetHandle) {
-  const google::crypto::tink::KeyTemplate* key_templates[] = {
+TEST_F(KeysetHandleTest, GenerateNew) {
+  const google::crypto::tink::KeyTemplate* templates[] = {
       &AeadKeyTemplates::Aes128Gcm(),
       &AeadKeyTemplates::Aes256Gcm(),
       &AeadKeyTemplates::Aes128CtrHmacSha256(),
       &AeadKeyTemplates::Aes256CtrHmacSha256(),
   };
-  for (auto templ : key_templates) {
-    auto handle_result = KeysetHandle::GenerateNew(*templ);
-    EXPECT_TRUE(handle_result.ok())
-        << "Failed for template:\n " << templ->SerializeAsString()
-        << "\n with status: "<< handle_result.status();
+  KeyGenConfiguration config;
+  ASSERT_THAT(internal::KeyGenConfigurationImpl::SetGlobalRegistryMode(config),
+              IsOk());
+  for (auto templ : templates) {
+    EXPECT_THAT(KeysetHandle::GenerateNew(*templ).status(), IsOk());
+    EXPECT_THAT(KeysetHandle::GenerateNew(*templ, config).status(), IsOk());
   }
 }
 
+TEST_F(KeysetHandleTest, GenerateNewWithBespokeConfig) {
+  KeyGenConfiguration config;
+  EXPECT_THAT(
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(), config).status(),
+      StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(internal::KeyGenConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), config),
+              IsOk());
+  EXPECT_THAT(
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(), config).status(),
+      IsOk());
+}
+
+TEST_F(KeysetHandleTest, GenerateNewWithGlobalRegistryConfig) {
+  KeyGenConfiguration config;
+  ASSERT_THAT(internal::KeyGenConfigurationImpl::SetGlobalRegistryMode(config),
+              IsOk());
+  EXPECT_THAT(KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(), config),
+              IsOk());
+}
+
 TEST_F(KeysetHandleTest, GenerateNewWithAnnotations) {
   const absl::flat_hash_map<std::string, std::string> kAnnotations = {
       {"key1", "value1"}, {"key2", "value2"}};
 
-  // The template used doesn't make any different w.r.t. annotations.
-  util::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+  // `handle` depends on the global registry.
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
       KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(), kAnnotations);
-  ASSERT_THAT(keyset_handle, IsOk());
-  auto primitive_wrapper = absl::make_unique<MockAeadPrimitiveWrapper>();
-  absl::flat_hash_map<std::string, std::string> generated_annotations;
-  EXPECT_CALL(*primitive_wrapper, Wrap(_))
-      .WillOnce(
-          [&generated_annotations](
-              std::unique_ptr<PrimitiveSet<Aead>> generated_primitive_set) {
-            generated_annotations = generated_primitive_set->get_annotations();
-            std::unique_ptr<Aead> aead = absl::make_unique<DummyAead>("");
-            return aead;
-          });
-  Registry::Reset();
-  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(std::move(primitive_wrapper)),
-              IsOk());
-  ASSERT_THAT(Registry::RegisterKeyManager(
-                  absl::make_unique<FakeAeadKeyManager>(
-                      "type.googleapis.com/google.crypto.tink.AesGcmKey"),
-                  true),
-              IsOk());
+  ASSERT_THAT(handle, IsOk());
 
-  EXPECT_THAT((*keyset_handle)->GetPrimitive<Aead>(), IsOk());
-  EXPECT_EQ(generated_annotations, kAnnotations);
-  // This is needed to cleanup mocks.
-  Registry::Reset();
+  // `config_handle` uses a config that depends on the global registry.
+  KeyGenConfiguration config;
+  ASSERT_THAT(internal::KeyGenConfigurationImpl::SetGlobalRegistryMode(config),
+              IsOk());
+  util::StatusOr<std::unique_ptr<KeysetHandle>> config_handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(), config,
+                                kAnnotations);
+  ASSERT_THAT(config_handle, IsOk());
+
+  for (KeysetHandle h : {**handle, **config_handle}) {
+    auto primitive_wrapper = absl::make_unique<MockAeadPrimitiveWrapper>();
+    absl::flat_hash_map<std::string, std::string> generated_annotations;
+    EXPECT_CALL(*primitive_wrapper, Wrap(_))
+        .WillOnce(
+            [&generated_annotations](
+                std::unique_ptr<PrimitiveSet<Aead>> generated_primitive_set) {
+              generated_annotations =
+                  generated_primitive_set->get_annotations();
+              std::unique_ptr<Aead> aead = absl::make_unique<DummyAead>("");
+              return aead;
+            });
+
+    Registry::Reset();
+    ASSERT_THAT(
+        Registry::RegisterPrimitiveWrapper(std::move(primitive_wrapper)),
+        IsOk());
+    ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                    absl::make_unique<FakeAeadKeyManager>(
+                        "type.googleapis.com/google.crypto.tink.AesGcmKey"),
+                    /*new_key_allowed=*/true),
+                IsOk());
+
+    EXPECT_THAT(h.GetPrimitive<Aead>(), IsOk());
+    EXPECT_EQ(generated_annotations, kAnnotations);
+
+    // This is needed to cleanup mocks.
+    Registry::Reset();
+  }
 }
 
-TEST_F(KeysetHandleTest, GenerateNewKeysetHandleErrors) {
+TEST_F(KeysetHandleTest, GenerateNewErrors) {
   KeyTemplate templ;
   templ.set_type_url("type.googleapis.com/some.unknown.KeyType");
   templ.set_output_prefix_type(OutputPrefixType::TINK);
@@ -621,7 +678,6 @@
   EXPECT_FALSE(handle_result.ok());
 }
 
-
 void CompareKeyMetadata(const Keyset::Key& expected,
                         const Keyset::Key& actual) {
   EXPECT_EQ(expected.status(), actual.status());
@@ -630,9 +686,9 @@
 }
 
 TEST_F(KeysetHandleTest, GetPublicKeysetHandle) {
-  { // A keyset with a single key.
-    auto handle_result = KeysetHandle::GenerateNew(
-        SignatureKeyTemplates::EcdsaP256());
+  {  // A keyset with a single key.
+    auto handle_result =
+        KeysetHandle::GenerateNew(SignatureKeyTemplates::EcdsaP256());
     ASSERT_TRUE(handle_result.ok()) << handle_result.status();
     auto handle = std::move(handle_result.value());
     auto public_handle_result = handle->GetPublicKeysetHandle();
@@ -646,7 +702,7 @@
     EXPECT_EQ(KeyData::ASYMMETRIC_PUBLIC,
               public_keyset.key(0).key_data().key_material_type());
   }
-  { // A keyset with multiple keys.
+  {  // A keyset with multiple keys.
     EcdsaSignKeyManager key_manager;
     Keyset keyset;
     int key_count = 3;
@@ -684,9 +740,9 @@
 }
 
 TEST_F(KeysetHandleTest, GetPublicKeysetHandleErrors) {
-  { // A keyset with a single key.
-    auto handle_result = KeysetHandle::GenerateNew(
-        AeadKeyTemplates::Aes128Eax());
+  {  // A keyset with a single key.
+    auto handle_result =
+        KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Eax());
     ASSERT_TRUE(handle_result.ok()) << handle_result.status();
     auto handle = std::move(handle_result.value());
     auto public_handle_result = handle->GetPublicKeysetHandle();
@@ -694,7 +750,7 @@
     EXPECT_PRED_FORMAT2(testing::IsSubstring, "ASYMMETRIC_PRIVATE",
                         std::string(public_handle_result.status().message()));
   }
-  { // A keyset with multiple keys.
+  {  // A keyset with multiple keys.
     Keyset keyset;
 
     EcdsaKeyFormat ecdsa_key_format;
@@ -760,6 +816,84 @@
   EXPECT_EQ(aead->Decrypt(raw_encryption, aad).value(), plaintext);
 }
 
+TEST_F(KeysetHandleTest, GetPrimitiveWithBespokeConfigSucceeds) {
+  KeyGenConfiguration key_gen_config;
+  ASSERT_THAT(internal::KeyGenConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), key_gen_config),
+              IsOk());
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(), key_gen_config);
+  ASSERT_THAT(handle, IsOk());
+
+  Configuration config;
+  ASSERT_THAT(internal::ConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), config),
+              IsOk());
+  ASSERT_THAT(internal::ConfigurationImpl::AddPrimitiveWrapper(
+                  absl::make_unique<AeadWrapper>(), config),
+              IsOk());
+
+  EXPECT_THAT((*handle)->GetPrimitive<Aead>(config).status(), IsOk());
+}
+
+TEST_F(KeysetHandleTest, GetPrimitiveWithBespokeConfigFailsIfEmpty) {
+  KeyGenConfiguration key_gen_config;
+  ASSERT_THAT(internal::KeyGenConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), key_gen_config),
+              IsOk());
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(), key_gen_config);
+  ASSERT_THAT(handle, IsOk());
+
+  Configuration config;
+  EXPECT_THAT((*handle)->GetPrimitive<Aead>(config).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST_F(KeysetHandleTest, GetPrimitiveWithGlobalRegistryConfig) {
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(),
+                                internal::KeyGenConfigGlobalRegistry());
+  ASSERT_THAT(handle, IsOk());
+
+  // TODO(b/265705174): Replace with ConfigGlobalRegistry instance.
+  Configuration config;
+  ASSERT_THAT(internal::ConfigurationImpl::SetGlobalRegistryMode(config),
+              IsOk());
+  EXPECT_THAT((*handle)->GetPrimitive<Aead>(config), IsOk());
+}
+
+TEST_F(KeysetHandleTest, GetPrimitiveWithConfigFips1402) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm(),
+                                KeyGenConfigFips140_2());
+  ASSERT_THAT(handle, IsOk());
+  EXPECT_THAT((*handle)->GetPrimitive<Aead>(ConfigFips140_2()), IsOk());
+}
+
+TEST_F(KeysetHandleTest, GetPrimitiveWithConfigFips1402FailsWithNonFipsHandle) {
+  if (!internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only test in FIPS mode";
+  }
+
+  Keyset keyset;
+  AesGcmSivKey key_proto;
+  *key_proto.mutable_key_value() = subtle::Random::GetRandomBytes(16);
+  test::AddTinkKey(AeadKeyTemplates::Aes256GcmSiv().type_url(), /*key_id=*/13,
+                   key_proto, KeyStatusType::ENABLED, KeyData::SYMMETRIC,
+                   &keyset);
+  keyset.set_primary_key_id(13);
+
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  EXPECT_THAT(handle->GetPrimitive<Aead>(ConfigFips140_2()).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
 // Tests that GetPrimitive(nullptr) fails with a non-ok status.
 TEST_F(KeysetHandleTest, GetPrimitiveNullptrKeyManager) {
   Keyset keyset;
@@ -803,9 +937,9 @@
 TEST_F(KeysetHandleTest, ReadNoSecret) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PUBLIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::REMOTE, &keyset);
   keyset.set_primary_key_id(42);
   auto handle_result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
@@ -841,12 +975,12 @@
   Registry::Reset();
   ASSERT_THAT(Registry::RegisterPrimitiveWrapper(std::move(primitive_wrapper)),
               IsOk());
-  ASSERT_THAT(Registry::RegisterKeyManager(
-                  absl::make_unique<FakeAeadKeyManager>("some key type"),
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<FakeAeadKeyManager>("some_key_type"),
                   /*new_key_allowed=*/true),
               IsOk());
-  ASSERT_THAT(Registry::RegisterKeyManager(
-                  absl::make_unique<FakeAeadKeyManager>("some other key type"),
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<FakeAeadKeyManager>("some_other_key_type"),
                   /*new_key_allowed=*/true),
               IsOk());
 
@@ -859,7 +993,7 @@
 TEST_F(KeysetHandleTest, ReadNoSecretFailForTypeUnknown) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::UNKNOWN_KEYMATERIAL, &keyset);
   keyset.set_primary_key_id(42);
   auto result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
@@ -869,7 +1003,7 @@
 TEST_F(KeysetHandleTest, ReadNoSecretFailForTypeSymmetric) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
   auto result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
@@ -879,7 +1013,7 @@
 TEST_F(KeysetHandleTest, ReadNoSecretFailForTypeAssymmetricPrivate) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PRIVATE, &keyset);
   keyset.set_primary_key_id(42);
   auto result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
@@ -889,13 +1023,13 @@
 TEST_F(KeysetHandleTest, ReadNoSecretFailForHidden) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PUBLIC, &keyset);
   for (int i = 0; i < 10; ++i) {
     AddTinkKey(absl::StrCat("more key type", i), i, key, KeyStatusType::ENABLED,
                KeyData::ASYMMETRIC_PUBLIC, &keyset);
   }
-  AddRawKey("some other key type", 10, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 10, key, KeyStatusType::ENABLED,
             KeyData::ASYMMETRIC_PRIVATE, &keyset);
   for (int i = 0; i < 10; ++i) {
     AddRawKey(absl::StrCat("more key type", i + 100), i + 100, key,
@@ -916,9 +1050,9 @@
 TEST_F(KeysetHandleTest, WriteNoSecret) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PUBLIC, &keyset);
-  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 711, key, KeyStatusType::ENABLED,
             KeyData::REMOTE, &keyset);
   keyset.set_primary_key_id(42);
 
@@ -935,7 +1069,7 @@
 TEST_F(KeysetHandleTest, WriteNoSecretFailForTypeUnknown) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::UNKNOWN_KEYMATERIAL, &keyset);
   keyset.set_primary_key_id(42);
 
@@ -952,7 +1086,7 @@
 TEST_F(KeysetHandleTest, WriteNoSecretFailForTypeSymmetric) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::SYMMETRIC, &keyset);
   keyset.set_primary_key_id(42);
 
@@ -969,7 +1103,7 @@
 TEST_F(KeysetHandleTest, WriteNoSecretFailForTypeAssymmetricPrivate) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PRIVATE, &keyset);
   keyset.set_primary_key_id(42);
 
@@ -986,13 +1120,13 @@
 TEST_F(KeysetHandleTest, WriteNoSecretFailForHidden) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PUBLIC, &keyset);
   for (int i = 0; i < 10; ++i) {
     AddTinkKey(absl::StrCat("more key type", i), i, key, KeyStatusType::ENABLED,
                KeyData::ASYMMETRIC_PUBLIC, &keyset);
   }
-  AddRawKey("some other key type", 10, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 10, key, KeyStatusType::ENABLED,
             KeyData::ASYMMETRIC_PRIVATE, &keyset);
   for (int i = 0; i < 10; ++i) {
     AddRawKey(absl::StrCat("more key type", i + 100), i + 100, key,
@@ -1014,13 +1148,13 @@
 TEST_F(KeysetHandleTest, GetKeysetInfo) {
   Keyset keyset;
   Keyset::Key key;
-  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+  AddTinkKey("some_key_type", 42, key, KeyStatusType::ENABLED,
              KeyData::ASYMMETRIC_PUBLIC, &keyset);
   for (int i = 0; i < 10; ++i) {
     AddTinkKey(absl::StrCat("more key type", i), i, key, KeyStatusType::ENABLED,
                KeyData::ASYMMETRIC_PUBLIC, &keyset);
   }
-  AddRawKey("some other key type", 10, key, KeyStatusType::ENABLED,
+  AddRawKey("some_other_key_type", 10, key, KeyStatusType::ENABLED,
             KeyData::ASYMMETRIC_PRIVATE, &keyset);
   for (int i = 0; i < 10; ++i) {
     AddRawKey(absl::StrCat("more key type", i + 100), i + 100, key,
@@ -1042,6 +1176,192 @@
   }
 }
 
+TEST_F(KeysetHandleTest, GetEntryFromSingleKeyKeyset) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(11);
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(handle->Validate(), IsOk());
+  ASSERT_THAT(*handle, SizeIs(1));
+
+  ASSERT_THAT(handle->ValidateAt(0), IsOk());
+  KeysetHandle::Entry entry = (*handle)[0];
+
+  EXPECT_THAT(entry.GetId(), Eq(11));
+  EXPECT_THAT(entry.GetStatus(), Eq(KeyStatus::kEnabled));
+  EXPECT_THAT(entry.IsPrimary(), IsTrue());
+  EXPECT_THAT(entry.GetKey()->GetIdRequirement(), Eq(11));
+  EXPECT_THAT(entry.GetKey()->GetParameters().HasIdRequirement(), IsTrue());
+}
+
+TEST_F(KeysetHandleTest, GetEntryFromMultipleKeyKeyset) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddRawKey("first_key_type", 11, key, KeyStatusType::DISABLED,
+            KeyData::SYMMETRIC, &keyset);
+  AddTinkKey("second_key_type", 22, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  AddRawKey("third_key_type", 33, key, KeyStatusType::DESTROYED,
+            KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(22);
+
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(handle->Validate(), IsOk());
+  ASSERT_THAT(*handle, SizeIs(3));
+
+  ASSERT_THAT(handle->ValidateAt(0), IsOk());
+  KeysetHandle::Entry entry0 = (*handle)[0];
+  EXPECT_THAT(entry0.GetId(), Eq(11));
+  EXPECT_THAT(entry0.GetStatus(), Eq(KeyStatus::kDisabled));
+  EXPECT_THAT(entry0.IsPrimary(), IsFalse());
+  EXPECT_THAT(entry0.GetKey()->GetIdRequirement(), Eq(absl::nullopt));
+  EXPECT_THAT(entry0.GetKey()->GetParameters().HasIdRequirement(), IsFalse());
+
+  ASSERT_THAT(handle->ValidateAt(1), IsOk());
+  KeysetHandle::Entry entry1 = (*handle)[1];
+  EXPECT_THAT(entry1.GetId(), Eq(22));
+  EXPECT_THAT(entry1.GetStatus(), Eq(KeyStatus::kEnabled));
+  EXPECT_THAT(entry1.IsPrimary(), IsTrue());
+  EXPECT_THAT(entry1.GetKey()->GetIdRequirement(), Eq(22));
+  EXPECT_THAT(entry1.GetKey()->GetParameters().HasIdRequirement(), IsTrue());
+
+  ASSERT_THAT(handle->ValidateAt(2), IsOk());
+  KeysetHandle::Entry entry2 = (*handle)[2];
+  EXPECT_THAT(entry2.GetId(), Eq(33));
+  EXPECT_THAT(entry2.GetStatus(), Eq(KeyStatus::kDestroyed));
+  EXPECT_THAT(entry2.IsPrimary(), IsFalse());
+  EXPECT_THAT(entry2.GetKey()->GetIdRequirement(), Eq(absl::nullopt));
+  EXPECT_THAT(entry2.GetKey()->GetParameters().HasIdRequirement(), IsFalse());
+}
+
+TEST_F(KeysetHandleDeathTest, EntryWithIndexOutOfBoundsCrashes) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(11);
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(handle->Validate(), IsOk());
+  ASSERT_THAT(*handle, SizeIs(1));
+
+  EXPECT_DEATH_IF_SUPPORTED((*handle)[-1],
+                            "Invalid index -1 for keyset of size 1");
+  EXPECT_DEATH_IF_SUPPORTED((*handle)[1],
+                            "Invalid index 1 for keyset of size 1");
+}
+
+TEST_F(KeysetHandleDeathTest, EntryWithUnknownStatusFails) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::UNKNOWN_STATUS,
+             KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(11);
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(*handle, SizeIs(1));
+
+  EXPECT_THAT(handle->Validate(), StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(handle->ValidateAt(0),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_DEATH_IF_SUPPORTED((*handle)[0], "Invalid key status type.");
+}
+
+TEST_F(KeysetHandleDeathTest, EntryWithUnprintableTypeUrlFails) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddRawKey("invalid key type url with spaces", 11, key, KeyStatusType::ENABLED,
+            KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(11);
+
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(*handle, SizeIs(1));
+
+  EXPECT_THAT(handle->Validate(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_THAT(handle->ValidateAt(0),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_DEATH_IF_SUPPORTED((*handle)[0],
+                            "Non-printable ASCII character in type URL.");
+}
+
+TEST_F(KeysetHandleTest, GetPrimary) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  AddTinkKey("first_key_type", 22, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  AddTinkKey("first_key_type", 33, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(33);
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(handle->Validate(), IsOk());
+  ASSERT_THAT(*handle, SizeIs(3));
+
+  util::StatusOr<KeysetHandle::Entry> primary = handle->GetPrimary();
+  ASSERT_THAT(primary, IsOk());
+
+  EXPECT_THAT(primary->GetId(), Eq(33));
+  EXPECT_THAT(primary->GetStatus(), Eq(KeyStatus::kEnabled));
+  EXPECT_THAT(primary->IsPrimary(), IsTrue());
+}
+
+TEST_F(KeysetHandleDeathTest, NonexistentPrimaryFails) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(*handle, SizeIs(1));
+
+  EXPECT_THAT(handle->Validate(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_DEATH_IF_SUPPORTED(handle->GetPrimary(), "Keyset has no primary");
+}
+
+TEST_F(KeysetHandleDeathTest, MultiplePrimariesFail) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  AddTinkKey("second_key_type", 11, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  // Multiple primaries since two distinct keys share the same key id.
+  keyset.set_primary_key_id(11);
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(*handle, SizeIs(2));
+
+  EXPECT_THAT(handle->Validate(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_DEATH_IF_SUPPORTED(handle->GetPrimary(),
+                            "Keyset has more than one primary");
+}
+
+TEST_F(KeysetHandleDeathTest, GetDisabledPrimaryFails) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("first_key_type", 11, key, KeyStatusType::DISABLED,
+             KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(11);
+  std::unique_ptr<KeysetHandle> handle =
+      TestKeysetHandle::GetKeysetHandle(keyset);
+  ASSERT_THAT(*handle, SizeIs(1));
+
+  EXPECT_THAT(handle->Validate(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_DEATH_IF_SUPPORTED(handle->GetPrimary(),
+                            "Keyset has primary that is not enabled");
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/core/keyset_manager.cc b/cc/core/keyset_manager.cc
index 6ca880a..947b3bd 100644
--- a/cc/core/keyset_manager.cc
+++ b/cc/core/keyset_manager.cc
@@ -17,14 +17,13 @@
 #include "tink/keyset_manager.h"
 
 #include <memory>
-#include <random>
 #include <utility>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
+#include "tink/internal/key_gen_configuration_impl.h"
+#include "tink/key_gen_configuration.h"
 #include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
-#include "tink/registry.h"
 #include "tink/util/enums.h"
 #include "tink/util/errors.h"
 #include "tink/util/status.h"
@@ -34,12 +33,12 @@
 namespace crypto {
 namespace tink {
 
+using ::crypto::tink::util::Enums;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
 using google::crypto::tink::Keyset;
 using google::crypto::tink::KeyStatusType;
 using google::crypto::tink::KeyTemplate;
-using crypto::tink::util::Enums;
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
 
 // static
 StatusOr<std::unique_ptr<KeysetManager>> KeysetManager::New(
@@ -71,17 +70,22 @@
   return Add(key_template, false);
 }
 
-crypto::tink::util::StatusOr<uint32_t> KeysetManager::Add(
+StatusOr<uint32_t> KeysetManager::Add(
     const google::crypto::tink::KeyTemplate& key_template, bool as_primary) {
+  KeyGenConfiguration config;
+  Status status =
+      internal::KeyGenConfigurationImpl::SetGlobalRegistryMode(config);
+  if (!status.ok()) {
+    return status;
+  }
   absl::MutexLock lock(&keyset_mutex_);
-  return KeysetHandle::AddToKeyset(key_template, as_primary, &keyset_);
+  return KeysetHandle::AddToKeyset(key_template, as_primary, config, &keyset_);
 }
 
 StatusOr<uint32_t> KeysetManager::Rotate(const KeyTemplate& key_template) {
   return Add(key_template, true);
 }
 
-
 Status KeysetManager::Enable(uint32_t key_id) {
   absl::MutexLock lock(&keyset_mutex_);
   for (auto& key : *(keyset_.mutable_key())) {
@@ -129,8 +133,7 @@
                      "Cannot delete primary key (key_id %u).", key_id);
   }
   auto key_field = keyset_.mutable_key();
-  for (auto key_iter = key_field->begin();
-       key_iter != key_field->end();
+  for (auto key_iter = key_field->begin(); key_iter != key_field->end();
        key_iter++) {
     auto key = *key_iter;
     if (key.key_id() == key_id) {
@@ -184,7 +187,6 @@
                    "No key with key_id %u found in the keyset.", key_id);
 }
 
-
 int KeysetManager::KeyCount() const {
   absl::MutexLock lock(&keyset_mutex_);
   return keyset_.key_size();
diff --git a/cc/core/keyset_manager_test.cc b/cc/core/keyset_manager_test.cc
index e60838e..230d660 100644
--- a/cc/core/keyset_manager_test.cc
+++ b/cc/core/keyset_manager_test.cc
@@ -20,14 +20,11 @@
 #include "gtest/gtest.h"
 #include "tink/aead/aead_config.h"
 #include "tink/aead/aes_gcm_key_manager.h"
-#include "tink/config.h"
 #include "tink/keyset_handle.h"
 #include "tink/util/test_keyset_handle.h"
 #include "proto/aes_gcm.pb.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::TestKeysetHandle;
-
 using google::crypto::tink::AesGcmKeyFormat;
 using google::crypto::tink::KeyData;
 using google::crypto::tink::KeyStatusType;
diff --git a/cc/core/partial_key_access_token_test.cc b/cc/core/partial_key_access_token_test.cc
new file mode 100644
index 0000000..41087e5
--- /dev/null
+++ b/cc/core/partial_key_access_token_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 <utility>
+
+#include "tink/partial_key_access_token.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "tink/partial_key_access.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+TEST(PartialKeyAccessTokenTest, CopyConstructor) {
+  PartialKeyAccessToken token = GetPartialKeyAccess();
+  PartialKeyAccessToken copy ABSL_ATTRIBUTE_UNUSED(token);
+}
+
+TEST(PartialKeyAccessTokenTest, CopyAssignment) {
+  PartialKeyAccessToken token = GetPartialKeyAccess();
+  PartialKeyAccessToken copy ABSL_ATTRIBUTE_UNUSED = token;
+}
+
+TEST(PartialKeyAccessTokenTest, MoveConstructor) {
+  PartialKeyAccessToken token = GetPartialKeyAccess();
+  PartialKeyAccessToken move ABSL_ATTRIBUTE_UNUSED(std::move(token));
+}
+
+TEST(PartialKeyAccessTokenTest, MoveAssignment) {
+  PartialKeyAccessToken token = GetPartialKeyAccess();
+  PartialKeyAccessToken move ABSL_ATTRIBUTE_UNUSED = std::move(token);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/primitive_set_test.cc b/cc/core/primitive_set_test.cc
index 3f3ebdf..ea877bd 100644
--- a/cc/core/primitive_set_test.cc
+++ b/cc/core/primitive_set_test.cc
@@ -24,7 +24,9 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "tink/cleartext_keyset_handle.h"
 #include "tink/crypto_format.h"
+#include "tink/keyderivation/keyset_deriver.h"
 #include "tink/mac.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
@@ -32,20 +34,21 @@
 
 using ::crypto::tink::test::DummyMac;
 using ::crypto::tink::test::IsOk;
+using ::google::crypto::tink::Keyset;
 using ::google::crypto::tink::KeysetInfo;
 using ::google::crypto::tink::KeyStatusType;
 using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::SizeIs;
 using ::testing::UnorderedElementsAreArray;
 
 namespace crypto {
 namespace tink {
 namespace {
 
-class PrimitiveSetTest : public ::testing::Test {
-};
+class PrimitiveSetTest : public ::testing::Test {};
 
-void add_primitives(PrimitiveSet<Mac>* primitive_set,
-                    int key_id_offset,
+void add_primitives(PrimitiveSet<Mac>* primitive_set, int key_id_offset,
                     int primitives_count) {
   for (int i = 0; i < primitives_count; i++) {
     int key_id = key_id_offset + i;
@@ -59,8 +62,20 @@
   }
 }
 
-void access_primitives(PrimitiveSet<Mac>* primitive_set,
-                       int key_id_offset,
+void add_primitives(PrimitiveSet<Mac>::Builder* primitive_set_builder,
+                    int key_id_offset, int primitives_count) {
+  for (int i = 0; i < primitives_count; i++) {
+    int key_id = key_id_offset + i;
+    KeysetInfo::KeyInfo key_info;
+    key_info.set_output_prefix_type(OutputPrefixType::TINK);
+    key_info.set_key_id(key_id);
+    key_info.set_status(KeyStatusType::ENABLED);
+    std::unique_ptr<Mac> mac(new DummyMac("dummy MAC"));
+    primitive_set_builder->AddPrimitive(std::move(mac), key_info);
+  }
+}
+
+void access_primitives(PrimitiveSet<Mac>* primitive_set, int key_id_offset,
                        int primitives_count) {
   for (int i = 0; i < primitives_count; i++) {
     int key_id = key_id_offset + i;
@@ -76,17 +91,24 @@
 }
 
 TEST_F(PrimitiveSetTest, ConcurrentOperations) {
-  PrimitiveSet<Mac> mac_set;
+  PrimitiveSet<Mac>::Builder mac_set_builder;
   int offset_a = 100;
   int offset_b = 150;
   int count = 100;
 
   // Add some primitives.
-  std::thread add_primitives_a(add_primitives, &mac_set, offset_a, count);
-  std::thread add_primitives_b(add_primitives, &mac_set, offset_b, count);
+  // See go/totw/133 on why we use a lambda here.
+  std::thread add_primitives_a(
+      [&]() { add_primitives(&mac_set_builder, offset_a, count); });
+  std::thread add_primitives_b(
+      [&]() { add_primitives(&mac_set_builder, offset_b, count); });
   add_primitives_a.join();
   add_primitives_b.join();
 
+  auto mac_set_result = std::move(mac_set_builder).Build();
+  ASSERT_TRUE(mac_set_result.ok()) << mac_set_result.status();
+  PrimitiveSet<Mac> mac_set = std::move(mac_set_result.value());
+
   // Access primitives.
   std::thread access_primitives_a(access_primitives, &mac_set, offset_a, count);
   std::thread access_primitives_b(access_primitives, &mac_set, offset_b, count);
@@ -137,7 +159,7 @@
   key_2.set_key_id(key_id_2);
   key_2.set_status(KeyStatusType::ENABLED);
 
-  uint32_t key_id_3 = key_id_2;    // same id as key_2
+  uint32_t key_id_3 = key_id_2;  // same id as key_2
   KeysetInfo::KeyInfo key_3;
   key_3.set_output_prefix_type(OutputPrefixType::TINK);
   key_3.set_key_id(key_id_3);
@@ -155,7 +177,408 @@
   key_5.set_key_id(key_id_5);
   key_5.set_status(KeyStatusType::ENABLED);
 
-  uint32_t key_id_6 = key_id_1;    // same id as key_1
+  uint32_t key_id_6 = key_id_1;  // same id as key_1
+  KeysetInfo::KeyInfo key_6;
+  key_6.set_output_prefix_type(OutputPrefixType::TINK);
+  key_6.set_key_id(key_id_6);
+  key_6.set_status(KeyStatusType::ENABLED);
+
+  PrimitiveSet<Mac>::Builder primitive_set_builder;
+
+  // Add all the primitives.
+  auto primitive_set_result = PrimitiveSet<Mac>::Builder{}
+                                  .AddPrimitive(std::move(mac_1), key_1)
+                                  .AddPrimitive(std::move(mac_2), key_2)
+                                  .AddPrimaryPrimitive(std::move(mac_3), key_3)
+                                  .AddPrimitive(std::move(mac_4), key_4)
+                                  .AddPrimitive(std::move(mac_5), key_5)
+                                  .AddPrimitive(std::move(mac_6), key_6)
+                                  .Build();
+
+  ASSERT_TRUE(primitive_set_result.ok()) << primitive_set_result.status();
+  PrimitiveSet<Mac> primitive_set = std::move(primitive_set_result.value());
+
+  std::string data = "some data";
+
+  {  // Check the primary.
+    auto primary = primitive_set.get_primary();
+    EXPECT_FALSE(primary == nullptr);
+    EXPECT_EQ(KeyStatusType::ENABLED, primary->get_status());
+    EXPECT_EQ(DummyMac(mac_name_3).ComputeMac(data).value(),
+              primary->get_primitive().ComputeMac(data).value());
+  }
+
+  {  // Check raw primitives.
+    auto& primitives = *(primitive_set.get_raw_primitives().value());
+    EXPECT_EQ(2, primitives.size());
+    EXPECT_EQ(DummyMac(mac_name_4).ComputeMac(data).value(),
+              primitives[0]->get_primitive().ComputeMac(data).value());
+    EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
+    EXPECT_EQ(key_4.key_id(), primitives[0]->get_key_id());
+    EXPECT_EQ(OutputPrefixType::RAW, primitives[0]->get_output_prefix_type());
+    EXPECT_EQ(DummyMac(mac_name_5).ComputeMac(data).value(),
+              primitives[1]->get_primitive().ComputeMac(data).value());
+    EXPECT_EQ(KeyStatusType::ENABLED, primitives[1]->get_status());
+    EXPECT_EQ(key_5.key_id(), primitives[1]->get_key_id());
+    EXPECT_EQ(OutputPrefixType::RAW, primitives[1]->get_output_prefix_type());
+  }
+
+  {  // Check Tink primitives.
+    std::string prefix = CryptoFormat::GetOutputPrefix(key_1).value();
+    auto& primitives = *(primitive_set.get_primitives(prefix).value());
+    EXPECT_EQ(2, primitives.size());
+    EXPECT_EQ(DummyMac(mac_name_1).ComputeMac(data).value(),
+              primitives[0]->get_primitive().ComputeMac(data).value());
+    EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
+    EXPECT_EQ(key_1.key_id(), primitives[0]->get_key_id());
+    EXPECT_EQ(OutputPrefixType::TINK, primitives[0]->get_output_prefix_type());
+    EXPECT_EQ(DummyMac(mac_name_6).ComputeMac(data).value(),
+              primitives[1]->get_primitive().ComputeMac(data).value());
+    EXPECT_EQ(KeyStatusType::ENABLED, primitives[1]->get_status());
+    EXPECT_EQ(key_1.key_id(), primitives[1]->get_key_id());
+    EXPECT_EQ(OutputPrefixType::TINK, primitives[1]->get_output_prefix_type());
+  }
+
+  {  // Check another Tink primitive.
+    std::string prefix = CryptoFormat::GetOutputPrefix(key_3).value();
+    auto& primitives = *(primitive_set.get_primitives(prefix).value());
+    EXPECT_EQ(1, primitives.size());
+    EXPECT_EQ(DummyMac(mac_name_3).ComputeMac(data).value(),
+              primitives[0]->get_primitive().ComputeMac(data).value());
+    EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
+    EXPECT_EQ(key_3.key_id(), primitives[0]->get_key_id());
+    EXPECT_EQ(OutputPrefixType::TINK, primitives[0]->get_output_prefix_type());
+  }
+
+  {  // Check legacy primitive.
+    std::string prefix = CryptoFormat::GetOutputPrefix(key_2).value();
+    auto& primitives = *(primitive_set.get_primitives(prefix).value());
+    EXPECT_EQ(1, primitives.size());
+    EXPECT_EQ(DummyMac(mac_name_2).ComputeMac(data).value(),
+              primitives[0]->get_primitive().ComputeMac(data).value());
+    EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
+    EXPECT_EQ(key_2.key_id(), primitives[0]->get_key_id());
+    EXPECT_EQ(OutputPrefixType::LEGACY,
+              primitives[0]->get_output_prefix_type());
+  }
+}
+
+TEST_F(PrimitiveSetTest, PrimaryKeyWithIdCollisions) {
+  std::string mac_name_1 = "MAC#1";
+  std::string mac_name_2 = "MAC#2";
+
+  uint32_t key_id_1 = 1234543;
+  KeysetInfo::KeyInfo key_info_1;
+  key_info_1.set_key_id(key_id_1);
+  key_info_1.set_status(KeyStatusType::ENABLED);
+
+  uint32_t key_id_2 = key_id_1;  // same id as key_2
+  KeysetInfo::KeyInfo key_info_2;
+  key_info_2.set_key_id(key_id_2);
+  key_info_2.set_status(KeyStatusType::ENABLED);
+
+  {  // Test with RAW-keys.
+    std::unique_ptr<Mac> mac_1(new DummyMac(mac_name_1));
+    std::unique_ptr<Mac> mac_2(new DummyMac(mac_name_2));
+    key_info_1.set_output_prefix_type(OutputPrefixType::RAW);
+    key_info_2.set_output_prefix_type(OutputPrefixType::RAW);
+    PrimitiveSet<Mac>::Builder primitive_set_builder;
+
+    // Add the first primitive, and set it as primary.
+    primitive_set_builder.AddPrimaryPrimitive(std::move(mac_1), key_info_1);
+
+    auto primitive_set_result = std::move(primitive_set_builder).Build();
+    ASSERT_TRUE(primitive_set_result.ok()) << primitive_set_result.status();
+    PrimitiveSet<Mac> primitive_set = std::move(primitive_set_result.value());
+
+    std::string identifier = "";
+    const auto& primitives =
+        *(primitive_set.get_primitives(identifier).value());
+    EXPECT_EQ(1, primitives.size());
+    EXPECT_EQ(primitive_set.get_primary(), primitives[0].get());
+  }
+
+  {  // Test with TINK-keys.
+    std::unique_ptr<Mac> mac_1(new DummyMac(mac_name_1));
+    std::unique_ptr<Mac> mac_2(new DummyMac(mac_name_2));
+    key_info_1.set_output_prefix_type(OutputPrefixType::TINK);
+    key_info_2.set_output_prefix_type(OutputPrefixType::TINK);
+    PrimitiveSet<Mac>::Builder primitive_set_builder;
+
+    // Add the first primitive, and set it as primary.
+    primitive_set_builder.AddPrimaryPrimitive(std::move(mac_1), key_info_1);
+
+    auto primitive_set_result = std::move(primitive_set_builder).Build();
+    ASSERT_TRUE(primitive_set_result.ok()) << primitive_set_result.status();
+    PrimitiveSet<Mac> primitive_set = std::move(primitive_set_result.value());
+    std::string identifier = CryptoFormat::GetOutputPrefix(key_info_1).value();
+    const auto& primitives =
+        *(primitive_set.get_primitives(identifier).value());
+    EXPECT_EQ(1, primitives.size());
+    EXPECT_EQ(primitive_set.get_primary(), primitives[0].get());
+  }
+
+  {  // Test with LEGACY-keys.
+    std::unique_ptr<Mac> mac_1(new DummyMac(mac_name_1));
+    std::unique_ptr<Mac> mac_2(new DummyMac(mac_name_2));
+    key_info_1.set_output_prefix_type(OutputPrefixType::LEGACY);
+    key_info_2.set_output_prefix_type(OutputPrefixType::LEGACY);
+    PrimitiveSet<Mac>::Builder primitive_set_builder;
+
+    // Add the first primitive, and set it as primary.
+    primitive_set_builder.AddPrimaryPrimitive(std::move(mac_1), key_info_1);
+
+    auto primitive_set_result = std::move(primitive_set_builder).Build();
+    ASSERT_TRUE(primitive_set_result.ok()) << primitive_set_result.status();
+    PrimitiveSet<Mac> primitive_set = std::move(primitive_set_result.value());
+    std::string identifier = CryptoFormat::GetOutputPrefix(key_info_1).value();
+    const auto& primitives =
+        *(primitive_set.get_primitives(identifier).value());
+    EXPECT_EQ(1, primitives.size());
+    EXPECT_EQ(primitive_set.get_primary(), primitives[0].get());
+  }
+}
+
+TEST_F(PrimitiveSetTest, DisabledKey) {
+  std::string mac_name_1 = "MAC#1";
+  std::unique_ptr<Mac> mac_1(new DummyMac(mac_name_1));
+
+  uint32_t key_id_1 = 1234543;
+  KeysetInfo::KeyInfo key_info_1;
+  key_info_1.set_output_prefix_type(OutputPrefixType::TINK);
+  key_info_1.set_key_id(key_id_1);
+  key_info_1.set_status(KeyStatusType::DISABLED);
+
+  // Add all the primitives.
+  auto add_primitive_result = PrimitiveSet<Mac>::Builder{}
+                                  .AddPrimitive(std::move(mac_1), key_info_1)
+                                  .Build();
+  EXPECT_FALSE(add_primitive_result.ok());
+}
+
+KeysetInfo::KeyInfo CreateKey(uint32_t key_id,
+                              OutputPrefixType output_prefix_type,
+                              KeyStatusType key_status,
+                              absl::string_view type_url) {
+  KeysetInfo::KeyInfo key_info;
+  key_info.set_output_prefix_type(output_prefix_type);
+  key_info.set_key_id(key_id);
+  key_info.set_status(key_status);
+  std::string type_url_str(type_url);
+  key_info.set_type_url(type_url_str);
+  return key_info;
+}
+
+// Struct to hold MAC, Id and type_url.
+struct MacIdAndTypeUrl {
+  std::string mac;
+  std::string id;
+  std::string type_url;
+};
+
+bool operator==(const MacIdAndTypeUrl& first, const MacIdAndTypeUrl& other) {
+  return first.mac == other.mac && first.id == other.id &&
+         first.type_url == other.type_url;
+}
+
+TEST_F(PrimitiveSetTest, GetAll) {
+  auto pset_result =
+      PrimitiveSet<Mac>::Builder{}
+          .AddPrimitive(
+              absl::make_unique<DummyMac>("MAC1"),
+              CreateKey(0x01010101, OutputPrefixType::TINK,
+                        KeyStatusType::ENABLED, /*type_url=*/
+                        "type.googleapis.com/google.crypto.tink.HmacKey"))
+          .AddPrimitive(
+              absl::make_unique<DummyMac>("MAC2"),
+              CreateKey(0x02020202, OutputPrefixType::TINK,
+                        KeyStatusType::ENABLED, /*type_url=*/
+                        "type.googleapis.com/google.crypto.tink.HmacKey"))
+          // Add primitive and make it primary.
+          .AddPrimaryPrimitive(
+              absl::make_unique<DummyMac>("MAC3"),
+              CreateKey(0x02020202, OutputPrefixType::TINK,
+                        KeyStatusType::ENABLED, /*type_url=*/
+                        "type.googleapis.com/google.crypto.tink.AesCmacKey"))
+          .AddPrimitive(
+              absl::make_unique<DummyMac>("MAC4"),
+              CreateKey(0x02020202, OutputPrefixType::RAW,
+                        KeyStatusType::ENABLED, /*type_url=*/
+                        "type.googleapis.com/google.crypto.tink.AesCmacKey"))
+          .AddPrimitive(
+              absl::make_unique<DummyMac>("MAC5"),
+              CreateKey(0x01010101, OutputPrefixType::TINK,
+                        KeyStatusType::ENABLED, /*type_url=*/
+                        "type.googleapis.com/google.crypto.tink.AesCmacKey"))
+          .Build();
+
+  ASSERT_TRUE(pset_result.ok()) << pset_result.status();
+  PrimitiveSet<Mac> pset = std::move(pset_result.value());
+
+  std::vector<MacIdAndTypeUrl> mac_id_and_type;
+  for (auto* entry : pset.get_all()) {
+    auto mac_or = entry->get_primitive().ComputeMac("");
+    ASSERT_THAT(mac_or, IsOk());
+    mac_id_and_type.push_back({mac_or.value(), entry->get_identifier(),
+                               std::string(entry->get_key_type_url())});
+  }
+
+  // In the following id part, the first byte is 1 for Tink.
+  std::vector<MacIdAndTypeUrl> expected_result = {
+      {/*mac=*/"13:0:DummyMac:MAC1", /*id=*/absl::StrCat("\1\1\1\1\1"),
+       /*type_url=*/"type.googleapis.com/google.crypto.tink.HmacKey"},
+      {/*mac=*/"13:0:DummyMac:MAC2", /*id=*/absl::StrCat("\1\2\2\2\2"),
+       /*type_url=*/"type.googleapis.com/google.crypto.tink.HmacKey"},
+      {/*mac=*/"13:0:DummyMac:MAC3", /*id=*/absl::StrCat("\1\2\2\2\2"),
+       /*type_url=*/"type.googleapis.com/google.crypto.tink.AesCmacKey"},
+      {/*mac=*/"13:0:DummyMac:MAC4", /*id=*/"",
+       /*type_url=*/"type.googleapis.com/google.crypto.tink.AesCmacKey"},
+      {/*mac=*/"13:0:DummyMac:MAC5", /*id=*/absl::StrCat("\1\1\1\1\1"),
+       /*type_url=*/"type.googleapis.com/google.crypto.tink.AesCmacKey"}};
+
+  EXPECT_THAT(mac_id_and_type, UnorderedElementsAreArray(expected_result));
+}
+
+class FakeDeriver : public KeysetDeriver {
+ public:
+  explicit FakeDeriver() = default;
+  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> DeriveKeyset(
+      absl::string_view salt) const override {
+    Keyset keyset;
+    return CleartextKeysetHandle::GetKeysetHandle(keyset);
+  }
+};
+
+TEST_F(PrimitiveSetTest, GetAllInKeysetOrder) {
+  auto pset = absl::make_unique<PrimitiveSet<KeysetDeriver>>();
+  std::vector<KeysetInfo::KeyInfo> key_infos;
+
+  KeysetInfo::KeyInfo key_info;
+  key_info.set_key_id(1010101);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::RAW);
+  key_info.set_type_url(
+      "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey");
+  ASSERT_THAT(pset->AddPrimitive(absl::make_unique<FakeDeriver>(), key_info),
+              IsOk());
+  key_infos.push_back(key_info);
+
+  key_info.set_key_id(2020202);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::LEGACY);
+  key_info.set_type_url(
+      "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey");
+  ASSERT_THAT(pset->AddPrimitive(absl::make_unique<FakeDeriver>(), key_info),
+              IsOk());
+  key_infos.push_back(key_info);
+
+  key_info.set_key_id(3030303);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::TINK);
+  key_info.set_type_url(
+      "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey");
+  ASSERT_THAT(pset->AddPrimitive(absl::make_unique<FakeDeriver>(), key_info),
+              IsOk());
+  key_infos.push_back(key_info);
+
+  std::vector<PrimitiveSet<KeysetDeriver>::Entry<KeysetDeriver>*> entries =
+      pset->get_all_in_keyset_order();
+  ASSERT_THAT(entries, SizeIs(key_infos.size()));
+
+  for (int i = 0; i < entries.size(); i++) {
+    EXPECT_THAT(entries[i]->get_identifier(),
+                Eq(*CryptoFormat::GetOutputPrefix(key_infos[i])));
+    EXPECT_THAT(entries[i]->get_status(), Eq(KeyStatusType::ENABLED));
+    EXPECT_THAT(entries[i]->get_key_id(), Eq(key_infos[i].key_id()));
+    EXPECT_THAT(entries[i]->get_output_prefix_type(),
+                Eq(key_infos[i].output_prefix_type()));
+    EXPECT_THAT(entries[i]->get_key_type_url(), Eq(key_infos[i].type_url()));
+  }
+}
+
+TEST_F(PrimitiveSetTest, LegacyConcurrentOperations) {
+  PrimitiveSet<Mac> mac_set;
+  int offset_a = 100;
+  int offset_b = 150;
+  int count = 100;
+
+  // Add some primitives.
+  std::thread add_primitives_a(
+      [&]() { add_primitives(&mac_set, offset_a, count); });
+  std::thread add_primitives_b(
+      [&]() { add_primitives(&mac_set, offset_b, count); });
+  add_primitives_a.join();
+  add_primitives_b.join();
+
+  // Access primitives.
+  std::thread access_primitives_a(access_primitives, &mac_set, offset_a, count);
+  std::thread access_primitives_b(access_primitives, &mac_set, offset_b, count);
+  access_primitives_a.join();
+  access_primitives_b.join();
+
+  // Verify the common key ids added by both threads.
+  for (int key_id = offset_a; key_id < offset_b + count; key_id++) {
+    KeysetInfo::KeyInfo key_info;
+    key_info.set_output_prefix_type(OutputPrefixType::TINK);
+    key_info.set_key_id(key_id);
+    key_info.set_status(KeyStatusType::ENABLED);
+    std::string prefix = CryptoFormat::GetOutputPrefix(key_info).value();
+    auto get_result = mac_set.get_primitives(prefix);
+    EXPECT_TRUE(get_result.ok()) << get_result.status();
+    auto macs = get_result.value();
+    if (key_id >= offset_b && key_id < offset_a + count) {
+      EXPECT_EQ(2, macs->size());  // overlapping key_id range
+    } else {
+      EXPECT_EQ(1, macs->size());
+    }
+  }
+}
+
+TEST_F(PrimitiveSetTest, LegacyBasic) {
+  std::string mac_name_1 = "MAC#1";
+  std::unique_ptr<Mac> mac_1(new DummyMac(mac_name_1));
+  std::string mac_name_2 = "MAC#2";
+  std::unique_ptr<Mac> mac_2(new DummyMac(mac_name_2));
+  std::string mac_name_3 = "MAC#3";
+  std::unique_ptr<Mac> mac_3(new DummyMac(mac_name_3));
+  std::string mac_name_4 = "MAC#3";
+  std::unique_ptr<Mac> mac_4(new DummyMac(mac_name_4));
+  std::string mac_name_5 = "MAC#3";
+  std::unique_ptr<Mac> mac_5(new DummyMac(mac_name_5));
+  std::string mac_name_6 = "MAC#3";
+  std::unique_ptr<Mac> mac_6(new DummyMac(mac_name_6));
+
+  uint32_t key_id_1 = 1234543;
+  KeysetInfo::KeyInfo key_1;
+  key_1.set_output_prefix_type(OutputPrefixType::TINK);
+  key_1.set_key_id(key_id_1);
+  key_1.set_status(KeyStatusType::ENABLED);
+
+  uint32_t key_id_2 = 7213743;
+  KeysetInfo::KeyInfo key_2;
+  key_2.set_output_prefix_type(OutputPrefixType::LEGACY);
+  key_2.set_key_id(key_id_2);
+  key_2.set_status(KeyStatusType::ENABLED);
+
+  uint32_t key_id_3 = key_id_2;  // same id as key_2
+  KeysetInfo::KeyInfo key_3;
+  key_3.set_output_prefix_type(OutputPrefixType::TINK);
+  key_3.set_key_id(key_id_3);
+  key_3.set_status(KeyStatusType::ENABLED);
+
+  uint32_t key_id_4 = 947327;
+  KeysetInfo::KeyInfo key_4;
+  key_4.set_output_prefix_type(OutputPrefixType::RAW);
+  key_4.set_key_id(key_id_4);
+  key_4.set_status(KeyStatusType::ENABLED);
+
+  uint32_t key_id_5 = 529472;
+  KeysetInfo::KeyInfo key_5;
+  key_5.set_output_prefix_type(OutputPrefixType::RAW);
+  key_5.set_key_id(key_id_5);
+  key_5.set_status(KeyStatusType::ENABLED);
+
+  uint32_t key_id_6 = key_id_1;  // same id as key_1
   KeysetInfo::KeyInfo key_6;
   key_6.set_output_prefix_type(OutputPrefixType::TINK);
   key_6.set_key_id(key_id_6);
@@ -260,7 +683,7 @@
   }
 }
 
-TEST_F(PrimitiveSetTest, PrimaryKeyWithIdCollisions) {
+TEST_F(PrimitiveSetTest, LegacyPrimaryKeyWithIdCollisions) {
   std::string mac_name_1 = "MAC#1";
   std::string mac_name_2 = "MAC#2";
 
@@ -269,7 +692,7 @@
   key_info_1.set_key_id(key_id_1);
   key_info_1.set_status(KeyStatusType::ENABLED);
 
-  uint32_t key_id_2 = key_id_1;    // same id as key_2
+  uint32_t key_id_2 = key_id_1;  // same id as key_2
   KeysetInfo::KeyInfo key_info_2;
   key_info_2.set_key_id(key_id_2);
   key_info_2.set_status(KeyStatusType::ENABLED);
@@ -362,7 +785,7 @@
   }
 }
 
-TEST_F(PrimitiveSetTest, DisabledKey) {
+TEST_F(PrimitiveSetTest, LegacyDisabledKey) {
   std::string mac_name_1 = "MAC#1";
   std::unique_ptr<Mac> mac_1(new DummyMac(mac_name_1));
 
@@ -379,32 +802,7 @@
   EXPECT_FALSE(add_primitive_result.ok());
 }
 
-KeysetInfo::KeyInfo CreateKey(uint32_t key_id,
-                              OutputPrefixType output_prefix_type,
-                              KeyStatusType key_status,
-                              absl::string_view type_url) {
-  KeysetInfo::KeyInfo key_info;
-  key_info.set_output_prefix_type(output_prefix_type);
-  key_info.set_key_id(key_id);
-  key_info.set_status(key_status);
-  std::string type_url_str(type_url);
-  key_info.set_type_url(type_url_str);
-  return key_info;
-}
-
-// Struct to hold MAC, Id and type_url.
-struct MacIdAndTypeUrl {
-  std::string mac;
-  std::string id;
-  std::string type_url;
-};
-
-bool operator==(const MacIdAndTypeUrl& first, const MacIdAndTypeUrl& other) {
-  return first.mac == other.mac && first.id == other.id &&
-         first.type_url == other.type_url;
-}
-
-TEST_F(PrimitiveSetTest, GetAll) {
+TEST_F(PrimitiveSetTest, LegacyGetAll) {
   PrimitiveSet<Mac> pset;
   EXPECT_THAT(
       pset.AddPrimitive(
diff --git a/cc/core/private_key_manager_impl.h b/cc/core/private_key_manager_impl.h
index 5d2d6ae..63a3e93 100644
--- a/cc/core/private_key_manager_impl.h
+++ b/cc/core/private_key_manager_impl.h
@@ -16,6 +16,7 @@
 #ifndef TINK_CORE_PRIVATE_KEY_MANAGER_IMPL_H_
 #define TINK_CORE_PRIVATE_KEY_MANAGER_IMPL_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/core/private_key_manager_impl_test.cc b/cc/core/private_key_manager_impl_test.cc
index 512531e..a874884 100644
--- a/cc/core/private_key_manager_impl_test.cc
+++ b/cc/core/private_key_manager_impl_test.cc
@@ -43,17 +43,22 @@
 using ::google::crypto::tink::EcdsaPrivateKey;
 using ::google::crypto::tink::EcdsaPublicKey;
 using ::google::crypto::tink::EcdsaSignatureEncoding;
-using ::google::crypto::tink::KeyData;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Return;
 
+}  // namespace
+
 // Placeholders for the primitives. We don't really want to test anything with
 // these except that things compile and List<PrivatePrimitive> is never confused
 // with List<PublicPrimitive> in private_key_manager_impl.
+// NOTE: These are outside of the anonymous namespace to allow compiling with
+// MSVC.
 class PrivatePrimitive {};
 class PublicPrimitive {};
 
+namespace {
+
 class ExamplePrivateKeyTypeManager
     : public PrivateKeyTypeManager<EcdsaPrivateKey, EcdsaKeyFormat,
                                    EcdsaPublicKey, List<PrivatePrimitive>> {
diff --git a/cc/core/restricted_data.cc b/cc/core/restricted_data.cc
new file mode 100644
index 0000000..4f66030
--- /dev/null
+++ b/cc/core/restricted_data.cc
@@ -0,0 +1,45 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/restricted_data.h"
+
+#include <iostream>
+
+#include "absl/log/check.h"
+#include "openssl/crypto.h"
+#include "tink/subtle/random.h"
+#include "tink/util/secret_data.h"
+
+namespace crypto {
+namespace tink {
+
+RestrictedData::RestrictedData(int64_t num_random_bytes) {
+  CHECK_GE(num_random_bytes, 0)
+      << "Cannot generate a negative number of random bytes.\n";
+  secret_ = util::SecretDataFromStringView(
+      subtle::Random::GetRandomBytes(num_random_bytes));
+}
+
+bool RestrictedData::operator==(const RestrictedData& other) const {
+  if (secret_.size() != other.secret_.size()) {
+    return false;
+  }
+  return CRYPTO_memcmp(secret_.data(), other.secret_.data(), secret_.size()) ==
+         0;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/restricted_data_test.cc b/cc/core/restricted_data_test.cc
new file mode 100644
index 0000000..4012563
--- /dev/null
+++ b/cc/core/restricted_data_test.cc
@@ -0,0 +1,117 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/restricted_data.h"
+
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/subtle/random.h"
+#include "tink/util/secret_data.h"
+
+namespace crypto {
+namespace tink {
+
+using ::crypto::tink::subtle::Random;
+using ::testing::Eq;
+using ::testing::SizeIs;
+
+TEST(RestrictedDataTest, CreateAndGetSecret) {
+  const std::string secret = Random::GetRandomBytes(32);
+  RestrictedData data(secret, InsecureSecretKeyAccess::Get());
+
+  EXPECT_THAT(data.GetSecret(InsecureSecretKeyAccess::Get()), Eq(secret));
+}
+
+TEST(RestrictedDataTest, GenerateRandomAndSize) {
+  RestrictedData data(/*num_random_bytes=*/32);
+
+  EXPECT_THAT(data.GetSecret(InsecureSecretKeyAccess::Get()), SizeIs(32));
+  EXPECT_THAT(data.size(), Eq(32));
+}
+
+TEST(RestrictedDataTest, GenerateRandomNegative) {
+  EXPECT_DEATH_IF_SUPPORTED(
+      RestrictedData(/*num_random_bytes=*/-1),
+      "Cannot generate a negative number of random bytes.\n");
+}
+
+TEST(RestrictedDataTest, Equals) {
+  const std::string secret = Random::GetRandomBytes(32);
+  RestrictedData data(secret, InsecureSecretKeyAccess::Get());
+  RestrictedData same_data(secret, InsecureSecretKeyAccess::Get());
+
+  EXPECT_TRUE(data == same_data);
+  EXPECT_TRUE(same_data == data);
+  EXPECT_FALSE(data != same_data);
+  EXPECT_FALSE(same_data != data);
+}
+
+TEST(RestrictedDataTest, NotEquals) {
+  RestrictedData data(
+      util::SecretDataAsStringView(Random::GetRandomKeyBytes(32)),
+      InsecureSecretKeyAccess::Get());
+  RestrictedData diff_data(
+      util::SecretDataAsStringView(Random::GetRandomKeyBytes(32)),
+      InsecureSecretKeyAccess::Get());
+
+  EXPECT_TRUE(data != diff_data);
+  EXPECT_TRUE(diff_data != data);
+  EXPECT_FALSE(data == diff_data);
+  EXPECT_FALSE(diff_data == data);
+}
+
+TEST(RestrictedDataTest, CopyConstructor) {
+  RestrictedData data(/*num_random_bytes=*/32);
+  RestrictedData copy(data);
+
+  EXPECT_THAT(copy, SizeIs(32));
+  EXPECT_THAT(copy.GetSecret(InsecureSecretKeyAccess::Get()),
+              Eq(data.GetSecret(InsecureSecretKeyAccess::Get())));
+}
+
+TEST(RestrictedDataTest, CopyAssignment) {
+  RestrictedData data(/*num_random_bytes=*/32);
+  RestrictedData copy = data;
+
+  EXPECT_THAT(copy, SizeIs(32));
+  EXPECT_THAT(copy.GetSecret(InsecureSecretKeyAccess::Get()),
+              Eq(copy.GetSecret(InsecureSecretKeyAccess::Get())));
+}
+
+TEST(RestrictedDataTest, MoveConstructor) {
+  const std::string secret = Random::GetRandomBytes(32);
+  RestrictedData data(secret, InsecureSecretKeyAccess::Get());
+  RestrictedData move(std::move(data));
+
+  EXPECT_THAT(move, SizeIs(32));
+  EXPECT_THAT(move.GetSecret(InsecureSecretKeyAccess::Get()), Eq(secret));
+}
+
+TEST(RestrictedDataTest, MoveAssignment) {
+  const std::string secret = Random::GetRandomBytes(32);
+  RestrictedData data(secret, InsecureSecretKeyAccess::Get());
+  RestrictedData move = std::move(data);
+
+  EXPECT_THAT(move, SizeIs(32));
+  EXPECT_THAT(move.GetSecret(InsecureSecretKeyAccess::Get()), Eq(secret));
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/version_test.cc b/cc/core/version_test.cc
index d25babd..3a4bac5 100644
--- a/cc/core/version_test.cc
+++ b/cc/core/version_test.cc
@@ -20,12 +20,16 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "tink/internal/util.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
-TEST(VersionTest, testVersionFormat) {
+using ::testing::AnyOf;
+using ::testing::MatchesRegex;
+
+TEST(VersionTest, VersionHasCorrectFormat) {
   // The regex represents Semantic Versioning syntax (www.semver.org),
   // i.e. three dot-separated numbers, with an optional suffix
   // that starts with a hyphen, to cover alpha/beta releases and
@@ -33,8 +37,16 @@
   //   1.2.3
   //   1.2.3-beta
   //   1.2.3-RC1
-  std::string version_regex = "[0-9]+[.][0-9]+[.][0-9]+(-[A-Za-z0-9]+)?";
-  EXPECT_THAT(Version::kTinkVersion, testing::MatchesRegex(version_regex));
+  if (crypto::tink::internal::IsWindows()) {
+    // Using the syntax in
+    // https://github.com/google/googletest/blob/main/docs/advanced.md#regular-expression-syntax.
+    EXPECT_THAT(Version::kTinkVersion,
+                AnyOf(MatchesRegex(R"regex(\d+\.\d+\.\d+)regex"),
+                      MatchesRegex(R"regex(\d+\.\d+\.\d+-\w+)regex")));
+  } else {
+    std::string version_regex = "[0-9]+[.][0-9]+[.][0-9]+(-[A-Za-z0-9]+)?";
+    EXPECT_THAT(Version::kTinkVersion, testing::MatchesRegex(version_regex));
+  }
 }
 
 }  // namespace
diff --git a/cc/daead/BUILD.bazel b/cc/daead/BUILD.bazel
index 2a4c933..e6bb7b0 100644
--- a/cc/daead/BUILD.bazel
+++ b/cc/daead/BUILD.bazel
@@ -56,9 +56,9 @@
     visibility = ["//visibility:public"],
     deps = [
         ":aes_siv_key_manager",
+        ":aes_siv_proto_serialization",
         ":deterministic_aead_wrapper",
         "//:registry",
-        "//config:config_util",
         "//config:tink_fips",
         "//proto:config_cc_proto",
         "//util:status",
@@ -97,6 +97,91 @@
     ],
 )
 
+cc_library(
+    name = "failing_daead",
+    srcs = ["failing_daead.cc"],
+    hdrs = ["failing_daead.h"],
+    include_prefix = "tink/daead",
+    deps = [
+        "//:deterministic_aead",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "deterministic_aead_parameters",
+    hdrs = ["deterministic_aead_parameters.h"],
+    include_prefix = "tink/daead",
+    deps = ["//:parameters"],
+)
+
+cc_library(
+    name = "deterministic_aead_key",
+    hdrs = ["deterministic_aead_key.h"],
+    include_prefix = "tink/daead",
+    deps = [
+        ":deterministic_aead_parameters",
+        "//:key",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "aes_siv_parameters",
+    srcs = ["aes_siv_parameters.cc"],
+    hdrs = ["aes_siv_parameters.h"],
+    include_prefix = "tink/daead",
+    deps = [
+        ":deterministic_aead_parameters",
+        "//util:statusor",
+    ],
+)
+
+cc_library(
+    name = "aes_siv_key",
+    srcs = ["aes_siv_key.cc"],
+    hdrs = ["aes_siv_key.h"],
+    include_prefix = "tink/daead",
+    deps = [
+        ":aes_siv_parameters",
+        ":deterministic_aead_key",
+        "//:partial_key_access_token",
+        "//:restricted_data",
+        "//subtle:subtle_util",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_library(
+    name = "aes_siv_proto_serialization",
+    srcs = ["aes_siv_proto_serialization.cc"],
+    hdrs = ["aes_siv_proto_serialization.h"],
+    include_prefix = "tink/daead",
+    deps = [
+        ":aes_siv_key",
+        ":aes_siv_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//internal:key_parser",
+        "//internal:key_serializer",
+        "//internal:mutable_serialization_registry",
+        "//internal:parameters_parser",
+        "//internal:parameters_serializer",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:aes_siv_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
 # tests
 
 cc_test(
@@ -143,14 +228,20 @@
     srcs = ["deterministic_aead_config_test.cc"],
     tags = ["fips"],
     deps = [
+        ":aes_siv_key",
         ":aes_siv_key_manager",
+        ":aes_siv_parameters",
         ":deterministic_aead_config",
         ":deterministic_aead_key_templates",
-        "//:config",
         "//:deterministic_aead",
+        "//:insecure_secret_key_access",
         "//:keyset_handle",
+        "//:partial_key_access",
         "//:registry",
         "//config:tink_fips",
+        "//internal:mutable_serialization_registry",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
@@ -195,17 +286,6 @@
     ],
 )
 
-cc_library(
-    name = "failing_daead",
-    srcs = ["failing_daead.cc"],
-    hdrs = ["failing_daead.h"],
-    include_prefix = "tink/daead",
-    deps = [
-        "//:deterministic_aead",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
 cc_test(
     name = "failing_daead_test",
     srcs = ["failing_daead_test.cc"],
@@ -216,3 +296,51 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "aes_siv_parameters_test",
+    srcs = ["aes_siv_parameters_test.cc"],
+    deps = [
+        ":aes_siv_parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "aes_siv_key_test",
+    srcs = ["aes_siv_key_test.cc"],
+    deps = [
+        ":aes_siv_key",
+        ":aes_siv_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "aes_siv_proto_serialization_test",
+    size = "small",
+    srcs = ["aes_siv_proto_serialization_test.cc"],
+    deps = [
+        ":aes_siv_key",
+        ":aes_siv_parameters",
+        ":aes_siv_proto_serialization",
+        "//:insecure_secret_key_access",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//internal:mutable_serialization_registry",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:aes_siv_cc_proto",
+        "//proto:tink_cc_proto",
+        "//subtle:random",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/daead/CMakeLists.txt b/cc/daead/CMakeLists.txt
index 4876217..95a2b78 100644
--- a/cc/daead/CMakeLists.txt
+++ b/cc/daead/CMakeLists.txt
@@ -53,11 +53,11 @@
     deterministic_aead_config.h
   DEPS
     tink::daead::aes_siv_key_manager
+    tink::daead::aes_siv_proto_serialization
     tink::daead::deterministic_aead_wrapper
     absl::core_headers
     absl::memory
     tink::core::registry
-    tink::config::config_util
     tink::config::tink_fips
     tink::util::status
     tink::proto::config_cc_proto
@@ -90,6 +90,87 @@
     tink::proto::tink_cc_proto
 )
 
+tink_cc_library(
+  NAME failing_daead
+  SRCS
+    failing_daead.cc
+    failing_daead.h
+  DEPS
+    absl::strings
+    tink::core::deterministic_aead
+)
+
+tink_cc_library(
+  NAME deterministic_aead_parameters
+  SRCS
+    deterministic_aead_parameters.h
+  DEPS
+    tink::core::parameters
+)
+
+tink_cc_library(
+  NAME deterministic_aead_key
+  SRCS
+    deterministic_aead_key.h
+  DEPS
+    tink::daead::deterministic_aead_parameters
+    absl::strings
+    tink::core::key
+)
+
+tink_cc_library(
+  NAME aes_siv_parameters
+  SRCS
+    aes_siv_parameters.cc
+    aes_siv_parameters.h
+  DEPS
+    tink::daead::deterministic_aead_parameters
+    tink::util::statusor
+)
+
+tink_cc_library(
+  NAME aes_siv_key
+  SRCS
+    aes_siv_key.cc
+    aes_siv_key.h
+  DEPS
+    tink::daead::aes_siv_parameters
+    tink::daead::deterministic_aead_key
+    absl::strings
+    absl::optional
+    tink::core::partial_key_access_token
+    tink::core::restricted_data
+    tink::subtle::subtle_util
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_library(
+  NAME aes_siv_proto_serialization
+  SRCS
+    aes_siv_proto_serialization.cc
+    aes_siv_proto_serialization.h
+  DEPS
+    tink::daead::aes_siv_key
+    tink::daead::aes_siv_parameters
+    absl::status
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::mutable_serialization_registry
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::util::status
+    tink::util::statusor
+    tink::proto::aes_siv_cc_proto
+    tink::proto::tink_cc_proto
+)
+
 # tests
 
 tink_cc_test(
@@ -133,16 +214,22 @@
   SRCS
     deterministic_aead_config_test.cc
   DEPS
+    tink::daead::aes_siv_key
     tink::daead::aes_siv_key_manager
+    tink::daead::aes_siv_parameters
     tink::daead::deterministic_aead_config
     tink::daead::deterministic_aead_key_templates
     gmock
     absl::status
-    tink::core::config
     tink::core::deterministic_aead
+    tink::core::insecure_secret_key_access
     tink::core::keyset_handle
+    tink::core::partial_key_access
     tink::core::registry
     tink::config::tink_fips
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
     tink::util::status
     tink::util::test_matchers
     tink::util::test_util
@@ -182,16 +269,6 @@
     tink::proto::tink_cc_proto
 )
 
-tink_cc_library(
-  NAME failing_daead
-  SRCS
-    failing_daead.cc
-    failing_daead.h
-  DEPS
-    absl::strings
-    tink::core::deterministic_aead
-)
-
 tink_cc_test(
   NAME failing_daead_test
   SRCS
@@ -202,3 +279,50 @@
     absl::status
     tink::util::test_matchers
 )
+
+tink_cc_test(
+  NAME aes_siv_parameters_test
+  SRCS
+    aes_siv_parameters_test.cc
+  DEPS
+    tink::daead::aes_siv_parameters
+    gmock
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_test(
+  NAME aes_siv_key_test
+  SRCS
+    aes_siv_key_test.cc
+  DEPS
+    tink::daead::aes_siv_key
+    tink::daead::aes_siv_parameters
+    gmock
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_test(
+  NAME aes_siv_proto_serialization_test
+  SRCS
+    aes_siv_proto_serialization_test.cc
+  DEPS
+    tink::daead::aes_siv_key
+    tink::daead::aes_siv_parameters
+    tink::daead::aes_siv_proto_serialization
+    gmock
+    tink::core::insecure_secret_key_access
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::subtle::random
+    tink::util::test_matchers
+    tink::proto::aes_siv_cc_proto
+    tink::proto::tink_cc_proto
+)
diff --git a/cc/daead/aes_siv_key.cc b/cc/daead/aes_siv_key.cc
new file mode 100644
index 0000000..00582b2
--- /dev/null
+++ b/cc/daead/aes_siv_key.cc
@@ -0,0 +1,107 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/daead/aes_siv_key.h"
+
+#include <string>
+
+#include "absl/strings/escaping.h"
+#include "absl/types/optional.h"
+#include "tink/daead/aes_siv_parameters.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/subtle_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+util::StatusOr<std::string> ComputeOutputPrefix(
+    const AesSivParameters& parameters, absl::optional<int> id_requirement) {
+  switch (parameters.GetVariant()) {
+    case AesSivParameters::Variant::kNoPrefix:
+      return std::string("");  // Empty prefix.
+    case AesSivParameters::Variant::kCrunchy:
+      if (!id_requirement.has_value()) {
+        return util::Status(
+            absl::StatusCode::kInvalidArgument,
+            "id requirement must have value with kCrunchy or kLegacy");
+      }
+      return absl::StrCat(absl::HexStringToBytes("00"),
+                          subtle::BigEndian32(*id_requirement));
+    case AesSivParameters::Variant::kTink:
+      if (!id_requirement.has_value()) {
+        return util::Status(absl::StatusCode::kInvalidArgument,
+                            "id requirement must have value with kTink");
+      }
+      return absl::StrCat(absl::HexStringToBytes("01"),
+                          subtle::BigEndian32(*id_requirement));
+    default:
+      return util::Status(
+          absl::StatusCode::kInvalidArgument,
+          absl::StrCat("Invalid variant: ", parameters.GetVariant()));
+  }
+}
+
+}  // namespace
+
+util::StatusOr<AesSivKey> AesSivKey::Create(const AesSivParameters& parameters,
+                                            const RestrictedData& key_bytes,
+                                            absl::optional<int> id_requirement,
+                                            PartialKeyAccessToken token) {
+  if (parameters.KeySizeInBytes() != key_bytes.size()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Key size does not match AES-SIV parameters");
+  }
+  if (parameters.HasIdRequirement() && !id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key without ID requirement with parameters with ID "
+        "requirement");
+  }
+  if (!parameters.HasIdRequirement() && id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key with ID requirement with parameters without ID "
+        "requirement");
+  }
+  util::StatusOr<std::string> output_prefix =
+      ComputeOutputPrefix(parameters, id_requirement);
+  if (!output_prefix.ok()) {
+    return output_prefix.status();
+  }
+  return AesSivKey(parameters, key_bytes, id_requirement,
+                   *std::move(output_prefix));
+}
+
+bool AesSivKey::operator==(const Key& other) const {
+  const AesSivKey* that = dynamic_cast<const AesSivKey*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (GetParameters() != that->GetParameters()) {
+    return false;
+  }
+  if (id_requirement_ != that->id_requirement_) {
+    return false;
+  }
+  return key_bytes_ == that->key_bytes_;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/aes_siv_key.h b/cc/daead/aes_siv_key.h
new file mode 100644
index 0000000..8ca060d
--- /dev/null
+++ b/cc/daead/aes_siv_key.h
@@ -0,0 +1,83 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_AES_SIV_KEY_H_
+#define TINK_DAEAD_AES_SIV_KEY_H_
+
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/daead/aes_siv_parameters.h"
+#include "tink/daead/deterministic_aead_key.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Represents a Deterministic AEAD that uses AES-SIV.
+class AesSivKey : public DeterministicAeadKey {
+ public:
+  // Copyable and movable.
+  AesSivKey(const AesSivKey& other) = default;
+  AesSivKey& operator=(const AesSivKey& other) = default;
+  AesSivKey(AesSivKey&& other) = default;
+  AesSivKey& operator=(AesSivKey&& other) = default;
+
+  // Creates a new AES-SIV key.  If the parameters specify a variant that uses
+  // a prefix, then the id is used to compute this prefix.
+  static util::StatusOr<AesSivKey> Create(const AesSivParameters& parameters,
+                                          const RestrictedData& key_bytes,
+                                          absl::optional<int> id_requirement,
+                                          PartialKeyAccessToken token);
+
+  // Returns the underlying AES-SIV key.
+  util::StatusOr<RestrictedData> GetKeyBytes(
+      PartialKeyAccessToken token) const {
+    return key_bytes_;
+  }
+
+  absl::string_view GetOutputPrefix() const override { return output_prefix_; }
+
+  const AesSivParameters& GetParameters() const override { return parameters_; }
+
+  absl::optional<int> GetIdRequirement() const override {
+    return id_requirement_;
+  }
+
+  bool operator==(const Key& other) const override;
+
+ private:
+  AesSivKey(const AesSivParameters& parameters, const RestrictedData& key_bytes,
+            absl::optional<int> id_requirement, std::string output_prefix)
+      : parameters_(parameters),
+        key_bytes_(key_bytes),
+        id_requirement_(id_requirement),
+        output_prefix_(std::move(output_prefix)) {}
+
+  AesSivParameters parameters_;
+  RestrictedData key_bytes_;
+  absl::optional<int> id_requirement_;
+  std::string output_prefix_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_AES_SIV_KEY_H_
diff --git a/cc/daead/aes_siv_key_manager.h b/cc/daead/aes_siv_key_manager.h
index 4d2429c..29aa509 100644
--- a/cc/daead/aes_siv_key_manager.h
+++ b/cc/daead/aes_siv_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_DAEAD_AES_SIV_KEY_MANAGER_H_
 #define TINK_DAEAD_AES_SIV_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/daead/aes_siv_key_manager_test.cc b/cc/daead/aes_siv_key_manager_test.cc
index f00daec..aebb705 100644
--- a/cc/daead/aes_siv_key_manager_test.cc
+++ b/cc/daead/aes_siv_key_manager_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/daead/aes_siv_key_manager.h"
 
+#include <sstream>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
diff --git a/cc/daead/aes_siv_key_test.cc b/cc/daead/aes_siv_key_test.cc
new file mode 100644
index 0000000..294525e
--- /dev/null
+++ b/cc/daead/aes_siv_key_test.cc
@@ -0,0 +1,230 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/daead/aes_siv_key.h"
+
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+#include "tink/daead/aes_siv_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  AesSivParameters::Variant variant;
+  absl::optional<int> id_requirement;
+  std::string output_prefix;
+};
+
+using AesSivKeyTest = TestWithParam<std::tuple<int, TestCase>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    AesSivKeyTestSuite, AesSivKeyTest,
+    Combine(Values(32, 48, 64),
+            Values(TestCase{AesSivParameters::Variant::kTink, 0x02030400,
+                            std::string("\x01\x02\x03\x04\x00", 5)},
+                   TestCase{AesSivParameters::Variant::kCrunchy, 0x01030005,
+                            std::string("\x00\x01\x03\x00\x05", 5)},
+                   TestCase{AesSivParameters::Variant::kNoPrefix, absl::nullopt,
+                            ""})));
+
+TEST_P(AesSivKeyTest, CreateSucceeds) {
+  int key_size;
+  TestCase test_case;
+  std::tie(key_size, test_case) = GetParam();
+
+  util::StatusOr<AesSivParameters> params =
+      AesSivParameters::Create(key_size, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<AesSivKey> key = AesSivKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  EXPECT_THAT(key->GetParameters(), Eq(*params));
+  EXPECT_THAT(key->GetIdRequirement(), Eq(test_case.id_requirement));
+  EXPECT_THAT(key->GetOutputPrefix(), Eq(test_case.output_prefix));
+}
+
+TEST(AesSivKeyTest, CreateKeyWithMismatchedKeySizeFails) {
+  // Key size parameter is 64 bytes.
+  util::StatusOr<AesSivParameters> params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  // Key material is 32 bytes (another valid key length).
+  RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/32);
+
+  EXPECT_THAT(AesSivKey::Create(*params, mismatched_secret,
+                                /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesSivKeyTest, CreateKeyWithInvalidIdRequirementFails) {
+  util::StatusOr<AesSivParameters> no_prefix_params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kNoPrefix);
+  ASSERT_THAT(no_prefix_params, IsOk());
+
+  util::StatusOr<AesSivParameters> tink_params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/64);
+
+  EXPECT_THAT(AesSivKey::Create(*no_prefix_params, secret,
+                                /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      AesSivKey::Create(*tink_params, secret,
+                        /*id_requirement=*/absl::nullopt, GetPartialKeyAccess())
+          .status(),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesSivKeyTest, GetKeyBytes) {
+  int key_size;
+  TestCase test_case;
+  std::tie(key_size, test_case) = GetParam();
+
+  util::StatusOr<AesSivParameters> params =
+      AesSivParameters::Create(key_size, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+
+  util::StatusOr<AesSivKey> key = AesSivKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), IsOkAndHolds(secret));
+}
+
+TEST_P(AesSivKeyTest, KeyEquals) {
+  int key_size;
+  TestCase test_case;
+  std::tie(key_size, test_case) = GetParam();
+
+  util::StatusOr<AesSivParameters> params =
+      AesSivParameters::Create(key_size, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<AesSivKey> key = AesSivKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesSivKey> other_key = AesSivKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key == *other_key);
+  EXPECT_TRUE(*other_key == *key);
+  EXPECT_FALSE(*key != *other_key);
+  EXPECT_FALSE(*other_key != *key);
+}
+
+TEST(AesSivKeyTest, DifferentVariantNotEqual) {
+  util::StatusOr<AesSivParameters> crunchy_params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kCrunchy);
+  ASSERT_THAT(crunchy_params, IsOk());
+
+  util::StatusOr<AesSivParameters> tink_params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/64);
+
+  util::StatusOr<AesSivKey> key =
+      AesSivKey::Create(*crunchy_params, secret, /*id_requirement=*/0x01020304,
+                        GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesSivKey> other_key =
+      AesSivKey::Create(*tink_params, secret, /*id_requirement=*/0x01020304,
+                        GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesSivKeyTest, DifferentSecretDataNotEqual) {
+  util::StatusOr<AesSivParameters> params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret1 = RestrictedData(/*num_random_bytes=*/64);
+  RestrictedData secret2 = RestrictedData(/*num_random_bytes=*/64);
+
+  util::StatusOr<AesSivKey> key = AesSivKey::Create(
+      *params, secret1, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesSivKey> other_key = AesSivKey::Create(
+      *params, secret2, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesSivKeyTest, DifferentIdRequirementNotEqual) {
+  util::StatusOr<AesSivParameters> params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/64);
+
+  util::StatusOr<AesSivKey> key = AesSivKey::Create(
+      *params, secret, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesSivKey> other_key = AesSivKey::Create(
+      *params, secret, /*id_requirement=*/0x02030405, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/aes_siv_parameters.cc b/cc/daead/aes_siv_parameters.cc
new file mode 100644
index 0000000..996be60
--- /dev/null
+++ b/cc/daead/aes_siv_parameters.cc
@@ -0,0 +1,58 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/daead/aes_siv_parameters.h"
+
+#include <set>
+
+namespace crypto {
+namespace tink {
+
+util::StatusOr<AesSivParameters> AesSivParameters::Create(int key_size_in_bytes,
+                                                          Variant variant) {
+  if (key_size_in_bytes != 32 && key_size_in_bytes != 48 &&
+      key_size_in_bytes != 64) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Key size should be 32, 48, or 64 bytes, got ",
+                     key_size_in_bytes, " bytes."));
+  }
+  static const std::set<Variant>* supported_variants = new std::set<Variant>(
+      {Variant::kTink, Variant::kCrunchy, Variant::kNoPrefix});
+  if (supported_variants->find(variant) == supported_variants->end()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create AES-SIV parameters with unknown variant.");
+  }
+  return AesSivParameters(key_size_in_bytes, variant);
+}
+
+bool AesSivParameters::operator==(const Parameters& other) const {
+  const AesSivParameters* that = dynamic_cast<const AesSivParameters*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (key_size_in_bytes_ != that->key_size_in_bytes_) {
+    return false;
+  }
+  if (variant_ != that->variant_) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/aes_siv_parameters.h b/cc/daead/aes_siv_parameters.h
new file mode 100644
index 0000000..3d828b9
--- /dev/null
+++ b/cc/daead/aes_siv_parameters.h
@@ -0,0 +1,73 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_AES_SIV_PARAMETERS_H_
+#define TINK_DAEAD_AES_SIV_PARAMETERS_H_
+
+#include "tink/daead/deterministic_aead_parameters.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes the parameters of an `AesSivKey`.
+class AesSivParameters : public DeterministicAeadParameters {
+ public:
+  // Description of the output prefix prepended to the ciphertext.
+  enum class Variant : int {
+    // Prepends '0x01<big endian key id>' to the ciphertext.
+    kTink = 1,
+    // Prepends '0x00<big endian key id>' to the ciphertext.
+    kCrunchy = 2,
+    // Does not prepend any prefix (i.e., keys must have no ID requirement).
+    kNoPrefix = 3,
+    // Added to guard from failures that may be caused by future expansions.
+    kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+  };
+
+  // Copyable and movable.
+  AesSivParameters(const AesSivParameters& other) = default;
+  AesSivParameters& operator=(const AesSivParameters& other) = default;
+  AesSivParameters(AesSivParameters&& other) = default;
+  AesSivParameters& operator=(AesSivParameters&& other) = default;
+
+  // Creates `AesSivParameters` object from `key_size_in_bytes` and `variant`.
+  // Only allows 32-, 48-, and 64-byte key sizes as specified in RFC 5297.
+  static util::StatusOr<AesSivParameters> Create(int key_size_in_bytes,
+                                                 Variant variant);
+
+  int KeySizeInBytes() const { return key_size_in_bytes_; }
+
+  Variant GetVariant() const { return variant_; }
+
+  bool HasIdRequirement() const override {
+    return variant_ != Variant::kNoPrefix;
+  }
+
+  bool operator==(const Parameters& other) const override;
+
+ private:
+  AesSivParameters(int key_size_in_bytes, Variant variant)
+      : key_size_in_bytes_(key_size_in_bytes), variant_(variant) {}
+
+  int key_size_in_bytes_;
+  Variant variant_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_AES_SIV_PARAMETERS_H_
diff --git a/cc/daead/aes_siv_parameters_test.cc b/cc/daead/aes_siv_parameters_test.cc
new file mode 100644
index 0000000..ac57baf
--- /dev/null
+++ b/cc/daead/aes_siv_parameters_test.cc
@@ -0,0 +1,182 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/daead/aes_siv_parameters.h"
+
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct CreateTestCase {
+  AesSivParameters::Variant variant;
+  int key_size;
+  bool has_id_requirement;
+};
+
+using AesSivParametersBuildTest = TestWithParam<CreateTestCase>;
+
+INSTANTIATE_TEST_SUITE_P(
+    AesSivParametersBuildTestSuite, AesSivParametersBuildTest,
+    Values(CreateTestCase{AesSivParameters::Variant::kTink, /*key_size=*/32,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{AesSivParameters::Variant::kCrunchy, /*key_size=*/48,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{AesSivParameters::Variant::kNoPrefix, /*key_size=*/64,
+                          /*has_id_requirement=*/false}));
+
+TEST_P(AesSivParametersBuildTest, Create) {
+  CreateTestCase test_case = GetParam();
+
+  util::StatusOr<AesSivParameters> parameters =
+      AesSivParameters::Create(test_case.key_size, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  EXPECT_THAT(parameters->KeySizeInBytes(), Eq(test_case.key_size));
+  EXPECT_THAT(parameters->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(parameters->HasIdRequirement(), Eq(test_case.has_id_requirement));
+}
+
+TEST(AesSivParametersTest, CreateWithInvalidVariantFails) {
+  EXPECT_THAT(AesSivParameters::Create(
+                  /*key_size_in_bytes=*/64,
+                  AesSivParameters::Variant::
+                      kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesSivParametersTest, CreateWithInvalidKeySizeFails) {
+  EXPECT_THAT(AesSivParameters::Create(/*key_size_in_bytes=*/31,
+                                       AesSivParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesSivParameters::Create(/*key_size_in_bytes=*/33,
+                                       AesSivParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesSivParameters::Create(/*key_size_in_bytes=*/47,
+                                       AesSivParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesSivParameters::Create(/*key_size_in_bytes=*/49,
+                                       AesSivParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesSivParameters::Create(/*key_size_in_bytes=*/63,
+                                       AesSivParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesSivParameters::Create(/*key_size_in_bytes=*/65,
+                                       AesSivParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesSivParametersTest, CopyConstructor) {
+  util::StatusOr<AesSivParameters> parameters = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  AesSivParameters copy(*parameters);
+  EXPECT_THAT(copy.KeySizeInBytes(), Eq(64));
+  EXPECT_THAT(copy.GetVariant(), Eq(AesSivParameters::Variant::kTink));
+  EXPECT_THAT(copy.HasIdRequirement(), IsTrue());
+}
+
+TEST(AesSivParametersTest, CopyAssignment) {
+  util::StatusOr<AesSivParameters> parameters = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  AesSivParameters copy = *parameters;
+  EXPECT_THAT(copy.KeySizeInBytes(), Eq(64));
+  EXPECT_THAT(copy.GetVariant(), Eq(AesSivParameters::Variant::kTink));
+  EXPECT_THAT(copy.HasIdRequirement(), IsTrue());
+}
+
+using AesSivParametersVariantTest =
+    TestWithParam<std::tuple<int, AesSivParameters::Variant>>;
+
+INSTANTIATE_TEST_SUITE_P(AesSivParametersVariantTestSuite,
+                         AesSivParametersVariantTest,
+                         Combine(Values(32, 48, 64),
+                                 Values(AesSivParameters::Variant::kTink,
+                                        AesSivParameters::Variant::kCrunchy,
+                                        AesSivParameters::Variant::kNoPrefix)));
+
+TEST_P(AesSivParametersVariantTest, ParametersEquals) {
+  int key_size;
+  AesSivParameters::Variant variant;
+  std::tie(key_size, variant) = GetParam();
+
+  util::StatusOr<AesSivParameters> parameters =
+      AesSivParameters::Create(key_size, variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesSivParameters> other_parameters =
+      AesSivParameters::Create(key_size, variant);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters == *other_parameters);
+  EXPECT_TRUE(*other_parameters == *parameters);
+  EXPECT_FALSE(*parameters != *other_parameters);
+  EXPECT_FALSE(*other_parameters != *parameters);
+}
+
+TEST(AesSivParametersTest, KeySizeNotEqual) {
+  util::StatusOr<AesSivParameters> parameters = AesSivParameters::Create(
+      /*key_size_in_bytes=*/48, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesSivParameters> other_parameters = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesSivParametersTest, VariantNotEqual) {
+  util::StatusOr<AesSivParameters> parameters = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesSivParameters> other_parameters = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kNoPrefix);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/aes_siv_proto_serialization.cc b/cc/daead/aes_siv_proto_serialization.cc
new file mode 100644
index 0000000..c1711c5
--- /dev/null
+++ b/cc/daead/aes_siv_proto_serialization.cc
@@ -0,0 +1,237 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/daead/aes_siv_proto_serialization.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/daead/aes_siv_key.h"
+#include "tink/daead/aes_siv_parameters.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::google::crypto::tink::AesSivKeyFormat;
+using ::google::crypto::tink::OutputPrefixType;
+
+using AesSivProtoParametersParserImpl =
+    internal::ParametersParserImpl<internal::ProtoParametersSerialization,
+                                   AesSivParameters>;
+using AesSivProtoParametersSerializerImpl =
+    internal::ParametersSerializerImpl<AesSivParameters,
+                                       internal::ProtoParametersSerialization>;
+using AesSivProtoKeyParserImpl =
+    internal::KeyParserImpl<internal::ProtoKeySerialization, AesSivKey>;
+using AesSivProtoKeySerializerImpl =
+    internal::KeySerializerImpl<AesSivKey, internal::ProtoKeySerialization>;
+
+const absl::string_view kTypeUrl =
+    "type.googleapis.com/google.crypto.tink.AesSivKey";
+
+util::StatusOr<AesSivParameters::Variant> ToVariant(
+    OutputPrefixType output_prefix_type) {
+  switch (output_prefix_type) {
+    case OutputPrefixType::LEGACY:
+      ABSL_FALLTHROUGH_INTENDED;  // Parse LEGACY output prefix as CRUNCHY.
+    case OutputPrefixType::CRUNCHY:
+      return AesSivParameters::Variant::kCrunchy;
+    case OutputPrefixType::RAW:
+      return AesSivParameters::Variant::kNoPrefix;
+    case OutputPrefixType::TINK:
+      return AesSivParameters::Variant::kTink;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine AesSivParameters::Variant");
+  }
+}
+
+util::StatusOr<OutputPrefixType> ToOutputPrefixType(
+    AesSivParameters::Variant variant) {
+  switch (variant) {
+    case AesSivParameters::Variant::kCrunchy:
+      return OutputPrefixType::CRUNCHY;
+    case AesSivParameters::Variant::kNoPrefix:
+      return OutputPrefixType::RAW;
+    case AesSivParameters::Variant::kTink:
+      return OutputPrefixType::TINK;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine output prefix type");
+  }
+}
+
+util::StatusOr<AesSivParameters> ParseParameters(
+    const internal::ProtoParametersSerialization& serialization) {
+  if (serialization.GetKeyTemplate().type_url() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing AesSivParameters.");
+  }
+
+  AesSivKeyFormat proto_key_format;
+  if (!proto_key_format.ParseFromString(
+          serialization.GetKeyTemplate().value())) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse AesSivKeyFormat proto");
+  }
+  if (proto_key_format.version() != 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Only version 0 keys are accepted.");
+  }
+
+  util::StatusOr<AesSivParameters::Variant> variant =
+      ToVariant(serialization.GetKeyTemplate().output_prefix_type());
+  if (!variant.ok()) return variant.status();
+
+  return AesSivParameters::Create(proto_key_format.key_size(), *variant);
+}
+
+util::StatusOr<internal::ProtoParametersSerialization> SerializeParameters(
+    const AesSivParameters& parameters) {
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(parameters.GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+  AesSivKeyFormat proto_key_format;
+  proto_key_format.set_key_size(parameters.KeySizeInBytes());
+
+  return internal::ProtoParametersSerialization::Create(
+      kTypeUrl, *output_prefix_type, proto_key_format.SerializeAsString());
+}
+
+util::StatusOr<AesSivKey> ParseKey(
+    const internal::ProtoKeySerialization& serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  if (serialization.TypeUrl() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing AesSivKey.");
+  }
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+  google::crypto::tink::AesSivKey proto_key;
+  RestrictedData restricted_data = serialization.SerializedKeyProto();
+  // OSS proto library complains if input is not converted to a string.
+  if (!proto_key.ParseFromString(
+          std::string(restricted_data.GetSecret(*token)))) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse AesSivKey proto");
+  }
+  if (proto_key.version() != 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Only version 0 keys are accepted.");
+  }
+
+  util::StatusOr<AesSivParameters::Variant> variant =
+      ToVariant(serialization.GetOutputPrefixType());
+  if (!variant.ok()) return variant.status();
+
+  util::StatusOr<AesSivParameters> parameters =
+      AesSivParameters::Create(proto_key.key_value().length(), *variant);
+  if (!parameters.ok()) return parameters.status();
+
+  return AesSivKey::Create(
+      *parameters, RestrictedData(proto_key.key_value(), *token),
+      serialization.IdRequirement(), GetPartialKeyAccess());
+}
+
+util::StatusOr<internal::ProtoKeySerialization> SerializeKey(
+    const AesSivKey& key, absl::optional<SecretKeyAccessToken> token) {
+  util::StatusOr<RestrictedData> restricted_input =
+      key.GetKeyBytes(GetPartialKeyAccess());
+  if (!restricted_input.ok()) return restricted_input.status();
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+
+  google::crypto::tink::AesSivKey proto_key;
+  proto_key.set_version(0);
+  // OSS proto library complains if input is not converted to a string.
+  proto_key.set_key_value(std::string(restricted_input->GetSecret(*token)));
+
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(key.GetParameters().GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+  RestrictedData restricted_output =
+      RestrictedData(proto_key.SerializeAsString(), *token);
+  return internal::ProtoKeySerialization::Create(
+      kTypeUrl, restricted_output, google::crypto::tink::KeyData::SYMMETRIC,
+      *output_prefix_type, key.GetIdRequirement());
+}
+
+AesSivProtoParametersParserImpl* AesSivProtoParametersParser() {
+  static auto* parser =
+      new AesSivProtoParametersParserImpl(kTypeUrl, ParseParameters);
+  return parser;
+}
+
+AesSivProtoParametersSerializerImpl* AesSivProtoParametersSerializer() {
+  static auto* serializer =
+      new AesSivProtoParametersSerializerImpl(kTypeUrl, SerializeParameters);
+  return serializer;
+}
+
+AesSivProtoKeyParserImpl* AesSivProtoKeyParser() {
+  static auto* parser = new AesSivProtoKeyParserImpl(kTypeUrl, ParseKey);
+  return parser;
+}
+
+AesSivProtoKeySerializerImpl* AesSivProtoKeySerializer() {
+  static auto* serializer = new AesSivProtoKeySerializerImpl(SerializeKey);
+  return serializer;
+}
+
+}  // namespace
+
+util::Status RegisterAesSivProtoSerialization() {
+  util::Status status =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .RegisterParametersParser(AesSivProtoParametersParser());
+  if (!status.ok()) return status;
+
+  status = internal::MutableSerializationRegistry::GlobalInstance()
+               .RegisterParametersSerializer(AesSivProtoParametersSerializer());
+  if (!status.ok()) return status;
+
+  status = internal::MutableSerializationRegistry::GlobalInstance()
+               .RegisterKeyParser(AesSivProtoKeyParser());
+  if (!status.ok()) return status;
+
+  return internal::MutableSerializationRegistry::GlobalInstance()
+      .RegisterKeySerializer(AesSivProtoKeySerializer());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/aes_siv_proto_serialization.h b/cc/daead/aes_siv_proto_serialization.h
new file mode 100644
index 0000000..daff867
--- /dev/null
+++ b/cc/daead/aes_siv_proto_serialization.h
@@ -0,0 +1,31 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_AES_SIV_PROTO_SERIALIZATION_H_
+#define TINK_DAEAD_AES_SIV_PROTO_SERIALIZATION_H_
+
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+// Registers proto parsers and serializers for AES-SIV parameters and keys.
+crypto::tink::util::Status RegisterAesSivProtoSerialization();
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_AES_SIV_PROTO_SERIALIZATION_H_
diff --git a/cc/daead/aes_siv_proto_serialization_test.cc b/cc/daead/aes_siv_proto_serialization_test.cc
new file mode 100644
index 0000000..1f6a45a
--- /dev/null
+++ b/cc/daead/aes_siv_proto_serialization_test.cc
@@ -0,0 +1,395 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/daead/aes_siv_proto_serialization.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/daead/aes_siv_key.h"
+#include "tink/daead/aes_siv_parameters.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_matchers.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::subtle::Random;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesSivKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::NotNull;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  AesSivParameters::Variant variant;
+  OutputPrefixType output_prefix_type;
+  int key_size;
+  absl::optional<int> id;
+  std::string output_prefix;
+};
+
+class AesSivProtoSerializationTest : public TestWithParam<TestCase> {
+ protected:
+  void SetUp() override {
+    internal::MutableSerializationRegistry::GlobalInstance().Reset();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    AesSivProtoSerializationTestSuite, AesSivProtoSerializationTest,
+    Values(TestCase{AesSivParameters::Variant::kTink, OutputPrefixType::TINK,
+                    /*key_size=*/32, /*id=*/0x02030400,
+                    /*output_prefix=*/std::string("\x01\x02\x03\x04\x00", 5)},
+           TestCase{AesSivParameters::Variant::kCrunchy,
+                    OutputPrefixType::CRUNCHY, /*key_size=*/48,
+                    /*id=*/0x01030005,
+                    /*output_prefix=*/std::string("\x00\x01\x03\x00\x05", 5)},
+           TestCase{AesSivParameters::Variant::kNoPrefix, OutputPrefixType::RAW,
+                    /*key_size=*/64, /*id=*/absl::nullopt,
+                    /*output_prefix=*/""}));
+
+TEST_P(AesSivProtoSerializationTest, ParseParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  AesSivKeyFormat key_format_proto;
+  key_format_proto.set_version(0);
+  key_format_proto.set_key_size(test_case.key_size);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey",
+          test_case.output_prefix_type, key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), test_case.id.has_value());
+
+  const AesSivParameters* siv_params =
+      dynamic_cast<const AesSivParameters*>(params->get());
+  ASSERT_THAT(siv_params, NotNull());
+  EXPECT_THAT(siv_params->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(siv_params->KeySizeInBytes(), Eq(test_case.key_size));
+}
+
+TEST_F(AesSivProtoSerializationTest, ParseParametersWithInvalidSerialization) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  AesSivKeyFormat key_format_proto;
+  key_format_proto.set_version(0);
+  key_format_proto.set_key_size(64);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey",
+          OutputPrefixType::RAW, "invalid_serialization");
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesSivProtoSerializationTest, ParseParametersWithUnkownOutputPrefix) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  AesSivKeyFormat key_format_proto;
+  key_format_proto.set_version(0);
+  key_format_proto.set_key_size(64);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey",
+          OutputPrefixType::UNKNOWN_PREFIX,
+          key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesSivProtoSerializationTest, ParseParametersWithInvalidVersion) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  AesSivKeyFormat key_format_proto;
+  key_format_proto.set_version(1);
+  key_format_proto.set_key_size(64);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey",
+          OutputPrefixType::RAW, key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  EXPECT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesSivProtoSerializationTest, SerializeParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  util::StatusOr<AesSivParameters> parameters =
+      AesSivParameters::Create(test_case.key_size, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.AesSivKey"));
+
+  const internal::ProtoParametersSerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoParametersSerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().type_url(),
+              Eq("type.googleapis.com/google.crypto.tink.AesSivKey"));
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().output_prefix_type(),
+              Eq(test_case.output_prefix_type));
+
+  AesSivKeyFormat key_format;
+  ASSERT_THAT(
+      key_format.ParseFromString(proto_serialization->GetKeyTemplate().value()),
+      IsTrue());
+  EXPECT_THAT(key_format.key_size(), Eq(test_case.key_size));
+}
+
+TEST_P(AesSivProtoSerializationTest, ParseKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  google::crypto::tink::AesSivKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey", serialized_key,
+          KeyData::SYMMETRIC, test_case.output_prefix_type, test_case.id);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT((*key)->GetIdRequirement(), Eq(test_case.id));
+  EXPECT_THAT((*key)->GetParameters().HasIdRequirement(),
+              test_case.id.has_value());
+
+  util::StatusOr<AesSivParameters> expected_parameters =
+      AesSivParameters::Create(test_case.key_size, test_case.variant);
+  ASSERT_THAT(expected_parameters, IsOk());
+
+  util::StatusOr<AesSivKey> expected_key = AesSivKey::Create(
+      *expected_parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      test_case.id, GetPartialKeyAccess());
+  ASSERT_THAT(expected_key, IsOk());
+
+  EXPECT_THAT(**key, Eq(*expected_key));
+}
+
+TEST_F(AesSivProtoSerializationTest, ParseLegacyKeyAsCrunchy) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(64);
+  google::crypto::tink::AesSivKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::LEGACY, /*id_requirement=*/123);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+
+  const AesSivKey* aes_siv_key = dynamic_cast<const AesSivKey*>(key->get());
+  ASSERT_THAT(aes_siv_key, NotNull());
+  EXPECT_THAT(aes_siv_key->GetParameters().GetVariant(),
+              Eq(AesSivParameters::Variant::kCrunchy));
+}
+
+TEST_F(AesSivProtoSerializationTest, ParseKeyWithInvalidSerialization) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  RestrictedData serialized_key =
+      RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesSivProtoSerializationTest, ParseKeyNoSecretKeyAccess) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(64);
+  google::crypto::tink::AesSivKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, /*token=*/absl::nullopt);
+  EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesSivProtoSerializationTest, ParseKeyWithInvalidVersion) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(64);
+  google::crypto::tink::AesSivKey key_proto;
+  key_proto.set_version(1);  // Invalid version number.
+  key_proto.set_key_value(raw_key_bytes);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  EXPECT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesSivProtoSerializationTest, SerializeKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  util::StatusOr<AesSivParameters> parameters =
+      AesSivParameters::Create(test_case.key_size, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  util::StatusOr<AesSivKey> key = AesSivKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      test_case.id, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.AesSivKey"));
+
+  const internal::ProtoKeySerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoKeySerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->TypeUrl(),
+              Eq("type.googleapis.com/google.crypto.tink.AesSivKey"));
+  EXPECT_THAT(proto_serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC));
+  EXPECT_THAT(proto_serialization->GetOutputPrefixType(),
+              Eq(test_case.output_prefix_type));
+  EXPECT_THAT(proto_serialization->IdRequirement(), Eq(test_case.id));
+
+  google::crypto::tink::AesSivKey proto_key;
+  // OSS proto library complains if input is not converted to a string.
+  ASSERT_THAT(proto_key.ParseFromString(std::string(
+                  proto_serialization->SerializedKeyProto().GetSecret(
+                      InsecureSecretKeyAccess::Get()))),
+              IsTrue());
+  EXPECT_THAT(proto_key.key_value().size(), Eq(test_case.key_size));
+}
+
+TEST_F(AesSivProtoSerializationTest, SerializeKeyNoSecretKeyAccess) {
+  ASSERT_THAT(RegisterAesSivProtoSerialization(), IsOk());
+
+  util::StatusOr<AesSivParameters> parameters = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(64);
+  util::StatusOr<AesSivKey> key = AesSivKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(*key, absl::nullopt);
+  EXPECT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_config.cc b/cc/daead/deterministic_aead_config.cc
index 749f8cf..5c1a229 100644
--- a/cc/daead/deterministic_aead_config.cc
+++ b/cc/daead/deterministic_aead_config.cc
@@ -17,26 +17,17 @@
 #include "tink/daead/deterministic_aead_config.h"
 
 #include "absl/memory/memory.h"
-#include "tink/config/config_util.h"
 #include "tink/config/tink_fips.h"
 #include "tink/daead/aes_siv_key_manager.h"
+#include "tink/daead/aes_siv_proto_serialization.h"
 #include "tink/daead/deterministic_aead_wrapper.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
-#include "proto/config.pb.h"
-
-using google::crypto::tink::RegistryConfig;
 
 namespace crypto {
 namespace tink {
 
 // static
-const RegistryConfig& DeterministicAeadConfig::Latest() {
-  static const RegistryConfig* config = new RegistryConfig();
-  return *config;
-}
-
-// static
 util::Status DeterministicAeadConfig::Register() {
   // Currently there are no FIPS-validated deterministic AEAD key managers
   // available, therefore none will be registered in FIPS only mode.
@@ -49,6 +40,9 @@
       absl::make_unique<AesSivKeyManager>(), true);
   if (!status.ok()) return status;
 
+  status = RegisterAesSivProtoSerialization();
+  if (!status.ok()) return status;
+
   // Register primitive wrapper.
   return Registry::RegisterPrimitiveWrapper(
       absl::make_unique<DeterministicAeadWrapper>());
diff --git a/cc/daead/deterministic_aead_config.h b/cc/daead/deterministic_aead_config.h
index 1441fdd..3fdd199 100644
--- a/cc/daead/deterministic_aead_config.h
+++ b/cc/daead/deterministic_aead_config.h
@@ -35,14 +35,6 @@
 //
 class DeterministicAeadConfig {
  public:
-  static constexpr char kCatalogueName[] = "TinkDeterministicAead";
-  static constexpr char kPrimitiveName[] = "DeterministicAead";
-
-  // Returns config of DeterministicAead implementations supported
-  // in the current Tink release.
-  ABSL_DEPRECATED("This is not supported anymore.")
-  static const google::crypto::tink::RegistryConfig& Latest();
-
   // Registers DeterministicAead primitive wrapper and key managers for all
   // DeterministicAead key types from the current Tink release.
   static crypto::tink::util::Status Register();
diff --git a/cc/daead/deterministic_aead_config_test.cc b/cc/daead/deterministic_aead_config_test.cc
index 1a59d14..ba833d1 100644
--- a/cc/daead/deterministic_aead_config_test.cc
+++ b/cc/daead/deterministic_aead_config_test.cc
@@ -17,17 +17,24 @@
 #include "tink/daead/deterministic_aead_config.h"
 
 #include <list>
+#include <memory>
 #include <utility>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
-#include "tink/config.h"
 #include "tink/config/tink_fips.h"
+#include "tink/daead/aes_siv_key.h"
 #include "tink/daead/aes_siv_key_manager.h"
+#include "tink/daead/aes_siv_parameters.h"
 #include "tink/daead/deterministic_aead_key_templates.h"
 #include "tink/deterministic_aead.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
 #include "tink/keyset_handle.h"
+#include "tink/partial_key_access.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
 #include "tink/util/test_matchers.h"
@@ -40,11 +47,16 @@
 using ::crypto::tink::test::DummyDeterministicAead;
 using ::crypto::tink::test::IsOk;
 using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
 using ::testing::Eq;
 
 class DeterministicAeadConfigTest : public ::testing::Test {
  protected:
-  void SetUp() override { Registry::Reset(); }
+  void SetUp() override {
+    Registry::Reset();
+    internal::MutableSerializationRegistry::GlobalInstance().Reset();
+  }
 };
 
 TEST_F(DeterministicAeadConfigTest, Basic) {
@@ -121,6 +133,98 @@
   }
 }
 
+TEST_F(DeterministicAeadConfigTest, AesSivProtoParamsSerializationRegistered) {
+  if (IsFipsModeEnabled()) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
+  util::StatusOr<internal::ProtoParametersSerialization>
+      proto_params_serialization =
+          internal::ProtoParametersSerialization::Create(
+              DeterministicAeadKeyTemplates::Aes256Siv());
+  ASSERT_THAT(proto_params_serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> parsed_params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *proto_params_serialization);
+  ASSERT_THAT(parsed_params.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  util::StatusOr<AesSivParameters> params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_params =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(*params);
+  ASSERT_THAT(serialized_params.status(),
+              StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(DeterministicAeadConfig::Register(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> parsed_params2 =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *proto_params_serialization);
+  ASSERT_THAT(parsed_params2, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_params2 =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(*params);
+  ASSERT_THAT(serialized_params2, IsOk());
+}
+
+TEST_F(DeterministicAeadConfigTest, AesSivProtoKeySerializationRegistered) {
+  if (IsFipsModeEnabled()) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
+  google::crypto::tink::AesSivKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(subtle::Random::GetRandomBytes(64));
+
+  util::StatusOr<internal::ProtoKeySerialization> proto_key_serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesSivKey",
+          RestrictedData(key_proto.SerializeAsString(),
+                         InsecureSecretKeyAccess::Get()),
+          KeyData::SYMMETRIC, OutputPrefixType::TINK, /*id_requirement=*/123);
+  ASSERT_THAT(proto_key_serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> parsed_key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *proto_key_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_key.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  util::StatusOr<AesSivParameters> params = AesSivParameters::Create(
+      /*key_size_in_bytes=*/64, AesSivParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  util::StatusOr<AesSivKey> key =
+      AesSivKey::Create(*params,
+                        RestrictedData(subtle::Random::GetRandomBytes(64),
+                                       InsecureSecretKeyAccess::Get()),
+                        /*id_requirement=*/123, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_key =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialized_key.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(DeterministicAeadConfig::Register(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> parsed_key2 =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *proto_key_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_key2, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_key2 =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialized_key2, IsOk());
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/daead/deterministic_aead_factory.cc b/cc/daead/deterministic_aead_factory.cc
index bdd23ed..8ae089a 100644
--- a/cc/daead/deterministic_aead_factory.cc
+++ b/cc/daead/deterministic_aead_factory.cc
@@ -16,6 +16,8 @@
 
 #include "tink/daead/deterministic_aead_factory.h"
 
+#include <memory>
+
 #include "tink/daead/deterministic_aead_wrapper.h"
 #include "tink/deterministic_aead.h"
 #include "tink/key_manager.h"
diff --git a/cc/daead/deterministic_aead_factory.h b/cc/daead/deterministic_aead_factory.h
index 5359a29..dcd365b 100644
--- a/cc/daead/deterministic_aead_factory.h
+++ b/cc/daead/deterministic_aead_factory.h
@@ -17,6 +17,8 @@
 #ifndef TINK_DAEAD_DETERMINISTIC_AEAD_FACTORY_H_
 #define TINK_DAEAD_DETERMINISTIC_AEAD_FACTORY_H_
 
+#include <memory>
+
 #include "absl/base/macros.h"
 #include "tink/deterministic_aead.h"
 #include "tink/key_manager.h"
diff --git a/cc/daead/deterministic_aead_factory_test.cc b/cc/daead/deterministic_aead_factory_test.cc
index 78cc363..ae6ac6d 100644
--- a/cc/daead/deterministic_aead_factory_test.cc
+++ b/cc/daead/deterministic_aead_factory_test.cc
@@ -31,7 +31,6 @@
 #include "tink/util/test_util.h"
 #include "proto/aes_siv.pb.h"
 
-using crypto::tink::TestKeysetHandle;
 using crypto::tink::test::AddRawKey;
 using crypto::tink::test::AddTinkKey;
 using google::crypto::tink::AesSivKeyFormat;
diff --git a/cc/daead/deterministic_aead_key.h b/cc/daead/deterministic_aead_key.h
new file mode 100644
index 0000000..1534754
--- /dev/null
+++ b/cc/daead/deterministic_aead_key.h
@@ -0,0 +1,52 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_DETERMINISTIC_AEAD_KEY_H_
+#define TINK_DAEAD_DETERMINISTIC_AEAD_KEY_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/daead/deterministic_aead_parameters.h"
+#include "tink/key.h"
+
+namespace crypto {
+namespace tink {
+
+// Represents a function to encrypt and decrypt data using deterministic
+// authenticated encryption with associated data (Deterministic AEAD).
+class DeterministicAeadKey : public Key {
+ public:
+  // Returns the bytes prefixed to every ciphertext generated by this key.
+  //
+  // In order to make key rotation more efficient, Tink allows every
+  // Deterministic AEAD key to have an associated ciphertext output prefix. When
+  // decrypting a ciphertext, only keys with a matching prefix have to be tried.
+  //
+  // Note that a priori, the output prefix may not be unique in a keyset
+  // (i.e., different keys in a keyset may have the same prefix or one prefix
+  // may be a prefix of another). To avoid this, built-in Tink keys use the
+  // convention that the prefix is either '0x00<big endian key id>' or
+  // '0x01<big endian key id>'.
+  virtual absl::string_view GetOutputPrefix() const = 0;
+
+  const DeterministicAeadParameters& GetParameters() const override = 0;
+
+  bool operator==(const Key& other) const override = 0;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_DETERMINISTIC_AEAD_KEY_H_
diff --git a/cc/daead/deterministic_aead_parameters.h b/cc/daead/deterministic_aead_parameters.h
new file mode 100644
index 0000000..53b746a
--- /dev/null
+++ b/cc/daead/deterministic_aead_parameters.h
@@ -0,0 +1,32 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_DETERMINISTIC_AEAD_PARAMETERS_H_
+#define TINK_DAEAD_DETERMINISTIC_AEAD_PARAMETERS_H_
+
+#include "tink/parameters.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes a `DeterministicAeadKey` (e.g., key attributes), excluding the
+// randomly chosen key material.
+class DeterministicAeadParameters : public Parameters {};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_DETERMINISTIC_AEAD_PARAMETERS_H_
diff --git a/cc/daead/deterministic_aead_wrapper.cc b/cc/daead/deterministic_aead_wrapper.cc
index 3db1e99..13cc57f 100644
--- a/cc/daead/deterministic_aead_wrapper.cc
+++ b/cc/daead/deterministic_aead_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/daead/deterministic_aead_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -70,7 +71,7 @@
       absl::string_view ciphertext,
       absl::string_view associated_data) const override;
 
-  ~DeterministicAeadSetWrapper() override {}
+  ~DeterministicAeadSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<DeterministicAead>> daead_set_;
diff --git a/cc/daead/deterministic_aead_wrapper.h b/cc/daead/deterministic_aead_wrapper.h
index ea95b94..77dd3d7 100644
--- a/cc/daead/deterministic_aead_wrapper.h
+++ b/cc/daead/deterministic_aead_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_DAEAD_DETERMINISTIC_AEAD_WRAPPER_H_
 #define TINK_DAEAD_DETERMINISTIC_AEAD_WRAPPER_H_
 
+#include <memory>
+
 #include "absl/strings/string_view.h"
 #include "tink/deterministic_aead.h"
 #include "tink/primitive_set.h"
diff --git a/cc/daead/failing_daead.cc b/cc/daead/failing_daead.cc
index 61b30f9..b3fa26f 100644
--- a/cc/daead/failing_daead.cc
+++ b/cc/daead/failing_daead.cc
@@ -15,6 +15,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/daead/failing_daead.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/daead/failing_daead.h b/cc/daead/failing_daead.h
index 551ad4b..74c7b50 100644
--- a/cc/daead/failing_daead.h
+++ b/cc/daead/failing_daead.h
@@ -16,6 +16,7 @@
 #ifndef TINK_DAEAD_FAILING_DAEAD_H_
 #define TINK_DAEAD_FAILING_DAEAD_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
diff --git a/cc/daead/subtle/aead_or_daead.cc b/cc/daead/subtle/aead_or_daead.cc
index c8c8b57..419c333 100644
--- a/cc/daead/subtle/aead_or_daead.cc
+++ b/cc/daead/subtle/aead_or_daead.cc
@@ -16,6 +16,7 @@
 
 #include "tink/daead/subtle/aead_or_daead.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/daead/subtle/aead_or_daead_test.cc b/cc/daead/subtle/aead_or_daead_test.cc
index 8244b72..a70e204 100644
--- a/cc/daead/subtle/aead_or_daead_test.cc
+++ b/cc/daead/subtle/aead_or_daead_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/daead/subtle/aead_or_daead.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/deterministic_aead.h b/cc/deterministic_aead.h
index fa73762..310f72f 100644
--- a/cc/deterministic_aead.h
+++ b/cc/deterministic_aead.h
@@ -53,7 +53,7 @@
       absl::string_view ciphertext,
       absl::string_view associated_data) const = 0;
 
-  virtual ~DeterministicAead() {}
+  virtual ~DeterministicAead() = default;
 };
 
 }  // namespace tink
diff --git a/cc/examples/.bazelrc b/cc/examples/.bazelrc
new file mode 100644
index 0000000..0fe2c3f
--- /dev/null
+++ b/cc/examples/.bazelrc
@@ -0,0 +1,4 @@
+# Minumum C++ version. Override it building this project with
+# `bazel build --cxxopt='-std=c++<XY>' --host_cxxopt='c++<XY>' ...`
+# (Both -std and --host_cxxopt must be set to force the desired version.)
+build --cxxopt='-std=c++14' --host_cxxopt='-std=c++14'
diff --git a/cc/examples/.bazelversion b/cc/examples/.bazelversion
index ac14c3d..09b254e 100644
--- a/cc/examples/.bazelversion
+++ b/cc/examples/.bazelversion
@@ -1 +1 @@
-5.1.1
+6.0.0
diff --git a/cc/examples/CMakeLists.txt b/cc/examples/CMakeLists.txt
new file mode 100644
index 0000000..a85f910
--- /dev/null
+++ b/cc/examples/CMakeLists.txt
@@ -0,0 +1,44 @@
+cmake_minimum_required(VERSION 3.13)
+
+project(Examples CXX)
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(CMAKE_BUILD_TYPE Release)
+
+# Import Tink as an in-tree dependency.
+add_subdirectory(../.. tink)
+
+# Make sure we have bash.
+find_program(BASH_PROGRAM bash REQUIRED)
+
+# Include path at the base of the examples folder.
+set(TINK_EXAMPLES_INCLUDE_PATH "${CMAKE_SOURCE_DIR}")
+
+include(FetchContent)
+
+FetchContent_Declare(
+  googletest
+  URL       https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz
+  URL_HASH  SHA256=b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5
+)
+
+FetchContent_GetProperties(googletest)
+if(NOT googletest_POPULATED)
+  FetchContent_Populate(googletest)
+  add_subdirectory(
+    ${googletest_SOURCE_DIR}
+    ${googletest_BINARY_DIR}
+    EXCLUDE_FROM_ALL)
+endif()
+
+enable_testing()
+
+add_subdirectory(aead)
+add_subdirectory(digital_signatures)
+add_subdirectory(hybrid_encryption)
+add_subdirectory(jwt)
+add_subdirectory(mac)
+add_subdirectory(util)
+add_subdirectory(walkthrough)
diff --git a/cc/examples/MODULE.bazel b/cc/examples/MODULE.bazel
new file mode 100644
index 0000000..654c59f
--- /dev/null
+++ b/cc/examples/MODULE.bazel
@@ -0,0 +1,11 @@
+"""Module definition for Tink C++ Examples."""
+# Omitting `version` because this is not meant to be depended on by other
+# modules.
+module(name = "tink_cc_examples")
+
+# Use local tink_cc.
+bazel_dep(name = "tink_cc", version = "")
+local_path_override(module_name = "tink_cc", path = "../")
+
+bazel_dep(name = "googletest", version = "1.12.1", repo_name = "com_google_googletest")
+bazel_dep(name = "abseil-cpp", version = "20230125.1", repo_name="com_google_absl")
diff --git a/cc/examples/WORKSPACE b/cc/examples/WORKSPACE
index b7fff2b..5cdbbc9 100644
--- a/cc/examples/WORKSPACE
+++ b/cc/examples/WORKSPACE
@@ -1,4 +1,4 @@
-workspace(name = "examples_cc")
+workspace(name = "tink_cc_examples")
 
 # The local_repository() rule is used below because the workspaces referenced
 # are all located within the Tink git repository.
diff --git a/cc/examples/WORKSPACE.bzlmod b/cc/examples/WORKSPACE.bzlmod
new file mode 100644
index 0000000..057d6aa
--- /dev/null
+++ b/cc/examples/WORKSPACE.bzlmod
@@ -0,0 +1 @@
+# This replaces the content of the WORKSPACE file when using --enable_bzlmod.
diff --git a/cc/examples/aead/BUILD.bazel b/cc/examples/aead/BUILD.bazel
index 80727bb..fa7907a 100644
--- a/cc/examples/aead/BUILD.bazel
+++ b/cc/examples/aead/BUILD.bazel
@@ -11,14 +11,12 @@
     name = "aead_cli",
     srcs = ["aead_cli.cc"],
     deps = [
+        "//util",
         "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/flags:parse",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/log:check",
         "@com_google_absl//absl/strings",
         "@tink_cc//:aead",
-        "@tink_cc//:cleartext_keyset_handle",
-        "@tink_cc//:json_keyset_reader",
         "@tink_cc//:keyset_handle",
         "@tink_cc//:keyset_reader",
         "@tink_cc//aead:aead_config",
diff --git a/cc/examples/aead/CMakeLists.txt b/cc/examples/aead/CMakeLists.txt
new file mode 100644
index 0000000..ae04ba6
--- /dev/null
+++ b/cc/examples/aead/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_executable(aead_cli aead_cli.cc)
+target_include_directories(aead_cli PUBLIC
+  "${CMAKE_CURRENT_SOURCE_DIR}"
+  "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(aead_cli
+  tink::static
+  absl::check
+  absl::flags_parse
+  util)
+
+add_test(
+  NAME aead_cli_test
+  COMMAND "${BASH_PROGRAM}"
+    "${CMAKE_CURRENT_SOURCE_DIR}/aead_cli_test.sh"
+    "${CMAKE_CURRENT_BINARY_DIR}/aead_cli"
+    "${CMAKE_CURRENT_SOURCE_DIR}/aead_test_keyset.json")
diff --git a/cc/examples/aead/aead_cli.cc b/cc/examples/aead/aead_cli.cc
index 028f9a1..36e3225 100644
--- a/cc/examples/aead/aead_cli.cc
+++ b/cc/examples/aead/aead_cli.cc
@@ -15,184 +15,117 @@
 ///////////////////////////////////////////////////////////////////////////////
 // [START aead-example]
 // A command-line utility for testing Tink AEAD.
-#include <fstream>
 #include <iostream>
 #include <memory>
-#include <sstream>
+#include <ostream>
 #include <string>
-#include <utility>
 
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
-#include "absl/memory/memory.h"
-#include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
+#include "absl/log/check.h"
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/aead/aead_config.h"
-#include "tink/cleartext_keyset_handle.h"
-#include "tink/json_keyset_reader.h"
+#include "util/util.h"
 #include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
 #include "tink/util/status.h"
 
 ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format");
 ABSL_FLAG(std::string, mode, "", "Mode of operation {encrypt|decrypt}");
 ABSL_FLAG(std::string, input_filename, "", "Filename to operate on");
 ABSL_FLAG(std::string, output_filename, "", "Output file name");
-ABSL_FLAG(std::string, associated_data, "", "Associated data for AEAD");
+ABSL_FLAG(std::string, associated_data, "",
+          "Associated data for AEAD (default: empty");
 
 namespace {
 
 using ::crypto::tink::Aead;
 using ::crypto::tink::AeadConfig;
-using ::crypto::tink::CleartextKeysetHandle;
-using ::crypto::tink::JsonKeysetReader;
 using ::crypto::tink::KeysetHandle;
-using ::crypto::tink::KeysetReader;
 using ::crypto::tink::util::Status;
 using ::crypto::tink::util::StatusOr;
 
 constexpr absl::string_view kEncrypt = "encrypt";
 constexpr absl::string_view kDecrypt = "decrypt";
 
-// Creates a KeysetReader that reads a JSON-formatted keyset
-// from the given file.
-StatusOr<std::unique_ptr<KeysetReader>> GetJsonKeysetReader(
-    const std::string& filename) {
-  std::clog << "Creating a JsonKeysetReader...\n";
-  auto key_input_stream = absl::make_unique<std::ifstream>();
-  key_input_stream->open(filename, std::ifstream::in);
-  return JsonKeysetReader::New(std::move(key_input_stream));
-}
-
-// Creates a KeysetHandle that for a keyset read from the given file,
-// which is expected to contain a JSON-formatted keyset.
-StatusOr<std::unique_ptr<KeysetHandle>> ReadKeyset(
-    const std::string& filename) {
-  StatusOr<std::unique_ptr<KeysetReader>> keyset_reader =
-      GetJsonKeysetReader(filename);
-  if (!keyset_reader.ok()) {
-    return keyset_reader.status();
-  }
-  return CleartextKeysetHandle::Read(*std::move(keyset_reader));
-}
-
-// Reads `filename` and returns the read content as a string, or an error status
-// if the file does not exist.
-StatusOr<std::string> Read(const std::string& filename) {
-  std::clog << "Reading the input...\n";
-  std::ifstream input_stream;
-  input_stream.open(filename, std::ifstream::in);
-  if (!input_stream.is_open()) {
-    return Status(absl::StatusCode::kInternal,
-                  absl::StrCat("Error opening input file ", filename));
-  }
-  std::stringstream input;
-  input << input_stream.rdbuf();
-  return input.str();
-}
-
-// Writes the given `data_to_write` to the specified file `filename`.
-Status Write(const std::string& data_to_write, const std::string& filename) {
-  std::clog << "Writing the output...\n";
-  std::ofstream output_stream(filename,
-                              std::ofstream::out | std::ofstream::binary);
-  if (!output_stream.is_open()) {
-    return Status(absl::StatusCode::kInternal,
-                  absl::StrCat("Error opening output file ", filename));
-  }
-  output_stream << data_to_write;
-  return crypto::tink::util::OkStatus();
+void ValidateParams() {
+  // [START_EXCLUDE]
+  CHECK(absl::GetFlag(FLAGS_mode) == kEncrypt ||
+        absl::GetFlag(FLAGS_mode) == kDecrypt)
+      << "Invalid mode; must be `encrypt` or `decrypt`";
+  CHECK(!absl::GetFlag(FLAGS_keyset_filename).empty())
+      << "Keyset file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_input_filename).empty())
+      << "Input file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_output_filename).empty())
+      << "Output file must be specified";
+  // [END_EXCLUDE]
 }
 
 }  // namespace
 
+namespace tink_cc_examples {
+
+// AEAD example CLI implementation.
+Status AeadCli(absl::string_view mode, const std::string& keyset_filename,
+               const std::string& input_filename,
+               const std::string& output_filename,
+               absl::string_view associated_data) {
+  Status result = AeadConfig::Register();
+  if (!result.ok()) return result;
+
+  // Read the keyset from file.
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      ReadJsonCleartextKeyset(keyset_filename);
+  if (!keyset_handle.ok()) return keyset_handle.status();
+
+  // Get the primitive.
+  StatusOr<std::unique_ptr<Aead>> aead = (*keyset_handle)->GetPrimitive<Aead>();
+  if (!aead.ok()) return aead.status();
+
+  // Read the input.
+  StatusOr<std::string> input_file_content = ReadFile(input_filename);
+  if (!input_file_content.ok()) return input_file_content.status();
+
+  // Compute the output.
+  std::string output;
+  if (mode == kEncrypt) {
+    StatusOr<std::string> encrypt_result =
+        (*aead)->Encrypt(*input_file_content, associated_data);
+    if (!encrypt_result.ok()) return encrypt_result.status();
+    output = encrypt_result.value();
+  } else {  // operation == kDecrypt.
+    StatusOr<std::string> decrypt_result =
+        (*aead)->Decrypt(*input_file_content, associated_data);
+    if (!decrypt_result.ok()) return decrypt_result.status();
+    output = decrypt_result.value();
+  }
+
+  // Write the output to the output file.
+  return WriteToFile(output, output_filename);
+}
+
+}  // namespace tink_cc_examples
+
 int main(int argc, char** argv) {
   absl::ParseCommandLine(argc, argv);
 
+  ValidateParams();
+
   std::string mode = absl::GetFlag(FLAGS_mode);
   std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
   std::string input_filename = absl::GetFlag(FLAGS_input_filename);
   std::string output_filename = absl::GetFlag(FLAGS_output_filename);
   std::string associated_data = absl::GetFlag(FLAGS_associated_data);
 
-  if (mode.empty()) {
-    std::cerr << "Mode must be specified with --mode=<" << kEncrypt << "|"
-              << kDecrypt << ">." << std::endl;
-    exit(1);
-  }
-
-  if (mode != kEncrypt && mode != kDecrypt) {
-    std::cerr << "Unknown mode '" << mode << "'; "
-              << "Expected either " << kEncrypt << " or " << kDecrypt << "."
-              << std::endl;
-    exit(1);
-  }
   std::clog << "Using keyset from file " << keyset_filename << " to AEAD-"
             << mode << " file " << input_filename << " with associated data '"
             << associated_data << "'." << std::endl;
   std::clog << "The resulting output will be written to " << output_filename
             << std::endl;
 
-  Status result = AeadConfig::Register();
-  if (!result.ok()) {
-    std::cerr << result.message() << std::endl;
-    exit(1);
-  }
-
-  // Read the keyset from file.
-  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
-      ReadKeyset(keyset_filename);
-  if (!keyset_handle.ok()) {
-    std::cerr << keyset_handle.status().message() << std::endl;
-    exit(1);
-  }
-
-  // Get the primitive.
-  StatusOr<std::unique_ptr<Aead>> aead_primitive =
-      (*keyset_handle)->GetPrimitive<Aead>();
-  if (!aead_primitive.ok()) {
-    std::cerr << aead_primitive.status().message() << std::endl;
-    exit(1);
-  }
-
-  // Read the input.
-  StatusOr<std::string> input_file_content = Read(input_filename);
-  if (!input_file_content.ok()) {
-    std::cerr << input_file_content.status().message() << std::endl;
-    exit(1);
-  }
-
-  // Compute the output.
-  std::clog << mode << "ing...\n";
-  std::string output;
-  if (mode == kEncrypt) {
-    StatusOr<std::string> encrypt_result =
-        (*aead_primitive)->Encrypt(*input_file_content, associated_data);
-    if (!encrypt_result.ok()) {
-      std::cerr << encrypt_result.status().message() << std::endl;
-      exit(1);
-    }
-    output = encrypt_result.value();
-  } else {  // operation == kDecrypt.
-    StatusOr<std::string> decrypt_result =
-        (*aead_primitive)->Decrypt(*input_file_content, associated_data);
-    if (!decrypt_result.ok()) {
-      std::cerr << decrypt_result.status().message() << std::endl;
-      exit(1);
-    }
-    output = decrypt_result.value();
-  }
-
-  // Write the output to the output file.
-  Status write_result = Write(output, output_filename);
-  if (!write_result.ok()) {
-    std::cerr << write_result.message() << std::endl;
-    exit(1);
-  }
-
-  std::clog << "All done." << std::endl;
+  CHECK_OK(tink_cc_examples::AeadCli(mode, keyset_filename, input_filename,
+                                     output_filename, associated_data));
   return 0;
 }
 // [END aead-example]
diff --git a/cc/examples/aead/aead_cli_test.sh b/cc/examples/aead/aead_cli_test.sh
index ad5579e..d1d2627 100755
--- a/cc/examples/aead/aead_cli_test.sh
+++ b/cc/examples/aead/aead_cli_test.sh
@@ -20,6 +20,8 @@
 # Tests for Tink CC AEAD.
 #############################################################################
 
+: "${TEST_TMPDIR:=$(mktemp -d)}"
+
 readonly CLI="$1"
 readonly KEYSET_FILE="$2"
 readonly DATA_FILE="${TEST_TMPDIR}/example_data.txt"
@@ -44,7 +46,7 @@
 }
 
 #######################################
-# Asserts that the outcome of the latest test command was the expected one.
+# Asserts that the outcome of the latest test command is 0.
 #
 # If not, it terminates the test execution.
 #
@@ -52,23 +54,29 @@
 #   TEST_STATUS
 #   TEST_NAME
 #   TEST_CASE
-# Arguments:
-#   expected_outcome: The expected outcome.
 #######################################
-_assert_test_command_outcome() {
-  expected_outcome="$1"
-  if (( TEST_STATUS != expected_outcome )); then
-      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
-      exit 1
+assert_command_succeeded() {
+  if (( TEST_STATUS != 0 )); then
+    echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+    exit 1
   fi
 }
 
-assert_command_succeeded() {
-  _assert_test_command_outcome 0
-}
-
+#######################################
+# Asserts that the outcome of the latest test command is not 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
 assert_command_failed() {
-  _assert_test_command_outcome 1
+  if (( TEST_STATUS == 0 )); then
+      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+      exit 1
+  fi
 }
 
 #######################################
diff --git a/cc/examples/aead/aead_test_keyset.json b/cc/examples/aead/aead_test_keyset.json
index ff0aa56..204a2b4 100644
--- a/cc/examples/aead/aead_test_keyset.json
+++ b/cc/examples/aead/aead_test_keyset.json
@@ -1,12 +1,15 @@
 {
-  "primaryKeyId":1931667682,
-  "key":[{
-    "keyData":{
-      "typeUrl":"type.googleapis.com/google.crypto.tink.AesGcmKey",
-      "value":"GhD+9l0RANZjzZEZ8PDp7LRW",
-      "keyMaterialType":"SYMMETRIC"},
-    "status":"ENABLED",
-    "keyId":1931667682,
-    "outputPrefixType":"TINK"
-  }]
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
+      },
+      "keyId": 294406504,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 294406504
 }
diff --git a/cc/examples/digital_signatures/BUILD.bazel b/cc/examples/digital_signatures/BUILD.bazel
index 3cc0bf2..a6adfaa 100644
--- a/cc/examples/digital_signatures/BUILD.bazel
+++ b/cc/examples/digital_signatures/BUILD.bazel
@@ -2,19 +2,11 @@
 
 licenses(["notice"])
 
-cc_library(
-    name = "util",
-    srcs = ["util.cc"],
-    hdrs = ["util.h"],
-    deps = [
-        "@tink_cc//:binary_keyset_reader",
-        "@tink_cc//:binary_keyset_writer",
-        "@tink_cc//:cleartext_keyset_handle",
-        "@tink_cc//:keyset_handle",
-        "@tink_cc//:keyset_reader",
-        "@tink_cc//:keyset_writer",
-        "@tink_cc//config:tink_config",
-        "@tink_cc//util:status",
+filegroup(
+    name = "digital_signature_keyset",
+    srcs = [
+        "digital_signature_private_keyset.json",
+        "digital_signature_public_keyset.json",
     ],
 )
 
@@ -22,10 +14,15 @@
     name = "digital_signatures_cli",
     srcs = ["digital_signatures_cli.cc"],
     deps = [
-        ":util",
+        "//util",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/log:check",
+        "@tink_cc//:keyset_handle",
         "@tink_cc//:public_key_sign",
         "@tink_cc//:public_key_verify",
-        "@tink_cc//signature:signature_key_templates",
+        "@tink_cc//signature:signature_config",
+        "@tink_cc//util:status",
     ],
 )
 
@@ -35,6 +32,10 @@
     srcs = ["digital_signatures_cli_test.sh"],
     args = [
         "$(rootpath :digital_signatures_cli)",
+        "$(rootpaths :digital_signature_keyset)",
     ],
-    data = [":digital_signatures_cli"],
+    data = [
+        ":digital_signature_keyset",
+        ":digital_signatures_cli",
+    ],
 )
diff --git a/cc/examples/digital_signatures/CMakeLists.txt b/cc/examples/digital_signatures/CMakeLists.txt
new file mode 100644
index 0000000..8a14e9e
--- /dev/null
+++ b/cc/examples/digital_signatures/CMakeLists.txt
@@ -0,0 +1,17 @@
+add_executable(digital_signatures_cli digital_signatures_cli.cc)
+target_include_directories(digital_signatures_cli PUBLIC
+  "${CMAKE_CURRENT_SOURCE_DIR}"
+  "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(digital_signatures_cli
+  tink::static
+  absl::check
+  absl::flags_parse
+  util)
+
+add_test(
+  NAME digital_signatures_cli_test
+  COMMAND "${BASH_PROGRAM}"
+    "${CMAKE_CURRENT_SOURCE_DIR}/digital_signatures_cli_test.sh"
+    "${CMAKE_CURRENT_BINARY_DIR}/digital_signatures_cli"
+    "${CMAKE_CURRENT_SOURCE_DIR}/digital_signature_private_keyset.json"
+    "${CMAKE_CURRENT_SOURCE_DIR}/digital_signature_public_keyset.json")
diff --git a/cc/examples/digital_signatures/README.md b/cc/examples/digital_signatures/README.md
deleted file mode 100644
index 4e9558a..0000000
--- a/cc/examples/digital_signatures/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# C++ Digital Signatures CLI
-
-This is a command-line tool that can generate
-[Digital Signature](../../../docs/PRIMITIVES.md#digital-signatures)
-keys, and create and verify digital signatures.
-
-It demonstrates the basic steps of using Tink, namely loading key material,
-obtaining a primitive, and using the primitive to do crypto.
-
-## Build and Run
-
-### Bazel
-
-```shell
-git clone https://github.com/google/tink
-cd tink/cc/examples
-bazel build ...
-echo "a message" > message.txt
-./bazel-bin/digital_signatures/digital_signatures_cli gen-private-key private_keyset.bin
-./bazel-bin/digital_signatures/digital_signatures_cli get-public-key private_keyset.bin \
-    public_keyset.bin
-./bazel-bin/digital_signatures/digital_signatures_cli sign private_keyset.bin \
-    message.txt signature.bin
-./bazel-bin/digital_signatures/digital_signatures_cli verify public_keyset.bin \
-    message.txt signature.bin result.txt
-cat result.txt
-```
diff --git a/cc/examples/digital_signatures/digital_signature_private_keyset.json b/cc/examples/digital_signatures/digital_signature_private_keyset.json
new file mode 100644
index 0000000..215dad2
--- /dev/null
+++ b/cc/examples/digital_signatures/digital_signature_private_keyset.json
@@ -0,0 +1,15 @@
+{
+  "primaryKeyId": 1487078030,
+  "key": [
+    {
+      "keyData": {
+        "typeUrl": "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey",
+        "value": "Ek0SBggDEAIYAhohANUKuRXZHBD8rPcB5M6+pmgVSjk3gLSD/htdVvbrfbnPIiAXepWekQPRS74qUTMEwN6nXeizXucBxDk0SoKoeqShOBogbJEwIZASdx42tIitAe8UoBxWyi11Mq+HnWNtcQWkG18=",
+        "keyMaterialType": "ASYMMETRIC_PRIVATE"
+      },
+      "status": "ENABLED",
+      "keyId": 1487078030,
+      "outputPrefixType": "TINK"
+    }
+  ]
+}
diff --git a/cc/examples/digital_signatures/digital_signature_public_keyset.json b/cc/examples/digital_signatures/digital_signature_public_keyset.json
new file mode 100644
index 0000000..01ebcfa
--- /dev/null
+++ b/cc/examples/digital_signatures/digital_signature_public_keyset.json
@@ -0,0 +1,15 @@
+{
+  "primaryKeyId": 1487078030,
+  "key": [
+    {
+      "keyData": {
+        "typeUrl": "type.googleapis.com/google.crypto.tink.EcdsaPublicKey",
+        "value": "EgYIAxACGAIaIQDVCrkV2RwQ/Kz3AeTOvqZoFUo5N4C0g/4bXVb26325zyIgF3qVnpED0Uu+KlEzBMDep13os17nAcQ5NEqCqHqkoTg=",
+        "keyMaterialType": "ASYMMETRIC_PUBLIC"
+      },
+      "status": "ENABLED",
+      "keyId": 1487078030,
+      "outputPrefixType": "TINK"
+    }
+  ]
+}
diff --git a/cc/examples/digital_signatures/digital_signatures_cli.cc b/cc/examples/digital_signatures/digital_signatures_cli.cc
index a6a2a79..caa37cc 100644
--- a/cc/examples/digital_signatures/digital_signatures_cli.cc
+++ b/cc/examples/digital_signatures/digital_signatures_cli.cc
@@ -13,246 +13,123 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
-
-// A command-line utility for generating Digital Signatures keys, and creating
-// and verifying digital signatures.
-//
-// The first argument is the operation and it should be one of the following:
-// gen-private-key get-public-key sign verify.
-// Additional arguments depend on the operation.
-//
-// gen-private-key
-//   Generates a new private keyset using the RsaSsaPkcs13072Sha256F4 template.
-//   It requires 1 additional argument:
-//     output-file: name of the file for the resulting output
-//
-// get-public-key
-//   Extracts a public keyset associated with the given private keyset.
-//   It requires 2 additional arguments:
-//     private-keyset-file: name of the file with the private keyset
-//     output-file: name of the file for the resulting output
-//
-// sign
-//   Signs the message using the given private keyset.
-//   It requires 3 additional arguments:
-//     private-keyset-file: name of the file with the private keyset
-//     message-file: name of the file with the message
-//     output-file: name of the file for the resulting output
-//
-// verify
-//   Verifies the signature of the message using the given public keyset.
-//   It requires 4 additional arguments:
-//     public-keyset-file: name of the file with the public keyset
-//     message-file: name of the file with the message
-//     signature-file: name of the file with the signature
-//     output-file: name of the file for the resulting output (valid/invalid)
-
+// [START digital-signature-example]
+// A utility for signing and verifying files using digital signatures.
 #include <iostream>
+#include <memory>
+#include <ostream>
 #include <string>
-#include <utility>
 
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/log/check.h"
+#include "util/util.h"
+#include "tink/keyset_handle.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
-#include "tink/signature/signature_key_templates.h"
-#include "digital_signatures/util.h"
+#include "tink/signature/signature_config.h"
+#include "tink/util/status.h"
 
-// Prints usage info.
-void PrintUsageInfo() {
-  std::clog << "Usage: operation arguments\n"
-            << "where operation is one of the following:\n"
-            << "  gen-private-key get-public-key sign verify\n"
-            << "and, depending on the operation, arguments are:\n"
-            << "  gen-private-key: output-file\n"
-            << "  get-public-key: private-keyset-file output-file\n"
-            << "  sign: private-keyset-file message-file output-file\n"
-            << "  verify: public-keyset-file message-file signature-file "
-            << "output-file" << std::endl;
+ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format");
+ABSL_FLAG(std::string, mode, "", "Mode of operation (sign|verify)");
+ABSL_FLAG(std::string, input_filename, "", "Filename to operate on");
+ABSL_FLAG(std::string, signature_filename, "", "Path to the signature file");
+
+namespace {
+
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::PublicKeySign;
+using ::crypto::tink::PublicKeyVerify;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+
+constexpr absl::string_view kSign = "sign";
+constexpr absl::string_view kVerify = "verify";
+
+void ValidateParams() {
+  // [START_EXCLUDE]
+  CHECK(absl::GetFlag(FLAGS_mode) == kSign ||
+        absl::GetFlag(FLAGS_mode) == kVerify)
+      << "Invalid mode; must be `" << kSign << "` or `" << kVerify << "`"
+      << std::endl;
+  CHECK(!absl::GetFlag(FLAGS_keyset_filename).empty())
+      << "Keyset file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_input_filename).empty())
+      << "Input file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_signature_filename).empty())
+      << "Signature file must be specified";
+  // [END_EXCLUDE]
 }
 
-// Generates a new private keyset using the RsaSsaPkcs13072Sha256F4 template
-// and writes it to the output file.
-void GeneratePrivateKey(const std::string& output_filename) {
-  std::clog << "Generating a new private keyset.." << std::endl;
+}  // namespace
 
-  auto key_template =
-      crypto::tink::SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4();
-  auto new_keyset_handle_result =
-      crypto::tink::KeysetHandle::GenerateNew(key_template);
-  if (!new_keyset_handle_result.ok()) {
-    std::clog << "Generating new keyset failed: "
-              << new_keyset_handle_result.status().message() << std::endl;
-    exit(1);
+namespace tink_cc_examples {
+
+// Digital signature example CLI implementation.
+Status DigitalSignatureCli(absl::string_view mode,
+                           const std::string& keyset_filename,
+                           const std::string& input_filename,
+                           const std::string& signature_filename) {
+  Status result = crypto::tink::SignatureConfig::Register();
+  if (!result.ok()) return result;
+
+  // Read the keyset from file.
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      ReadJsonCleartextKeyset(keyset_filename);
+  if (!keyset_handle.ok()) return keyset_handle.status();
+
+  // Read the input.
+  StatusOr<std::string> input_file_content = ReadFile(input_filename);
+  if (!input_file_content.ok()) return input_file_content.status();
+
+  if (mode == kSign) {
+    StatusOr<std::unique_ptr<PublicKeySign>> public_key_sign =
+        (*keyset_handle)->GetPrimitive<PublicKeySign>();
+    if (!public_key_sign.ok()) return public_key_sign.status();
+
+    StatusOr<std::string> signature =
+        (*public_key_sign)->Sign(*input_file_content);
+    if (!signature.ok()) return signature.status();
+
+    return WriteToFile(*signature, signature_filename);
+  } else {  // mode == kVerify
+    StatusOr<std::unique_ptr<PublicKeyVerify>> public_key_verify =
+        (*keyset_handle)->GetPrimitive<PublicKeyVerify>();
+    if (!public_key_verify.ok()) return public_key_verify.status();
+
+    // Read the signature.
+    StatusOr<std::string> signature_file_content = ReadFile(signature_filename);
+    if (!signature_file_content.ok()) return signature_file_content.status();
+
+    return (*public_key_verify)
+        ->Verify(*signature_file_content, *input_file_content);
   }
-  auto keyset_handle = std::move(new_keyset_handle_result.value());
-
-  std::clog << "Writing the keyset to file " << output_filename
-            << "..." << std::endl;
-
-  Util::WriteKeyset(keyset_handle, output_filename);
 }
 
-// Extracts a public keyset associated with the given private keyset
-// and writes it to the output file.
-void ExtractPublicKey(const std::string& private_keyset_filename,
-                      const std::string& output_filename) {
-  std::clog << "Extracting a public keyset associated with the private "
-            << "keyset from file " << private_keyset_filename << "..."
-            << std::endl;
-
-  auto private_keyset_handle = Util::ReadKeyset(private_keyset_filename);
-
-  auto new_keyset_handle_result =
-      private_keyset_handle->GetPublicKeysetHandle();
-  if (!new_keyset_handle_result.ok()) {
-    std::clog << "Getting the keyset failed: "
-              << new_keyset_handle_result.status().message() << std::endl;
-    exit(1);
-  }
-  auto public_keyset_handle = std::move(new_keyset_handle_result.value());
-
-  std::clog << "Writing the keyset to file " << output_filename
-            << "..." << std::endl;
-
-  Util::WriteKeyset(public_keyset_handle, output_filename);
-}
-
-// Signs the message using the given private keyset
-// and writes the signature to the output file.
-void Sign(const std::string& keyset_filename,
-          const std::string& message_filename,
-          const std::string& output_filename) {
-  auto keyset_handle = Util::ReadKeyset(keyset_filename);
-
-  auto primitive_result =
-      keyset_handle->GetPrimitive<crypto::tink::PublicKeySign>();
-  if (!primitive_result.ok()) {
-    std::clog << "Getting PublicKeySign-primitive from the factory failed: "
-              << primitive_result.status().message() << std::endl;
-    exit(1);
-  }
-  auto public_key_sign = std::move(primitive_result.value());
-
-  std::clog << "Signing message from file " << message_filename
-            << " using private keyset from file " << keyset_filename
-            << "..." << std::endl;
-
-  std::string message = Util::Read(message_filename);
-
-  auto sign_result = public_key_sign->Sign(message);
-  if (!sign_result.ok()) {
-    std::clog << "Error while signing the message: "
-              << sign_result.status().message() << std::endl;
-    exit(1);
-  }
-  std::string signature = sign_result.value();
-
-  std::clog << "Writing the resulting signature to file " << output_filename
-            << "..." << std::endl;
-
-  Util::Write(signature, output_filename);
-}
-
-// Verifies the signature of the message using the given public keyset
-// and writes the result to the output file.
-void Verify(const std::string& keyset_filename,
-            const std::string& message_filename,
-            const std::string& signature_filename,
-            const std::string& output_filename) {
-  auto keyset_handle = Util::ReadKeyset(keyset_filename);
-
-  auto primitive_result =
-      keyset_handle->GetPrimitive<crypto::tink::PublicKeyVerify>();
-  if (!primitive_result.ok()) {
-    std::clog << "Getting PublicKeyVerify-primitive from the factory "
-              << "failed: " << primitive_result.status().message() << std::endl;
-    exit(1);
-  }
-  auto public_key_verify = std::move(primitive_result.value());
-
-  std::clog << "Verifying signature from file " << signature_filename
-            << " of the message from file " << message_filename
-            << " using public keyset from file " << keyset_filename
-            << "..." << std::endl;
-
-  std::string signature = Util::Read(signature_filename);
-  std::string message = Util::Read(message_filename);
-
-  std::string result;
-  auto verify_status = public_key_verify->Verify(signature, message);
-  if (!verify_status.ok()) {
-    std::clog << "Error while verifying the signature: "
-              << verify_status.message() << std::endl;
-    result = "invalid";
-  } else {
-    result = "valid";
-  }
-
-  std::clog << "Writing the result to file " << output_filename
-            << "..." << std::endl;
-
-  Util::Write(result, output_filename);
-}
+}  // namespace tink_cc_examples
 
 int main(int argc, char** argv) {
-  if (argc == 1) {
-    PrintUsageInfo();
-    exit(1);
+  absl::ParseCommandLine(argc, argv);
+
+  ValidateParams();
+
+  std::string mode = absl::GetFlag(FLAGS_mode);
+  std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
+  std::string input_filename = absl::GetFlag(FLAGS_input_filename);
+  std::string signature_filename = absl::GetFlag(FLAGS_signature_filename);
+
+  std::clog << "Using keyset in " << keyset_filename << " to " << mode;
+  if (mode == kSign) {
+    std::clog << " file " << input_filename
+              << "; the resulting signature is written to "
+              << signature_filename << std::endl;
+  } else {  // mode == kVerify
+    std::clog << " the signature in " << signature_filename
+              << " over the content of " << input_filename << std::endl;
   }
 
-  Util::InitTink();
-
-  std::string operation = argv[1];
-
-  if (operation == "gen-private-key") {
-    if (argc != 3) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string output_filename = argv[2];
-
-    GeneratePrivateKey(output_filename);
-  } else if (operation == "get-public-key") {
-    if (argc != 4) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string private_keyset_filename = argv[2];
-    std::string output_filename = argv[3];
-
-    ExtractPublicKey(private_keyset_filename, output_filename);
-  } else if (operation == "sign") {
-    if (argc != 5) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string keyset_filename = argv[2];
-    std::string message_filename = argv[3];
-    std::string output_filename = argv[4];
-
-    Sign(keyset_filename, message_filename, output_filename);
-  } else if (operation == "verify") {
-    if (argc != 6) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string keyset_filename = argv[2];
-    std::string message_filename = argv[3];
-    std::string signature_filename = argv[4];
-    std::string output_filename = argv[5];
-
-    Verify(keyset_filename, message_filename, signature_filename,
-           output_filename);
-  } else {
-    std::clog << "Unknown operation. Supported operations are: "
-              << "gen-private-key get-public-key sign verify" << std::endl;
-    exit(1);
-  }
-
-  std::clog << "Done!" << std::endl;
-
+  CHECK_OK(tink_cc_examples::DigitalSignatureCli(
+      mode, keyset_filename, input_filename, signature_filename));
   return 0;
 }
+// [END digital-signature-example]
diff --git a/cc/examples/digital_signatures/digital_signatures_cli_test.sh b/cc/examples/digital_signatures/digital_signatures_cli_test.sh
index c83e92c..b84f3df 100755
--- a/cc/examples/digital_signatures/digital_signatures_cli_test.sh
+++ b/cc/examples/digital_signatures/digital_signatures_cli_test.sh
@@ -14,114 +14,166 @@
 # limitations under the License.
 ################################################################################
 
+set -euo pipefail
 
 #############################################################################
-#### Tests for digital_signatures_cli binary.
-
-SIGNATURE_CLI="$1"
-
-PRIVATE_KEYSET_FILE="$TEST_TMPDIR/private_keyset.bin"
-PUBLIC_KEYSET_FILE="$TEST_TMPDIR/public_keyset.bin"
-MESSAGE_FILE="$TEST_TMPDIR/message.txt"
-SIGNATURE_FILE="$TEST_TMPDIR/signature.bin"
-RESULT_FILE="$TEST_TMPDIR/result.txt"
-
-OTHER_PRIVATE_KEYSET_FILE="$TEST_TMPDIR/other_private_keyset.bin"
-OTHER_PUBLIC_KEYSET_FILE="$TEST_TMPDIR/other_public_keyset.bin"
-OTHER_MESSAGE_FILE="$TEST_TMPDIR/other_message.txt"
-
-echo "This is a message." > $MESSAGE_FILE
-echo "This is a different message." > $OTHER_MESSAGE_FILE
-
+# Tests for Tink C++ digital signature example.
 #############################################################################
-#### Helper function that checks if values are equal.
 
-assert_equal() {
-  if [ "$1" == "$2" ]; then
-    echo "+++ Success: values are equal."
-  else
-    echo "--- Failure: values are different. Expected: [$1], actual: [$2]."
+: "${TEST_TMPDIR:=$(mktemp -d)}"
+
+readonly CLI="$1"
+readonly PRIVATE_KEYSET_FILE="$2"
+readonly PUBLIC_KEYSET_FILE="$3"
+readonly MESSAGE_FILE="${TEST_TMPDIR}/message.txt"
+readonly SIGNATURE_FILE="${TEST_TMPDIR}/signature.txt"
+readonly TEST_NAME="TinkCcExamplesDigitalSignaturesTest"
+
+echo "This is some message to be signed." > "${MESSAGE_FILE}"
+
+#######################################
+# A helper function for getting the return code of a command that may fail.
+# Temporarily disables error safety and stores return value in TEST_STATUS.
+#
+# Globals:
+#   TEST_STATUS
+# Arguments:
+#   Command to execute.
+#######################################
+test_command() {
+  set +e
+  "$@"
+  TEST_STATUS=$?
+  set -e
+}
+
+#######################################
+# Asserts that the outcome of the latest test command is 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+assert_command_succeeded() {
+  if (( TEST_STATUS != 0 )); then
+    echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
     exit 1
   fi
 }
 
-#############################################################################
-#### All good, everything should work.
-test_name="all_good"
-echo "+++ Starting test $test_name..."
+#######################################
+# Asserts that the outcome of the latest test command is not 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+assert_command_failed() {
+  if (( TEST_STATUS == 0 )); then
+      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+      exit 1
+  fi
+}
 
-#### Generate a private key and get a public key.
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
+#######################################
+# Starts a new test case; records the test case name to TEST_CASE.
+#
+# Globals:
+#   TEST_NAME
+#   TEST_CASE
+# Arguments:
+#   test_case: The name of the test case.
+#######################################
+start_test_case() {
+  TEST_CASE="$1"
+  echo "[ RUN      ] ${TEST_NAME}.${TEST_CASE}"
+}
 
-#### Sign the message.
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE || exit 1
-
-#### Verify the signature.
-$SIGNATURE_CLI verify $PUBLIC_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE $RESULT_FILE || exit 1
-
-#### Check that the signature is valid.
-RESULT=$(<$RESULT_FILE)
-assert_equal "valid" "$RESULT"
+#######################################
+# Ends a test case printing a success message.
+#
+# Globals:
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+end_test_case() {
+  echo "[       OK ] ${TEST_NAME}.${TEST_CASE}"
+}
 
 #############################################################################
-#### Bad private key when getting the public key.
-test_name="get_public_key_with_bad_private_key"
-echo "+++ Starting test $test_name..."
 
-echo "abcd" >> $PRIVATE_KEYSET_FILE
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE
+start_test_case "sign_verify_all_good"
 
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
+# Sign.
+test_command "${CLI}" \
+  --mode sign \
+  --keyset_filename "${PRIVATE_KEYSET_FILE}" \
+  --input_filename "${MESSAGE_FILE}" \
+  --signature_filename "${SIGNATURE_FILE}"
+assert_command_succeeded
+
+# Verify.
+test_command "${CLI}" \
+  --mode verify \
+  --keyset_filename "${PUBLIC_KEYSET_FILE}" \
+  --input_filename "${MESSAGE_FILE}" \
+  --signature_filename "${SIGNATURE_FILE}"
+assert_command_succeeded
+
+end_test_case
 
 #############################################################################
-#### Different public key when verifying a signature.
-test_name="verify_with_different_public_key"
-echo "+++ Starting test $test_name..."
 
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI gen-private-key $OTHER_PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $OTHER_PRIVATE_KEYSET_FILE $OTHER_PUBLIC_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE || exit 1
-$SIGNATURE_CLI verify $OTHER_PUBLIC_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE $RESULT_FILE || exit 1
+start_test_case "verify_fails_with_modified_signature"
 
-RESULT=$(<$RESULT_FILE)
-assert_equal "invalid" "$RESULT"
+# Sign.
+test_command "${CLI}" \
+  --mode sign \
+  --keyset_filename "${PRIVATE_KEYSET_FILE}" \
+  --input_filename "${MESSAGE_FILE}" \
+  --signature_filename "${SIGNATURE_FILE}"
+assert_command_succeeded
+
+# Modify signature.
+echo "modified" >> "${SIGNATURE_FILE}"
+
+# Verify.
+test_command "${CLI}" \
+  --mode verify \
+  --keyset_filename "${PUBLIC_KEYSET_FILE}" \
+  --input_filename "${MESSAGE_FILE}" \
+  --signature_filename "${SIGNATURE_FILE}"
+assert_command_failed
+
+end_test_case
 
 #############################################################################
-#### Different message when verifying a signature.
-test_name="verify_with_different_message"
-echo "+++ Starting test $test_name..."
 
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE || exit 1
-$SIGNATURE_CLI verify $PUBLIC_KEYSET_FILE $OTHER_MESSAGE_FILE $SIGNATURE_FILE $RESULT_FILE || exit 1
+start_test_case "verify_fails_with_modified_message"
 
-RESULT=$(<$RESULT_FILE)
-assert_equal "invalid" "$RESULT"
+# Sign.
+test_command "${CLI}" \
+  --mode sign \
+  --keyset_filename "${PRIVATE_KEYSET_FILE}" \
+  --input_filename "${MESSAGE_FILE}" \
+  --signature_filename "${SIGNATURE_FILE}"
+assert_command_succeeded
 
-#############################################################################
-#### Sign with wrong key.
-test_name="sign_with_wrong_key"
-echo "+++ Starting test $test_name..."
+# Modify message.
+echo "modified" >> "${MESSAGE_FILE}"
 
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PUBLIC_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE
+# Verify.
+test_command "${CLI}" \
+  --mode verify \
+  --keyset_filename "${PUBLIC_KEYSET_FILE}" \
+  --input_filename "${MESSAGE_FILE}" \
+  --signature_filename "${SIGNATURE_FILE}"
+assert_command_failed
 
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
-
-#############################################################################
-#### Verify with wrong key.
-test_name="verify_with_wrong_key"
-echo "+++ Starting test $test_name..."
-
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE || exit 1
-$SIGNATURE_CLI verify $PRIVATE_KEYSET_FILE $MESSAGE_FILE $SIGNATURE_FILE $RESULT_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
+end_test_case
diff --git a/cc/examples/digital_signatures/util.cc b/cc/examples/digital_signatures/util.cc
deleted file mode 100644
index 4ba9a10..0000000
--- a/cc/examples/digital_signatures/util.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// 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
-//
-//     http://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 "digital_signatures/util.h"
-
-#include <fstream>
-#include <iostream>
-#include <string>
-#include <utility>
-
-#include "tink/binary_keyset_reader.h"
-#include "tink/binary_keyset_writer.h"
-#include "tink/cleartext_keyset_handle.h"
-#include "tink/config.h"
-#include "tink/config/tink_config.h"
-#include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
-#include "tink/keyset_writer.h"
-#include "tink/util/status.h"
-
-using crypto::tink::BinaryKeysetReader;
-using crypto::tink::BinaryKeysetWriter;
-using crypto::tink::CleartextKeysetHandle;
-using crypto::tink::KeysetHandle;
-using crypto::tink::KeysetReader;
-using crypto::tink::KeysetWriter;
-using crypto::tink::TinkConfig;
-
-// static
-std::unique_ptr<KeysetReader> Util::GetBinaryKeysetReader(
-    const std::string& filename) {
-  std::unique_ptr<std::ifstream> keyset_stream(new std::ifstream());
-  keyset_stream->open(filename, std::ifstream::in);
-  auto keyset_reader_result = BinaryKeysetReader::New(std::move(keyset_stream));
-  if (!keyset_reader_result.ok()) {
-    std::clog << "Creation of the BinaryKeysetReader failed: "
-              << keyset_reader_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_reader_result.value());
-}
-
-// static
-std::unique_ptr<KeysetWriter> Util::GetBinaryKeysetWriter(
-    const std::string& filename) {
-  std::unique_ptr<std::ofstream> keyset_stream(new std::ofstream());
-  keyset_stream->open(filename, std::ofstream::out);
-  auto keyset_writer_result = BinaryKeysetWriter::New(std::move(keyset_stream));
-  if (!keyset_writer_result.ok()) {
-    std::clog << "Creation of the BinaryKeysetWriter failed: "
-              << keyset_writer_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_writer_result.value());
-}
-
-// static
-std::unique_ptr<KeysetHandle> Util::ReadKeyset(const std::string& filename) {
-  auto keyset_reader = GetBinaryKeysetReader(filename);
-  auto keyset_handle_result =
-      CleartextKeysetHandle::Read(std::move(keyset_reader));
-  if (!keyset_handle_result.ok()) {
-    std::clog << "Reading the keyset failed: "
-              << keyset_handle_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_handle_result.value());
-}
-
-// static
-void Util::WriteKeyset(const std::unique_ptr<KeysetHandle>& keyset_handle,
-                       const std::string& filename) {
-  auto keyset_writer = GetBinaryKeysetWriter(filename);
-  auto status = CleartextKeysetHandle::Write(keyset_writer.get(),
-                                             *keyset_handle);
-  if (!status.ok()) {
-    std::clog << "Writing the keyset failed: " << status.message() << std::endl;
-    exit(1);
-  }
-}
-
-// static
-void Util::InitTink() {
-  auto status = TinkConfig::Register();
-  if (!status.ok()) {
-    std::clog << "Initialization of Tink failed: " << status.message()
-              << std::endl;
-    exit(1);
-  }
-}
-
-// static
-std::string Util::Read(const std::string& filename) {
-  std::ifstream input_stream;
-  input_stream.open(filename, std::ifstream::in);
-  if (!input_stream.is_open()) {
-    std::clog << "Error opening input file " << filename << std::endl;
-    exit(1);
-  }
-  std::stringstream input;
-  input << input_stream.rdbuf();
-  input_stream.close();
-  return input.str();
-}
-
-// static
-void Util::Write(const std::string& output, const std::string& filename) {
-  std::ofstream output_stream(filename,
-                              std::ofstream::out | std::ofstream::binary);
-  if (!output_stream.is_open()) {
-    std::clog << "Error opening output file " << filename << std::endl;
-    exit(1);
-  }
-  output_stream << output;
-  output_stream.close();
-}
diff --git a/cc/examples/digital_signatures/util.h b/cc/examples/digital_signatures/util.h
deleted file mode 100644
index de3bf60..0000000
--- a/cc/examples/digital_signatures/util.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_EXAMPLES_DIGITAL_SIGNATURES_UTIL_H_
-#define TINK_EXAMPLES_DIGITAL_SIGNATURES_UTIL_H_
-
-#include <fstream>
-#include <iostream>
-#include <string>
-
-#include "tink/keyset_handle.h"
-
-// Helper functions for Digital Signatures CLI
-class Util {
- public:
-  // Returns a BinaryKeysetReader that reads from the specified file.
-  // In case of errors writes a log message and aborts.
-  static std::unique_ptr<crypto::tink::KeysetReader> GetBinaryKeysetReader(
-      const std::string& filename);
-
-  // Returns a BinaryKeysetWriter that writes from the specified file.
-  // In case of errors writes a log message and aborts.
-  static std::unique_ptr<crypto::tink::KeysetWriter> GetBinaryKeysetWriter(
-      const std::string& filename);
-
-  // Reads a keyset from the specified file.
-  // In case of errors writes a log message and aborts.
-  static std::unique_ptr<crypto::tink::KeysetHandle> ReadKeyset(
-      const std::string& filename);
-
-  // Writes the keyset to the specified file.
-  // In case of errors writes a log message and aborts.
-  static void WriteKeyset(
-      const std::unique_ptr<crypto::tink::KeysetHandle>& keyset_handle,
-      const std::string& filename);
-
-  // Initializes Tink registry.
-  // In case of errors writes a log message and aborts.
-  static void InitTink();
-
-  // Reads the specified file and returns the contents as a string.
-  // In case of errors writes a log message and aborts.
-  static std::string Read(const std::string& filename);
-
-  // Writes the given 'output' to the specified file.
-  // In case of errors writes a log message and aborts.
-  static void Write(const std::string& output, const std::string& filename);
-};
-
-#endif  // TINK_EXAMPLES_DIGITAL_SIGNATURES_UTIL_H_
diff --git a/cc/examples/helloworld/BUILD.bazel b/cc/examples/helloworld/BUILD.bazel
deleted file mode 100644
index f6b6efb..0000000
--- a/cc/examples/helloworld/BUILD.bazel
+++ /dev/null
@@ -1,32 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-exports_files(["aes128_gcm_test_keyset_json.txt"])
-
-cc_binary(
-    name = "hello_world",
-    srcs = ["hello_world.cc"],
-    deps = [
-        "@tink_cc",
-        "@tink_cc//:cleartext_keyset_handle",
-        "@tink_cc//config:tink_config",
-        "@tink_cc//util:status",
-    ],
-)
-
-sh_test(
-    name = "hello_world_test",
-    size = "small",
-    srcs = [
-        "hello_world_test.sh",
-    ],
-    args = [
-        "$(rootpath :hello_world)",
-        "$(rootpath :aes128_gcm_test_keyset_json.txt)",
-    ],
-    data = [
-        ":aes128_gcm_test_keyset_json.txt",
-        ":hello_world",
-    ],
-)
diff --git a/cc/examples/helloworld/CMakeLists.txt b/cc/examples/helloworld/CMakeLists.txt
deleted file mode 100644
index 32932a5..0000000
--- a/cc/examples/helloworld/CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-# 3.5 is the minimum version supported by Tink and its dependencies.
-cmake_minimum_required(VERSION 3.5)
-project(TinkHelloWorld CXX)
-
-set(CMAKE_BUILD_TYPE Release)
-
-# Import Tink as an in-tree dependency.
-add_subdirectory(../../.. tink)
-
-add_executable(hello_world hello_world.cc)
-target_link_libraries(hello_world tink::static)
diff --git a/cc/examples/helloworld/CMakeLists_for_CMakeBuildTest.txt b/cc/examples/helloworld/CMakeLists_for_CMakeBuildTest.txt
deleted file mode 100644
index d8a0778..0000000
--- a/cc/examples/helloworld/CMakeLists_for_CMakeBuildTest.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-cmake_minimum_required(VERSION 3.5)
-project(HelloWorld CXX)
-set(CMAKE_CXX_STANDARD 11)
-
-add_subdirectory(third_party/tink)
-
-add_executable(hello_world hello_world.cc)
-target_link_libraries(hello_world tink::static)
diff --git a/cc/examples/helloworld/README.md b/cc/examples/helloworld/README.md
deleted file mode 100644
index 92952a5..0000000
--- a/cc/examples/helloworld/README.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# C++ Hello World
-
-This is a command-line tool that can encrypt and decrypt small files using
-[AEAD (Authenticated Encryption with Associated Data)](https://developers.google.com/tink/aead).
-
-It demonstrates the basic steps of using Tink, namely loading key material,
-obtaining a primitive, and using the primitive to do crypto.
-
-## Build and Run
-
-### Bazel
-
-```shell
-# Build the code.
-git clone https://github.com/google/tink
-cd tink/cc/examples
-bazel build ...
-
-# Create some input.
-echo "some plaintext" > foo.txt
-
-# Encrypt.
-./bazel-bin/helloworld/hello_world \
-  ./helloworld/aes128_gcm_test_keyset_json.txt \
-  encrypt \
-  foo.txt \
-  "some aad" \
-  foo.encrypted
-
-# Decrypt.
-./bazel-bin/helloworld/hello_world \
-  ./helloworld/aes128_gcm_test_keyset_json.txt \
-  decrypt \
-  foo.encrypted \
-  "some aad" \
-  foo-decrypted.tx
-
-# Inspect the output.
-cat foo-decrypted.txt
-```
-
-### CMake
-
-```shell
-
-# Clone the Tink repository.
-git clone https://github.com/google/tink
-cd tink/cc/examples/helloworld
-
-# Build the hello world example.
-(
-  mkdir build && cd build
-  # Note: Specify -DTINK_USE_SYSTEM_OPENSSL=ON if you want to build Tink against
-  # OpenSSL rather than BoringSSL. CMake will search for a suitable OpenSSL
-  # library in the system's library path.
-  cmake .. -DCMAKE_CXX_STANDARD=11
-  make -j"$(nproc)"
-)
-
-# Create some input.
-echo "some plaintext" > foo.txt
-
-./build/hello_world \
-  aes128_gcm_test_keyset_json.txt \
-  encrypt \
-  foo.txt \
-  "some aad" \
-  foo.txt.encrypted
-
-./build/hello_world \
-  aes128_gcm_test_keyset_json.txt \
-  decrypt \
-  foo.txt.encrypted \
-  "some aad" \
-  foo.txt.decrypted
-
-if [[ "$(diff foo.txt foo.txt.decrypted)" == "" ]]; then
-  echo "File correctly encrypted and decrypted"
-else
-  echo "Failed to encrypt-and-decrypt. Diff:"
-  diff foo.txt foo.txt.decrypted
-fi
-```
-
-The `cmake_build_test.sh` script will run an encrypt-then-decrypt test with
-AEAD. You can build Tink against OpenSSL by passing the `--openssl` option --
-the script will download, build and install OpenSSL in a temporary directory.
diff --git a/cc/examples/helloworld/aes128_gcm_test_keyset_json.txt b/cc/examples/helloworld/aes128_gcm_test_keyset_json.txt
deleted file mode 100644
index 456c46a..0000000
--- a/cc/examples/helloworld/aes128_gcm_test_keyset_json.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "primaryKeyId": 515279322,
-    "key": [{
-        "keyData": {
-            "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
-            "keyMaterialType": "SYMMETRIC",
-            "value": "GhCS/1+ejWpx68NfGt6ziYHd"
-        },
-        "outputPrefixType": "TINK",
-        "keyId": 515279322,
-        "status": "ENABLED"
-    }]
-}
diff --git a/cc/examples/helloworld/cmake_build_test.sh b/cc/examples/helloworld/cmake_build_test.sh
deleted file mode 100755
index 2bbbcab..0000000
--- a/cc/examples/helloworld/cmake_build_test.sh
+++ /dev/null
@@ -1,210 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# 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
-#
-#      http://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.
-################################################################################
-
-#!/bin/bash
-
-set -e
-
-readonly TINK_USE_CXX_STANDARD=11
-
-# Test for using Tink in a CMake project.
-
-if [[ -z "${TEST_TMPDIR}" ]]; then
-  echo "Error: TEST_TMPDIR must be set to a temporary working directory."
-  exit 1
-fi
-
-if [[ -z "${TEST_SRCDIR}" ]]; then
-  echo "Error: TEST_SRCDIR must be set to Tink's parent directory."
-  exit 1
-fi
-
-# XDG_CACHE_HOME must be set for a successful build of BoringSSL.
-export XDG_CACHE_HOME="${TEST_TMPDIR}/cache"
-TEST_DATA_DIR="${TEST_SRCDIR}/tink/cc/examples/helloworld"
-CMAKE_LISTS_FILE="${TEST_DATA_DIR}/CMakeLists_for_CMakeBuildTest.txt"
-HELLO_WORLD_SRC="${TEST_DATA_DIR}/hello_world.cc"
-KEYSET_FILE="${TEST_DATA_DIR}/aes128_gcm_test_keyset_json.txt"
-
-PROJECT_DIR="${TEST_TMPDIR}/my_project"
-PLAINTEXT_FILE="${TEST_TMPDIR}/example_plaintext.txt"
-CIPHERTEXT_FILE="${TEST_TMPDIR}/ciphertext.bin"
-DECRYPTED_FILE="${TEST_TMPDIR}/decrypted.txt"
-AAD_TEXT="some associated data"
-
-# If "true" build and install OpenSSL and build Tink against it.
-USE_OPENSSL="false"
-# If "true" build and install Abseil and build Tink against it.
-USE_INSTALLED_ABSEIL="false"
-
-# Parse parameters.
-while [[ "$#" -gt 0 ]]; do
-  case "$1" in
-    --openssl)
-      USE_OPENSSL="true"
-      shift
-      ;;
-    # Use prebuilt static libraries of Abseil.
-    --use_installed_abseil)
-      USE_INSTALLED_ABSEIL="true"
-      shift
-      ;;
-    *)
-      echo "Unknown parameter - $1"
-      exit 1
-  esac
-done
-
-#######################################
-# Install a given version of OpenSSL in a temporary directory
-#
-# Adds the directory to PATH and sets OPENSSL_ROOT_DIR accordingly.
-#
-# Globals:
-#   OPENSSL_ROOT_DIR Gets populated with OpenSSL temporary directory.
-#   PATH Gets updated with bin's path.
-# Arguments:
-#   openssl_version Version of OpenSSL to install, e.g., 1.1.1l.
-#######################################
-install_openssl() {
-  local openssl_version="$1"
-
-  local openssl_name="openssl-${openssl_version}"
-  local openssl_archive="${openssl_name}.tar.gz"
-  local openssl_url="https://www.openssl.org/source/${openssl_archive}"
-  local openssl_sha256="$(curl -sS https://www.openssl.org/source/${openssl_archive}.sha256)"
-
-  local -r openssl_tmpdir="$(mktemp -dt tink-openssl.XXXXXX)"
-  (
-    cd "${openssl_tmpdir}"
-    curl -OLsS "${openssl_url}"
-    echo "${openssl_sha256} ${openssl_archive}" | sha256sum -c
-
-    tar xzf "${openssl_archive}"
-    cd "${openssl_name}"
-    ./config --prefix="${openssl_tmpdir}" --openssldir="${openssl_tmpdir}"
-    make -j"$(nproc)"
-    make install
-  )
-  export OPENSSL_ROOT_DIR="${openssl_tmpdir}"
-  export PATH="${openssl_tmpdir}/bin:${PATH}"
-}
-
-#######################################
-# Install Abeseil into a temporary folder.
-#
-# Globals:
-#   ABSEIL_INSTALL_PATH Gets populated with Abseil's installation path.
-# Arguments:
-#   abseil_commit Abseils commit to lookup.
-#######################################
-install_abseil() {
-  local -r abseil_commit="$1"
-  local -r abseil_tmpdir="$(mktemp -dt tink-abseil.XXXXXX)"
-  local -r abseil_install_dir="${abseil_tmpdir}/install"
-  (
-    cd "${abseil_tmpdir}"
-    mkdir "install"
-    curl -OLsS "https://github.com/abseil/abseil-cpp/archive/${abseil_commit}.zip"
-    unzip "${abseil_commit}.zip" && cd "abseil-cpp-${abseil_commit}"
-    mkdir build && cd build
-    cmake .. \
-      -DCMAKE_INSTALL_PREFIX="${abseil_install_dir}" \
-      -DCMAKE_CXX_STANDARD="${TINK_USE_CXX_STANDARD}"
-    cmake --build . --target install
-  )
-  export ABSEIL_INSTALL_PATH="${abseil_install_dir}"
-}
-
-
-#######################################
-# Builds the hello world project
-#
-# Globals:
-#   USE_OPENSSL if "true" install OpenSSL and build against it.
-#   USE_INSTALLED_ABSEIL if "true" install Abseil and build against it.
-# Arguments:
-#   None
-#######################################
-build_hello_world() {
-  local cmake_parameters=(
-    -DCMAKE_CXX_STANDARD="${TINK_USE_CXX_STANDARD}"
-  )
-  if [[ "${USE_OPENSSL}" == "true" ]]; then
-    # Install OpenSSL in a temporary directory.
-    install_openssl "1.1.1l"
-    cmake_parameters+=( -DTINK_USE_SYSTEM_OPENSSL=ON )
-  fi
-  if [[ "${USE_INSTALLED_ABSEIL}" == "true" ]]; then
-    # Commit from 2021-12-03
-    install_abseil "9336be04a242237cd41a525bedfcf3be1bb55377"
-    cmake_parameters+=( -DCMAKE_PREFIX_PATH="${ABSEIL_INSTALL_PATH}" )
-    cmake_parameters+=( -DTINK_USE_INSTALLED_ABSEIL=ON )
-  fi
-  readonly cmake_parameters
-  (
-    mkdir build && cd build
-    cmake --version
-    cmake .. "${cmake_parameters[@]}"
-    make -j"$(nproc)"
-  )
-}
-
-main() {
-  # Create necessary directories, and create a symlink to Tink in the
-  # "my_project" directory.
-  mkdir -p "${XDG_CACHE_HOME}"
-  mkdir -p "${PROJECT_DIR}" "${PROJECT_DIR}/third_party"
-  ln -s "${TEST_SRCDIR}/tink" "${PROJECT_DIR}/third_party/tink"
-
-  # Copy "my_project" files.
-  cp "${HELLO_WORLD_SRC}" "${KEYSET_FILE}" "${PROJECT_DIR}"
-  cp "${CMAKE_LISTS_FILE}" "${PROJECT_DIR}/CMakeLists.txt"
-
-  # Move into the newly populated project directory.
-  cd "${PROJECT_DIR}"
-
-  # Build the project. This will produce ./build/hello_world.
-  build_hello_world
-
-  # Create a plaintext.
-  echo "This is some message to be encrypted." > "${PLAINTEXT_FILE}"
-
-  # Run encryption & decryption.
-  ./build/hello_world \
-    "${KEYSET_FILE}" \
-    encrypt \
-    "${PLAINTEXT_FILE}" \
-    "${AAD_TEXT}" \
-    "${CIPHERTEXT_FILE}"
-
-  ./build/hello_world \
-    "${KEYSET_FILE}" \
-    decrypt \
-    "${CIPHERTEXT_FILE}" \
-    "${AAD_TEXT}" \
-    "${DECRYPTED_FILE}"
-
-  # Check that decryption is correct.
-  diff -q "${DECRYPTED_FILE}" "${PLAINTEXT_FILE}"
-  if [ $? -ne 0 ]; then
-    echo "--- Failure: the decrypted file differs from the original plaintext."
-    diff "${DECRYPTED_FILE}" "${PLAINTEXT_FILE}"
-    exit 1
-  fi
-  echo "+++ Success: decryption was correct."
-}
-
-main
diff --git a/cc/examples/helloworld/hello_world.cc b/cc/examples/helloworld/hello_world.cc
deleted file mode 100644
index 6653b2c..0000000
--- a/cc/examples/helloworld/hello_world.cc
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-// A command-line utility for testing Tink-primitives.
-// It requires 5 arguments:
-//   keyset-file:  name of the file with the keyset to be used for encryption
-//   operation: the actual AEAD-operation, i.e. "encrypt" or "decrypt"
-//   input-file:  name of the file with input (plaintext for encryption, or
-//                or ciphertext for decryption)
-//   associated-data:  a string to be used as assciated data
-//   output-file:  name of the file for the resulting output
-
-#include <fstream>
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <utility>
-
-#include "tink/aead.h"
-#include "tink/cleartext_keyset_handle.h"
-#include "tink/config.h"
-#include "tink/config/tink_config.h"
-#include "tink/json_keyset_reader.h"
-#include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
-
-namespace {
-
-// Helper functions.
-// Upon failure each function writes an error message, and terminates.
-
-// Initializes Tink.
-void InitTink() {
-  std::clog << "Initializing Tink...\n";
-  auto status = crypto::tink::TinkConfig::Register();
-  if (!status.ok()) {
-    std::clog << "Initialization of Tink failed: " << status.message()
-              << std::endl;
-    exit(1);
-  }
-}
-
-// Creates a KeysetReader that reads a JSON-formatted keyset
-// from the given file.
-std::unique_ptr<crypto::tink::KeysetReader> GetJsonKeysetReader(
-    const std::string& filename) {
-  std::clog << "Creating a JsonKeysetReader...\n";
-  std::unique_ptr<std::ifstream> keyset_stream(new std::ifstream());
-  keyset_stream->open(filename, std::ifstream::in);
-  auto keyset_reader_result =
-      crypto::tink::JsonKeysetReader::New(std::move(keyset_stream));
-  if (!keyset_reader_result.ok()) {
-    std::clog << "Creation of the reader failed: "
-              << keyset_reader_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_reader_result.value());
-}
-
-// Creates a KeysetHandle that for a keyset read from the given file,
-// which is expected to contain a JSON-formatted keyset.
-std::unique_ptr<crypto::tink::KeysetHandle> ReadKeyset(
-    const std::string& filename) {
-  auto keyset_reader = GetJsonKeysetReader(filename);
-  auto keyset_handle_result =
-      crypto::tink::CleartextKeysetHandle::Read(std::move(keyset_reader));
-  if (!keyset_handle_result.ok()) {
-    std::clog << "Reading the keyset failed: "
-              << keyset_handle_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_handle_result.value());
-}
-
-// Reads the specified file and returns the read content as a string.
-std::string Read(const std::string& filename) {
-  std::clog << "Reading the input...\n";
-  std::ifstream input_stream;
-  input_stream.open(filename, std::ifstream::in);
-  if (!input_stream.is_open()) {
-    std::clog << "Error opening input file " << filename << std::endl;
-    exit(1);
-  }
-  std::stringstream input;
-  input << input_stream.rdbuf();
-  input_stream.close();
-  return input.str();
-}
-
-// Writes the given string to the specified file.
-void Write(const std::string& output, const std::string& filename) {
-  std::clog << "Writing the output...\n";
-  std::ofstream output_stream(filename,
-                              std::ofstream::out | std::ofstream::binary);
-  if (!output_stream.is_open()) {
-    std::clog << "Error opening output file " << filename << std::endl;
-    exit(1);
-  }
-  output_stream << output;
-  output_stream.close();
-}
-
-}  // namespace
-
-int main(int argc, char** argv) {
-  if (argc != 6) {
-    std::clog << "Usage: " << argv[0]
-         << "  keyset-file operation input-file associated-data output-file\n";
-    exit(1);
-  }
-
-  std::string keyset_filename(argv[1]);
-  std::string operation(argv[2]);
-  std::string input_filename(argv[3]);
-  std::string associated_data(argv[4]);
-  std::string output_filename(argv[5]);
-  if (!(operation == "encrypt" || operation == "decrypt")) {
-    std::clog << "Unknown operation '" << operation << "'.\n"
-              << "Expected 'encrypt' or 'decrypt'.\n";
-    exit(1);
-  }
-  std::clog << "Using keyset from file " << keyset_filename
-            << " to AEAD-" << operation
-            << " file "<< input_filename
-            << " with associated data '" << associated_data << "'.\n"
-            << "The resulting output will be written to file "
-            << output_filename << std::endl;
-
-  // Init Tink;
-  InitTink();
-
-  // Read the keyset.
-  auto keyset_handle = ReadKeyset(keyset_filename);
-
-  // Get the primitive.
-  auto primitive_result = keyset_handle->GetPrimitive<crypto::tink::Aead>();
-  if (!primitive_result.ok()) {
-    std::clog << "Getting AEAD-primitive from the factory failed: "
-              << primitive_result.status().message() << std::endl;
-    exit(1);
-  }
-  std::unique_ptr<crypto::tink::Aead> aead =
-      std::move(primitive_result.value());
-
-  // Read the input.
-  std::string input = Read(input_filename);
-
-  // Compute the output.
-  std::clog << operation << "ing...\n";
-  std::string output;
-  if (operation == "encrypt") {
-    auto encrypt_result = aead->Encrypt(input, associated_data);
-    if (!encrypt_result.ok()) {
-      std::clog << "Error while encrypting the input:"
-                << encrypt_result.status().message() << std::endl;
-      exit(1);
-    }
-    output = encrypt_result.value();
-  } else {  // operation == "decrypt"
-    auto decrypt_result = aead->Decrypt(input, associated_data);
-    if (!decrypt_result.ok()) {
-      std::clog << "Error while decrypting the input:"
-                << decrypt_result.status().message() << std::endl;
-      exit(1);
-    }
-    output = decrypt_result.value();
-  }
-
-  // Write the output to the output file.
-  Write(output, output_filename);
-
-  std::clog << "All done.\n";
-  return 0;
-}
diff --git a/cc/examples/helloworld/hello_world_test.sh b/cc/examples/helloworld/hello_world_test.sh
deleted file mode 100755
index aaf5dfb..0000000
--- a/cc/examples/helloworld/hello_world_test.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2020 Google LLC
-#
-# 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
-#
-#      http://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.
-################################################################################
-
-#!/bin/bash
-
-#############################################################################
-##### Tests for hello_world binary.
-
-HELLO_WORLD_CLI="$1"
-KEYSET_FILE="$2"
-PLAINTEXT_FILE="$TEST_TMPDIR/example_plaintext.txt"
-CIPHERTEXT_FILE="$TEST_TMPDIR/ciphertext.bin"
-DECRYPTED_FILE="$TEST_TMPDIR/decrypted.txt"
-AAD_TEXT="some associated data"
-
-#############################################################################
-
-##### Create a plaintext.
-echo "This is some message to be encrypted." > $PLAINTEXT_FILE
-
-##### Run encryption & decryption.
-$HELLO_WORLD_CLI $KEYSET_FILE encrypt $PLAINTEXT_FILE "$AAD_TEXT" $CIPHERTEXT_FILE
-$HELLO_WORLD_CLI $KEYSET_FILE decrypt $CIPHERTEXT_FILE "$AAD_TEXT" $DECRYPTED_FILE
-
-##### Check that decryption is correct.
-diff -q $DECRYPTED_FILE $PLAINTEXT_FILE
-if [ $? -ne 0 ]; then
-  echo "--- Failure: the decrypted file differs from the original plaintext."
-  diff $DECRYPTED_FILE $PLAINTEXT_FILE
-  exit 1
-fi
-echo "+++ Success: decryption was correct."
diff --git a/cc/examples/hybrid_encryption/BUILD.bazel b/cc/examples/hybrid_encryption/BUILD.bazel
index 39c43b7..dc2f7c7 100644
--- a/cc/examples/hybrid_encryption/BUILD.bazel
+++ b/cc/examples/hybrid_encryption/BUILD.bazel
@@ -2,69 +2,21 @@
 
 licenses(["notice"])
 
-cc_library(
-    name = "util",
-    srcs = ["util.cc"],
-    hdrs = ["util.h"],
-    deps = [
-        "@tink_cc//:binary_keyset_reader",
-        "@tink_cc//:binary_keyset_writer",
-        "@tink_cc//:cleartext_keyset_handle",
-        "@tink_cc//:keyset_handle",
-        "@tink_cc//:keyset_reader",
-        "@tink_cc//:keyset_writer",
-        "@tink_cc//config:tink_config",
-        "@tink_cc//util:status",
-    ],
-)
-
-cc_binary(
-    name = "hybrid_encryption_cli",
-    srcs = ["hybrid_encryption_cli.cc"],
-    deps = [
-        ":util",
-        "@tink_cc//:hybrid_decrypt",
-        "@tink_cc//:hybrid_encrypt",
-        "@tink_cc//hybrid:hybrid_key_templates",
-    ],
-)
-
-sh_test(
-    name = "hybrid_encryption_cli_test",
-    size = "small",
-    srcs = ["hybrid_encryption_cli_test.sh"],
-    args = [
-        "$(rootpath :hybrid_encryption_cli)",
-    ],
-    data = [":hybrid_encryption_cli"],
-)
-
-filegroup(
-    name = "hybrid_test_keyset",
-    srcs = [
-        "hybrid_test_private_keyset.json",
-        "hybrid_test_public_keyset.json",
-    ],
-)
-
 cc_binary(
     name = "hybrid_cli",
     srcs = ["hybrid_cli.cc"],
-    data = [":hybrid_test_keyset"],
     deps = [
+        "//util",
         "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/flags:parse",
-        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/log:check",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
-        "@tink_cc//:cleartext_keyset_handle",
         "@tink_cc//:hybrid_decrypt",
         "@tink_cc//:hybrid_encrypt",
-        "@tink_cc//:json_keyset_reader",
         "@tink_cc//:keyset_handle",
-        "@tink_cc//:keyset_reader",
-        "@tink_cc//config:tink_config",
         "@tink_cc//hybrid:hpke_config",
+        "@tink_cc//hybrid:hybrid_config",
         "@tink_cc//util:status",
     ],
 )
@@ -75,10 +27,10 @@
     srcs = ["hybrid_cli_test.sh"],
     args = [
         "$(rootpath :hybrid_cli)",
-        "$(rootpaths :hybrid_test_keyset)",
+        "$(rootpaths //hybrid_encryption/testdata:hpke_test_keyset)",
     ],
     data = [
         ":hybrid_cli",
-        ":hybrid_test_keyset",
+        "//hybrid_encryption/testdata:hpke_test_keyset",
     ],
 )
diff --git a/cc/examples/hybrid_encryption/CMakeLists.txt b/cc/examples/hybrid_encryption/CMakeLists.txt
new file mode 100644
index 0000000..99b6157
--- /dev/null
+++ b/cc/examples/hybrid_encryption/CMakeLists.txt
@@ -0,0 +1,20 @@
+add_executable(hybrid_cli hybrid_cli.cc)
+target_include_directories(hybrid_cli PUBLIC
+  "${CMAKE_CURRENT_SOURCE_DIR}"
+  "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(hybrid_cli
+  tink::static
+  absl::check
+  absl::flags_parse
+  util)
+# Tink CMake's configuration doesn't expose tink::core::hpke_config. Remove
+# HPKE from this example when building with CMake.
+target_compile_definitions(hybrid_cli PRIVATE TINK_EXAMPLES_EXCLUDE_HPKE)
+
+add_test(
+  NAME hybrid_cli_test
+  COMMAND "${BASH_PROGRAM}"
+    "${CMAKE_CURRENT_SOURCE_DIR}/hybrid_cli_test.sh"
+    "${CMAKE_CURRENT_BINARY_DIR}/hybrid_cli"
+    "${CMAKE_CURRENT_SOURCE_DIR}/testdata/hybrid_test_private_keyset.json"
+    "${CMAKE_CURRENT_SOURCE_DIR}/testdata/hybrid_test_public_keyset.json")
diff --git a/cc/examples/hybrid_encryption/README.md b/cc/examples/hybrid_encryption/README.md
deleted file mode 100644
index b72cb9d..0000000
--- a/cc/examples/hybrid_encryption/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# C++ Hybrid Encryption CLI
-
-This is a command-line tool that can generate
-[Hybrid Encryption](../../../docs/PRIMITIVES.md#hybrid_encryption) keys, and
-encrypt and decrypt using Hybrid encryption.
-
-It demonstrates the basic steps of using Tink, namely loading key material,
-obtaining a primitive, and using the primitive to do crypto.
-
-## Build and Run
-
-### Bazel
-
-```shell
-git clone https://github.com/google/tink
-cd tink/cc/examples
-bazel build ...
-echo "a message" > message.txt
-echo "context" > context_info.txt
-./bazel-bin/hybrid_encryption/hybrid_encryption_cli gen-private-key private_keyset.bin
-./bazel-bin/hybrid_encryption/hybrid_encryption_cli get-public-key private_keyset.bin \
-    public_keyset.bin
-./bazel-bin/hybrid_encryption/hybrid_encryption_cli encrypt public_keyset.bin \
-    message.txt context_info.txt encrypted_message.bin
-./bazel-bin/hybrid_encryption/hybrid_encryption_cli decrypt private_keyset.bin \
-    encrypted_message.bin context_info.txt decrypted_message.txt
-```
diff --git a/cc/examples/hybrid_encryption/hybrid_cli.cc b/cc/examples/hybrid_encryption/hybrid_cli.cc
index fbf958b..8c4239e 100644
--- a/cc/examples/hybrid_encryption/hybrid_cli.cc
+++ b/cc/examples/hybrid_encryption/hybrid_cli.cc
@@ -15,27 +15,23 @@
 ///////////////////////////////////////////////////////////////////////////////
 // [START hybrid-example]
 // A command-line utility for testing Tink Hybrid Encryption.
-
-#include <fstream>
 #include <iostream>
 #include <memory>
-#include <sstream>
+#include <ostream>
 #include <string>
-#include <utility>
 
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
-#include "absl/memory/memory.h"
-#include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
+#include "absl/log/check.h"
 #include "absl/strings/string_view.h"
-#include "tink/cleartext_keyset_handle.h"
+#include "util/util.h"
+#ifndef TINK_EXAMPLES_EXCLUDE_HPKE
 #include "tink/hybrid/hpke_config.h"
+#endif
+#include "tink/hybrid/hybrid_config.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/hybrid_encrypt.h"
-#include "tink/json_keyset_reader.h"
 #include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
 #include "tink/util/status.h"
 
 ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format");
@@ -47,163 +43,107 @@
 
 namespace {
 
-using ::crypto::tink::CleartextKeysetHandle;
 using ::crypto::tink::HybridDecrypt;
 using ::crypto::tink::HybridEncrypt;
-using ::crypto::tink::JsonKeysetReader;
 using ::crypto::tink::KeysetHandle;
-using ::crypto::tink::KeysetReader;
 using ::crypto::tink::util::Status;
 using ::crypto::tink::util::StatusOr;
 
 constexpr absl::string_view kEncrypt = "encrypt";
 constexpr absl::string_view kDecrypt = "decrypt";
 
-// Creates a KeysetReader that reads a JSON-formatted keyset
-// from the given file.
-StatusOr<std::unique_ptr<KeysetReader>> GetJsonKeysetReader(
-    const std::string& filename) {
-  std::clog << "Creating a JsonKeysetReader...\n";
-  auto key_input_stream = absl::make_unique<std::ifstream>();
-  key_input_stream->open(filename, std::ifstream::in);
-  return JsonKeysetReader::New(std::move(key_input_stream));
-}
-
-// Creates a KeysetHandle that for a keyset read from the given file,
-// which is expected to contain a JSON-formatted keyset.
-StatusOr<std::unique_ptr<KeysetHandle>> ReadKeyset(
-    const std::string& filename) {
-  StatusOr<std::unique_ptr<KeysetReader>> keyset_reader =
-      GetJsonKeysetReader(filename);
-  if (!keyset_reader.ok()) {
-    return keyset_reader.status();
-  }
-  return CleartextKeysetHandle::Read(*std::move(keyset_reader));
-}
-
-// Reads `filename` and returns the read content as a string, or an error status
-// if the file does not exist.
-StatusOr<std::string> Read(const std::string& filename) {
-  std::clog << "Reading the input...\n";
-  std::ifstream input_stream;
-  input_stream.open(filename, std::ifstream::in);
-  if (!input_stream.is_open()) {
-    return Status(absl::StatusCode::kInternal,
-                  absl::StrCat("Error opening input file ", filename));
-  }
-  std::stringstream input;
-  input << input_stream.rdbuf();
-  return input.str();
-}
-
-// Writes the given `data_to_write` to the specified file `filename`.
-Status Write(const std::string& data_to_write, const std::string& filename) {
-  std::clog << "Writing the output...\n";
-  std::ofstream output_stream(filename,
-                              std::ofstream::out | std::ofstream::binary);
-  if (!output_stream.is_open()) {
-    return Status(absl::StatusCode::kInternal,
-                  absl::StrCat("Error opening output file ", filename));
-  }
-  output_stream << data_to_write;
-  return crypto::tink::util::OkStatus();
+void ValidateParams() {
+  // [START_EXCLUDE]
+  CHECK(absl::GetFlag(FLAGS_mode) == kEncrypt ||
+        absl::GetFlag(FLAGS_mode) == kDecrypt)
+      << "Invalid mode; must be `encrypt` or `decrypt`";
+  CHECK(!absl::GetFlag(FLAGS_keyset_filename).empty())
+      << "Keyset file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_input_filename).empty())
+      << "Input file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_output_filename).empty())
+      << "Output file must be specified";
+  // [END_EXCLUDE]
 }
 
 }  // namespace
 
+namespace tink_cc_examples {
+
+Status HybridCli(absl::string_view mode, const std::string& keyset_filename,
+                 const std::string& input_filename,
+                 const std::string& output_filename,
+                 absl::string_view context_info) {
+  Status result = crypto::tink::HybridConfig::Register();
+  if (!result.ok()) return result;
+#ifndef TINK_EXAMPLES_EXCLUDE_HPKE
+  // HPKE isn't supported when using OpenSSL as a backend.
+  result = crypto::tink::RegisterHpke();
+  if (!result.ok()) return result;
+#endif
+
+  // Read the keyset from file.
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      ReadJsonCleartextKeyset(keyset_filename);
+  if (!keyset_handle.ok()) return keyset_handle.status();
+
+  // Read the input.
+  StatusOr<std::string> input_file_content = ReadFile(input_filename);
+  if (!input_file_content.ok()) return input_file_content.status();
+
+  // Compute the output.
+  std::string output;
+  if (mode == kEncrypt) {
+    // Get the hybrid encryption primitive.
+    StatusOr<std::unique_ptr<HybridEncrypt>> hybrid_encrypt_primitive =
+        (*keyset_handle)->GetPrimitive<HybridEncrypt>();
+    if (!hybrid_encrypt_primitive.ok()) {
+      return hybrid_encrypt_primitive.status();
+    }
+    // Generate the ciphertext.
+    StatusOr<std::string> encrypt_result =
+        (*hybrid_encrypt_primitive)->Encrypt(*input_file_content, context_info);
+    if (!encrypt_result.ok()) return encrypt_result.status();
+    output = encrypt_result.value();
+  } else {  // operation == kDecrypt.
+    // Get the hybrid decryption primitive.
+    StatusOr<std::unique_ptr<HybridDecrypt>> hybrid_decrypt_primitive =
+        (*keyset_handle)->GetPrimitive<HybridDecrypt>();
+    if (!hybrid_decrypt_primitive.ok()) {
+      return hybrid_decrypt_primitive.status();
+    }
+    // Recover the plaintext.
+    StatusOr<std::string> decrypt_result =
+        (*hybrid_decrypt_primitive)->Decrypt(*input_file_content, context_info);
+    if (!decrypt_result.ok()) return decrypt_result.status();
+    output = decrypt_result.value();
+  }
+
+  // Write the output to the output file.
+  return WriteToFile(output, output_filename);
+}
+
+}  // namespace tink_cc_examples
+
 int main(int argc, char** argv) {
   absl::ParseCommandLine(argc, argv);
 
+  ValidateParams();
+
   std::string mode = absl::GetFlag(FLAGS_mode);
   std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
   std::string input_filename = absl::GetFlag(FLAGS_input_filename);
   std::string output_filename = absl::GetFlag(FLAGS_output_filename);
   std::string context_info = absl::GetFlag(FLAGS_context_info);
 
-  if (mode.empty()) {
-    std::cerr << "Mode must be specified with --mode=<" << kEncrypt << "|"
-              << kDecrypt << ">." << std::endl;
-    exit(1);
-  }
-
-  if (mode != kEncrypt && mode != kDecrypt) {
-    std::cerr << "Unknown mode '" << mode << "'; "
-              << "Expected either " << kEncrypt << " or " << kDecrypt << "."
-              << std::endl;
-    exit(1);
-  }
   std::clog << "Using keyset from file " << keyset_filename << " to hybrid "
             << mode << " file " << input_filename << " with context info '"
             << context_info << "'." << std::endl;
   std::clog << "The resulting output will be written to " << output_filename
             << std::endl;
 
-  Status result = crypto::tink::RegisterHpke();
-  if (!result.ok()) {
-    std::cerr << result.message() << std::endl;
-    exit(1);
-  }
-
-  // Read the keyset from file.
-  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
-      ReadKeyset(keyset_filename);
-  if (!keyset_handle.ok()) {
-    std::cerr << keyset_handle.status().message() << std::endl;
-    exit(1);
-  }
-
-  // Read the input.
-  StatusOr<std::string> input_file_content = Read(input_filename);
-  if (!input_file_content.ok()) {
-    std::cerr << input_file_content.status().message() << std::endl;
-    exit(1);
-  }
-
-  // Compute the output.
-  std::clog << mode << "ing...\n";
-  std::string output;
-  if (mode == kEncrypt) {
-    // Get the hybrid encryption primitive.
-    StatusOr<std::unique_ptr<HybridEncrypt>> hybrid_encrypt_primitive =
-        (*keyset_handle)->GetPrimitive<HybridEncrypt>();
-    if (!hybrid_encrypt_primitive.ok()) {
-      std::cerr << hybrid_encrypt_primitive.status().message() << std::endl;
-      exit(1);
-    }
-    // Generate the ciphertext.
-    StatusOr<std::string> encrypt_result =
-        (*hybrid_encrypt_primitive)->Encrypt(*input_file_content, context_info);
-    if (!encrypt_result.ok()) {
-      std::cerr << encrypt_result.status().message() << std::endl;
-      exit(1);
-    }
-    output = encrypt_result.value();
-  } else {  // operation == kDecrypt.
-    // Get the hybrid decryption primitive.
-    StatusOr<std::unique_ptr<HybridDecrypt>> hybrid_decrypt_primitive =
-        (*keyset_handle)->GetPrimitive<HybridDecrypt>();
-    if (!hybrid_decrypt_primitive.ok()) {
-      std::cerr << hybrid_decrypt_primitive.status().message() << std::endl;
-      exit(1);
-    }
-    // Recover the plaintext.
-    StatusOr<std::string> decrypt_result =
-        (*hybrid_decrypt_primitive)->Decrypt(*input_file_content, context_info);
-    if (!decrypt_result.ok()) {
-      std::cerr << decrypt_result.status().message() << std::endl;
-      exit(1);
-    }
-    output = decrypt_result.value();
-  }
-
-  // Write the output to the output file.
-  Status write_result = Write(output, output_filename);
-  if (!write_result.ok()) {
-    std::cerr << write_result.message() << std::endl;
-    exit(1);
-  }
-
-  std::clog << "All done." << std::endl;
+  CHECK_OK(tink_cc_examples::HybridCli(mode, keyset_filename, input_filename,
+                                       output_filename, context_info));
   return 0;
 }
 // [END hybrid-example]
diff --git a/cc/examples/hybrid_encryption/hybrid_cli_test.sh b/cc/examples/hybrid_encryption/hybrid_cli_test.sh
index 22142c7..1909902 100755
--- a/cc/examples/hybrid_encryption/hybrid_cli_test.sh
+++ b/cc/examples/hybrid_encryption/hybrid_cli_test.sh
@@ -20,6 +20,8 @@
 # Tests for Tink C++ hybrid encryption example.
 #############################################################################
 
+: "${TEST_TMPDIR:=$(mktemp -d)}"
+
 readonly CLI="$1"
 readonly PRIVATE_KEYSET_FILE="$2"
 readonly PUBLIC_KEYSET_FILE="$3"
@@ -45,7 +47,7 @@
 }
 
 #######################################
-# Asserts that the outcome of the latest test command was the expected one.
+# Asserts that the outcome of the latest test command is 0.
 #
 # If not, it terminates the test execution.
 #
@@ -53,23 +55,29 @@
 #   TEST_STATUS
 #   TEST_NAME
 #   TEST_CASE
-# Arguments:
-#   expected_outcome: The expected outcome.
 #######################################
-_assert_test_command_outcome() {
-  expected_outcome="$1"
-  if (( TEST_STATUS != expected_outcome )); then
-      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
-      exit 1
+assert_command_succeeded() {
+  if (( TEST_STATUS != 0 )); then
+    echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+    exit 1
   fi
 }
 
-assert_command_succeeded() {
-  _assert_test_command_outcome 0
-}
-
+#######################################
+# Asserts that the outcome of the latest test command is not 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
 assert_command_failed() {
-  _assert_test_command_outcome 1
+  if (( TEST_STATUS == 0 )); then
+      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+      exit 1
+  fi
 }
 
 #######################################
diff --git a/cc/examples/hybrid_encryption/hybrid_encryption_cli.cc b/cc/examples/hybrid_encryption/hybrid_encryption_cli.cc
deleted file mode 100644
index 36dd752..0000000
--- a/cc/examples/hybrid_encryption/hybrid_encryption_cli.cc
+++ /dev/null
@@ -1,266 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-// A command-line utility for generating Hybrid Encryption keys, and encrypting
-// and decrypting files using hybrid encryption.
-//
-// The first argument is the operation and it should be one of the following:
-// gen-private-key get-public-key encrypt decrypt.
-// Additional arguments depend on the operation.
-//
-// gen-private-key
-//   Generates a new private keyset using the RsaSsaPkcs13072Sha256F4 template.
-//   It requires 1 additional argument:
-//     output-file: name of the file for the resulting output
-//
-// get-public-key
-//   Extracts a public keyset associated with the given private keyset.
-//   It requires 2 additional arguments:
-//     private-keyset-file: name of the file with the private keyset
-//     output-file: name of the file for the resulting output
-//
-// encrypt
-//   Encrypts the message using the given public keyset.
-//   It requires 4 additional arguments:
-//     public-keyset-file: name of the file with the public keyset
-//     message-file: name of the file with the message
-//     context-info-file: name of the file with the context-info,
-//                        can also be an empty file
-//     output-file: name of the file for the resulting output
-//
-// decrypt
-//   Decrypts the encrypted message using the given private keyset.
-//   It requires 4 additional arguments:
-//     private-keyset-file: name of the file with the private keyset
-//     encrypted-message-file: name of the file with the message
-//     context-info-file: name of the file with the context-info
-//     output-file: name of the file for the decrypted message
-
-#include <iostream>
-#include <string>
-#include <utility>
-
-#include "tink/hybrid/hybrid_key_templates.h"
-#include "tink/hybrid_decrypt.h"
-#include "tink/hybrid_encrypt.h"
-#include "hybrid_encryption/util.h"
-
-// Prints usage info.
-void PrintUsageInfo() {
-  std::clog << "Usage: operation arguments\n"
-            << "where operation is one of the following:\n"
-            << "  gen-private-key get-public-key encrypt decrypt\n"
-            << "and, depending on the operation, arguments are:\n"
-            << "  gen-private-key: output-file\n"
-            << "  get-public-key: private-keyset-file output-file\n"
-            << "  encrypt: public-keyset-file message-file context-info-file"
-            << " output-file\n"
-            << "  decrypt: private-keyset-file encrypted-file context-info-file"
-            << " output-file"
-            << std::endl;
-}
-
-// Generates a new private keyset using the EciesP256HkdfHmacSha256Aes128Gcm
-// template and writes it to the output file.
-void GeneratePrivateKey(const std::string& output_filename) {
-  std::clog << "Generating a new private keyset.." << std::endl;
-
-  auto key_template =
-      crypto::tink::HybridKeyTemplates::EciesP256HkdfHmacSha256Aes128Gcm();
-  auto new_keyset_handle_result =
-      crypto::tink::KeysetHandle::GenerateNew(key_template);
-  if (!new_keyset_handle_result.ok()) {
-    std::clog << "Generating new keyset failed: "
-              << new_keyset_handle_result.status().message() << std::endl;
-    exit(1);
-  }
-  auto keyset_handle = std::move(new_keyset_handle_result.value());
-
-  std::clog << "Writing the keyset to file " << output_filename << "..."
-            << std::endl;
-
-  Util::WriteKeyset(keyset_handle, output_filename);
-}
-
-// Extracts a public keyset associated with the given private keyset
-// and writes it to the output file.
-void ExtractPublicKey(const std::string& private_keyset_filename,
-                      const std::string& output_filename) {
-  std::clog << "Extracting a public keyset associated with the private "
-            << "keyset from file " << private_keyset_filename << "..."
-            << std::endl;
-
-  auto private_keyset_handle = Util::ReadKeyset(private_keyset_filename);
-
-  auto new_keyset_handle_result =
-      private_keyset_handle->GetPublicKeysetHandle();
-  if (!new_keyset_handle_result.ok()) {
-    std::clog << "Getting the keyset failed: "
-              << new_keyset_handle_result.status().message() << std::endl;
-    exit(1);
-  }
-  auto public_keyset_handle = std::move(new_keyset_handle_result.value());
-
-  std::clog << "Writing the keyset to file " << output_filename << "..."
-            << std::endl;
-
-  Util::WriteKeyset(public_keyset_handle, output_filename);
-}
-
-// Encrypts the message using the given public keyset
-// and writes the encrypted message to the output file.
-void Encrypt(const std::string& keyset_filename,
-             const std::string& message_filename,
-             const std::string& context_info_filename,
-             const std::string& output_filename) {
-  auto keyset_handle = Util::ReadKeyset(keyset_filename);
-
-  auto primitive_result =
-      keyset_handle->GetPrimitive<crypto::tink::HybridEncrypt>();
-  if (!primitive_result.ok()) {
-    std::clog << "Getting HybridEncryption-primitive from the factory failed: "
-              << primitive_result.status().message() << std::endl;
-    exit(1);
-  }
-  auto hybrid_encrypt = std::move(primitive_result.value());
-
-  std::clog << "Encrypting message from file " << message_filename
-            << " using public keyset from file " << keyset_filename << "..."
-            << std::endl;
-
-  std::string message = Util::Read(message_filename);
-  std::string context_info = Util::Read(context_info_filename);
-
-  auto encrypt_result = hybrid_encrypt->Encrypt(message, context_info);
-  if (!encrypt_result.ok()) {
-    std::clog << "Error while encrypting the message: "
-              << encrypt_result.status().message() << std::endl;
-    exit(1);
-  }
-  std::string encrypted_message = encrypt_result.value();
-
-  std::clog << "Writing the resulting encrypted message to file "
-            << output_filename << "..." << std::endl;
-
-  Util::Write(encrypted_message, output_filename);
-}
-
-// Decrypts the encrypted message using the given private keyset
-// and writes the result to the output file.
-void Decrypt(const std::string& keyset_filename,
-             const std::string& message_filename,
-             const std::string& context_info_filename,
-             const std::string& output_filename) {
-  auto keyset_handle = Util::ReadKeyset(keyset_filename);
-
-  auto primitive_result =
-      keyset_handle->GetPrimitive<crypto::tink::HybridDecrypt>();
-  if (!primitive_result.ok()) {
-    std::clog << "Getting HybridDecrypt-primitive from the factory "
-              << "failed: " << primitive_result.status().message() << std::endl;
-    exit(1);
-  }
-  auto hybrid_decrypt = std::move(primitive_result.value());
-
-  std::clog << "Decrypting the encrypted file " << message_filename
-            << " to the file " << output_filename
-            << " using private keyset from file " << keyset_filename << "..."
-            << std::endl;
-
-  std::string message = Util::Read(message_filename);
-  std::string context_info = Util::Read(context_info_filename);
-
-  std::string result;
-  auto decrypt_status = hybrid_decrypt->Decrypt(message, context_info);
-  if (!decrypt_status.ok()) {
-    std::clog << "Error while decrypting the file: "
-              << decrypt_status.status().message() << std::endl;
-    exit(1);
-  }
-
-  std::string decrypted_message = decrypt_status.value();
-
-  std::clog << "Writing the resulting decrypted message to file "
-            << output_filename << "..." << std::endl;
-
-  Util::Write(decrypted_message, output_filename);
-}
-
-int main(int argc, char** argv) {
-  if (argc == 1) {
-    PrintUsageInfo();
-    exit(1);
-  }
-
-  Util::InitTink();
-
-  std::string operation = argv[1];
-
-  if (operation == "gen-private-key") {
-    if (argc != 3) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string output_filename = argv[2];
-
-    GeneratePrivateKey(output_filename);
-  } else if (operation == "get-public-key") {
-    if (argc != 4) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string private_keyset_filename = argv[2];
-    std::string output_filename = argv[3];
-
-    ExtractPublicKey(private_keyset_filename, output_filename);
-  } else if (operation == "encrypt") {
-    if (argc != 6) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string keyset_filename = argv[2];
-    std::string message_filename = argv[3];
-    std::string context_info_filename = argv[4];
-    std::string output_filename = argv[5];
-
-    Encrypt(keyset_filename, message_filename, context_info_filename,
-            output_filename);
-  } else if (operation == "decrypt") {
-    if (argc != 6) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string keyset_filename = argv[2];
-    std::string message_filename = argv[3];
-    std::string context_info_filename = argv[4];
-    std::string output_filename = argv[5];
-
-    Decrypt(keyset_filename, message_filename, context_info_filename,
-            output_filename);
-  } else {
-    std::clog << "Unknown operation. Supported operations are: "
-              << "gen-private-key get-public-key encrypt decrypt" << std::endl;
-    exit(1);
-  }
-
-  std::clog << "Done!" << std::endl;
-
-  return 0;
-}
diff --git a/cc/examples/hybrid_encryption/hybrid_encryption_cli_test.sh b/cc/examples/hybrid_encryption/hybrid_encryption_cli_test.sh
deleted file mode 100755
index d60d681..0000000
--- a/cc/examples/hybrid_encryption/hybrid_encryption_cli_test.sh
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/bin/bash
-# Copyright 2020 Google LLC
-#
-# 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
-#
-#      http://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.
-################################################################################
-
-
-#############################################################################
-#### Tests for hybrid_encrypt_cli binary.
-
-HYBRID_CLI="$1"
-
-PRIVATE_KEYSET_FILE="$TEST_TMPDIR/private_keyset.bin"
-PUBLIC_KEYSET_FILE="$TEST_TMPDIR/public_keyset.bin"
-MESSAGE_FILE="$TEST_TMPDIR/message.txt"
-CONTEXT_INFO_FILE="$TEST_TMPDIR/context_info.txt"
-ENCRYPTED_MESSAGE_FILE="$TEST_TMPDIR/encrypted_message.bin"
-DECRYPTED_MESSAGE_FILE="$TEST_TMPDIR/decrypted_message.txt"
-RESULT_FILE="$TEST_TMPDIR/result.txt"
-
-OTHER_PRIVATE_KEYSET_FILE="$TEST_TMPDIR/other_private_keyset.bin"
-OTHER_PUBLIC_KEYSET_FILE="$TEST_TMPDIR/other_public_keyset.bin"
-OTHER_MESSAGE_FILE="$TEST_TMPDIR/other_message.txt"
-
-echo "This is a message." > $MESSAGE_FILE
-echo "This is a different message." > $OTHER_MESSAGE_FILE
-echo "context" > $CONTEXT_INFO_FILE
-echo "different context" > $OTHER_CONTEXT_INFO_FILE
-
-#############################################################################
-#### Helper function that checks if values are equal.
-
-assert_equal() {
-  if [ "$1" == "$2" ]; then
-    echo "+++ Success: values are equal."
-  else
-    echo "--- Failure: values are different. Expected: [$1], actual: [$2]."
-    exit 1
-  fi
-}
-
-#############################################################################
-#### All good, everything should work.
-test_name="all_good"
-echo "+++ Starting test $test_name..."
-
-#### Generate a private key and get a public key.
-$HYBRID_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$HYBRID_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-
-#### Encrypt the message.
-$HYBRID_CLI encrypt $PUBLIC_KEYSET_FILE $MESSAGE_FILE $CONTEXT_INFO_FILE $ENCRYPTED_MESSAGE_FILE || exit 1
-
-#### Decrypt the encrypted message.
-$HYBRID_CLI decrypt $PRIVATE_KEYSET_FILE $ENCRYPTED_MESSAGE_FILE $CONTEXT_INFO_FILE $DECRYPTED_MESSAGE_FILE || exit 1
-
-#### Check that the decrypted message is same as original message.
-DECRYPTED_MESSAGE=$(<$DECRYPTED_MESSAGE_FILE)
-ORIGINAL_MESSAGE=$(<$MESSAGE_FILE)
-assert_equal "$ORIGINAL_MESSAGE" "$DECRYPTED_MESSAGE"
-
-#############################################################################
-#### Bad private key when getting the public key.
-test_name="get_public_key_with_bad_private_key"
-echo "+++ Starting test $test_name..."
-
-echo "abcd" >> $PRIVATE_KEYSET_FILE
-$HYBRID_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
-
-#############################################################################
-#### Decrypting a bad encrypted file.
-test_name="decrypt_a_bad_file"
-echo "+++ Starting test $test_name..."
-
-$HYBRID_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$HYBRID_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$HYBRID_CLI encrypt $PUBLIC_KEYSET_FILE $MESSAGE_FILE $CONTEXT_INFO_FILE $ENCRYPTED_MESSAGE_FILE || exit 1
-$HYBRID_CLI decrypt $PRIVATE_KEYSET_FILE $OTHER_MESSAGE_FILE $CONTEXT_INFO_FILE $RESULT_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
-
-#############################################################################
-#### Encrypt with wrong key.
-test_name="encrypt_with_wrong_key"
-echo "+++ Starting test $test_name..."
-
-$HYBRID_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$HYBRID_CLI encrypt $PRIVATE_KEYSET_FILE $MESSAGE_FILE $ENCRYPTED_MESSAGE_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
-
-#############################################################################
-#### Decrypt with wrong key.
-test_name="decrypt_with_wrong_key"
-echo "+++ Starting test $test_name..."
-
-$HYBRID_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$HYBRID_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$HYBRID_CLI encrypt $PUBLIC_KEYSET_FILE $MESSAGE_FILE $CONTEXT_INFO_FILE $ENCRYPTED_MESSAGE_FILE || exit 1
-$HYBRID_CLI decrypt $PUBLIC_KEYSET_FILE $ENCRYPTED_MESSAGE_FILE $CONTEXT_INFO_FILE $RESULT_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
-
-#############################################################################
-#### Decrypt with different context.
-test_name="decrypt_with_different_context"
-echo "+++ Starting test $test_name..."
-
-$HYBRID_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$HYBRID_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$HYBRID_CLI encrypt $PUBLIC_KEYSET_FILE $MESSAGE_FILE $CONTEXT_INFO_FILE $ENCRYPTED_MESSAGE_FILE || exit 1
-$HYBRID_CLI decrypt $PRIVATE_KEYSET_FILE $ENCRYPTED_MESSAGE_FILE $OTHER_CONTEXT_INFO_FILE $RESULT_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
diff --git a/cc/examples/hybrid_encryption/testdata/BUILD.bazel b/cc/examples/hybrid_encryption/testdata/BUILD.bazel
new file mode 100644
index 0000000..323479b
--- /dev/null
+++ b/cc/examples/hybrid_encryption/testdata/BUILD.bazel
@@ -0,0 +1,19 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+filegroup(
+    name = "hpke_test_keyset",
+    srcs = [
+        "hpke_test_private_keyset.json",
+        "hpke_test_public_keyset.json",
+    ],
+)
+
+filegroup(
+    name = "hybrid_test_keyset",
+    srcs = [
+        "hybrid_test_private_keyset.json",
+        "hybrid_test_public_keyset.json",
+    ],
+)
diff --git a/cc/examples/hybrid_encryption/hybrid_test_private_keyset.json b/cc/examples/hybrid_encryption/testdata/hpke_test_private_keyset.json
similarity index 100%
rename from cc/examples/hybrid_encryption/hybrid_test_private_keyset.json
rename to cc/examples/hybrid_encryption/testdata/hpke_test_private_keyset.json
diff --git a/cc/examples/hybrid_encryption/hybrid_test_public_keyset.json b/cc/examples/hybrid_encryption/testdata/hpke_test_public_keyset.json
similarity index 100%
rename from cc/examples/hybrid_encryption/hybrid_test_public_keyset.json
rename to cc/examples/hybrid_encryption/testdata/hpke_test_public_keyset.json
diff --git a/cc/examples/hybrid_encryption/testdata/hybrid_test_private_keyset.json b/cc/examples/hybrid_encryption/testdata/hybrid_test_private_keyset.json
new file mode 100644
index 0000000..b237360
--- /dev/null
+++ b/cc/examples/hybrid_encryption/testdata/hybrid_test_private_keyset.json
@@ -0,0 +1,15 @@
+{
+  "primaryKeyId": 548859458,
+  "key": [
+    {
+      "keyData": {
+        "typeUrl": "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey",
+        "value": "EowBEkQKBAgCEAMSOhI4CjB0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5jcnlwdG8udGluay5BZXNHY21LZXkSAhAQGAEYARohAKjjAxgGmD9j90UyzNunoC04kWqaWiXGFRhOYfLS7Z2tIiEAhqqb+D0Din92zHwGQefzui0hma5khIZQCWyWHHVgNpsaIBQrEEuEn3hClVKM+4bsvmaUOqFYMbl7E6lNFJzbr+lp",
+        "keyMaterialType": "ASYMMETRIC_PRIVATE"
+      },
+      "status": "ENABLED",
+      "keyId": 548859458,
+      "outputPrefixType": "TINK"
+    }
+  ]
+}
diff --git a/cc/examples/hybrid_encryption/testdata/hybrid_test_public_keyset.json b/cc/examples/hybrid_encryption/testdata/hybrid_test_public_keyset.json
new file mode 100644
index 0000000..ddb1095
--- /dev/null
+++ b/cc/examples/hybrid_encryption/testdata/hybrid_test_public_keyset.json
@@ -0,0 +1,15 @@
+{
+  "primaryKeyId": 548859458,
+  "key": [
+    {
+      "keyData": {
+        "typeUrl": "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey",
+        "value": "EkQKBAgCEAMSOhI4CjB0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5jcnlwdG8udGluay5BZXNHY21LZXkSAhAQGAEYARohAKjjAxgGmD9j90UyzNunoC04kWqaWiXGFRhOYfLS7Z2tIiEAhqqb+D0Din92zHwGQefzui0hma5khIZQCWyWHHVgNps=",
+        "keyMaterialType": "ASYMMETRIC_PUBLIC"
+      },
+      "status": "ENABLED",
+      "keyId": 548859458,
+      "outputPrefixType": "TINK"
+    }
+  ]
+}
diff --git a/cc/examples/hybrid_encryption/util.cc b/cc/examples/hybrid_encryption/util.cc
deleted file mode 100644
index 3a9760f..0000000
--- a/cc/examples/hybrid_encryption/util.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// 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
-//
-//     http://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 "hybrid_encryption/util.h"
-
-#include <fstream>
-#include <iostream>
-#include <string>
-#include <utility>
-
-#include "tink/binary_keyset_reader.h"
-#include "tink/binary_keyset_writer.h"
-#include "tink/cleartext_keyset_handle.h"
-#include "tink/config.h"
-#include "tink/config/tink_config.h"
-#include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
-#include "tink/keyset_writer.h"
-#include "tink/util/status.h"
-
-using crypto::tink::BinaryKeysetReader;
-using crypto::tink::BinaryKeysetWriter;
-using crypto::tink::CleartextKeysetHandle;
-using crypto::tink::KeysetHandle;
-using crypto::tink::KeysetReader;
-using crypto::tink::KeysetWriter;
-using crypto::tink::TinkConfig;
-
-// static
-std::unique_ptr<KeysetReader> Util::GetBinaryKeysetReader(
-    const std::string& filename) {
-  std::unique_ptr<std::ifstream> keyset_stream(new std::ifstream());
-  keyset_stream->open(filename, std::ifstream::in);
-  auto keyset_reader_result = BinaryKeysetReader::New(std::move(keyset_stream));
-  if (!keyset_reader_result.ok()) {
-    std::clog << "Creation of the BinaryKeysetReader failed: "
-              << keyset_reader_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_reader_result.value());
-}
-
-// static
-std::unique_ptr<KeysetWriter> Util::GetBinaryKeysetWriter(
-    const std::string& filename) {
-  std::unique_ptr<std::ofstream> keyset_stream(new std::ofstream());
-  keyset_stream->open(filename, std::ofstream::out);
-  auto keyset_writer_result = BinaryKeysetWriter::New(std::move(keyset_stream));
-  if (!keyset_writer_result.ok()) {
-    std::clog << "Creation of the BinaryKeysetWriter failed: "
-              << keyset_writer_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_writer_result.value());
-}
-
-// static
-std::unique_ptr<KeysetHandle> Util::ReadKeyset(const std::string& filename) {
-  auto keyset_reader = GetBinaryKeysetReader(filename);
-  auto keyset_handle_result =
-      CleartextKeysetHandle::Read(std::move(keyset_reader));
-  if (!keyset_handle_result.ok()) {
-    std::clog << "Reading the keyset failed: "
-              << keyset_handle_result.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(keyset_handle_result.value());
-}
-
-// static
-void Util::WriteKeyset(const std::unique_ptr<KeysetHandle>& keyset_handle,
-                       const std::string& filename) {
-  auto keyset_writer = GetBinaryKeysetWriter(filename);
-  auto status =
-      CleartextKeysetHandle::Write(keyset_writer.get(), *keyset_handle);
-  if (!status.ok()) {
-    std::clog << "Writing the keyset failed: " << status.message() << std::endl;
-    exit(1);
-  }
-}
-
-// static
-void Util::InitTink() {
-  auto status = TinkConfig::Register();
-  if (!status.ok()) {
-    std::clog << "Initialization of Tink failed: " << status.message()
-              << std::endl;
-    exit(1);
-  }
-}
-
-// static
-std::string Util::Read(const std::string& filename) {
-  std::ifstream input_stream;
-  input_stream.open(filename, std::ifstream::in);
-  if (!input_stream.is_open()) {
-    std::clog << "Error opening input file " << filename << std::endl;
-    exit(1);
-  }
-  std::stringstream input;
-  input << input_stream.rdbuf();
-  input_stream.close();
-  return input.str();
-}
-
-// static
-void Util::Write(const std::string& output, const std::string& filename) {
-  std::ofstream output_stream(filename,
-                              std::ofstream::out | std::ofstream::binary);
-  if (!output_stream.is_open()) {
-    std::clog << "Error opening output file " << filename << std::endl;
-    exit(1);
-  }
-  output_stream << output;
-  output_stream.close();
-}
diff --git a/cc/examples/hybrid_encryption/util.h b/cc/examples/hybrid_encryption/util.h
deleted file mode 100644
index 072fb14..0000000
--- a/cc/examples/hybrid_encryption/util.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 Google LLC
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_EXAMPLES_HYBRID_ENCRYPTION_UTIL_H_
-#define TINK_EXAMPLES_HYBRID_ENCRYPTION_UTIL_H_
-
-#include <fstream>
-#include <iostream>
-#include <string>
-
-#include "tink/keyset_handle.h"
-
-// Helper functions for Digital Signatures CLI
-class Util {
- public:
-  // Returns a BinaryKeysetReader that reads from the specified file.
-  // In case of errors writes a log message and aborts.
-  static std::unique_ptr<crypto::tink::KeysetReader> GetBinaryKeysetReader(
-      const std::string& filename);
-
-  // Returns a BinaryKeysetWriter that writes from the specified file.
-  // In case of errors writes a log message and aborts.
-  static std::unique_ptr<crypto::tink::KeysetWriter> GetBinaryKeysetWriter(
-      const std::string& filename);
-
-  // Reads a keyset from the specified file.
-  // In case of errors writes a log message and aborts.
-  static std::unique_ptr<crypto::tink::KeysetHandle> ReadKeyset(
-      const std::string& filename);
-
-  // Writes the keyset to the specified file.
-  // In case of errors writes a log message and aborts.
-  static void WriteKeyset(
-      const std::unique_ptr<crypto::tink::KeysetHandle>& keyset_handle,
-      const std::string& filename);
-
-  // Initializes Tink registry.
-  // In case of errors writes a log message and aborts.
-  static void InitTink();
-
-  // Reads the specified file and returns the contents as a string.
-  // In case of errors writes a log message and aborts.
-  static std::string Read(const std::string& filename);
-
-  // Writes the given 'output' to the specified file.
-  // In case of errors writes a log message and aborts.
-  static void Write(const std::string& output, const std::string& filename);
-};
-
-#endif  // TINK_EXAMPLES_HYBRID_ENCRYPTION_UTIL_H_
diff --git a/cc/examples/jwt/BUILD.bazel b/cc/examples/jwt/BUILD.bazel
index 5c38728..e35bad5 100644
--- a/cc/examples/jwt/BUILD.bazel
+++ b/cc/examples/jwt/BUILD.bazel
@@ -2,19 +2,11 @@
 
 licenses(["notice"])
 
-cc_library(
-    name = "util",
-    srcs = ["util.cc"],
-    hdrs = ["util.h"],
-    deps = [
-        "@com_google_absl//absl/strings",
-        "@tink_cc//:binary_keyset_reader",
-        "@tink_cc//:binary_keyset_writer",
-        "@tink_cc//:cleartext_keyset_handle",
-        "@tink_cc//:keyset_handle",
-        "@tink_cc//:keyset_reader",
-        "@tink_cc//:keyset_writer",
-        "@tink_cc//util:status",
+filegroup(
+    name = "jwt_signature_keysets",
+    srcs = [
+        "jwt_signature_private_keyset.json",
+        "jwt_signature_public_keyset.json",
     ],
 )
 
@@ -22,14 +14,17 @@
     name = "jwt_signature_cli",
     srcs = ["jwt_signature_cli.cc"],
     deps = [
-        ":util",
-        "@tink_cc//jwt:jwt_key_templates",
+        "//util",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/log:check",
+        "@tink_cc//:keyset_handle",
         "@tink_cc//jwt:jwt_public_key_sign",
         "@tink_cc//jwt:jwt_public_key_verify",
         "@tink_cc//jwt:jwt_signature_config",
         "@tink_cc//jwt:jwt_validator",
         "@tink_cc//jwt:raw_jwt",
-        "@tink_cc//jwt:verified_jwt",
+        "@tink_cc//util:status",
     ],
 )
 
@@ -39,6 +34,10 @@
     srcs = ["jwt_signature_cli_test.sh"],
     args = [
         "$(rootpath :jwt_signature_cli)",
+        "$(rootpaths :jwt_signature_keysets)",
     ],
-    data = [":jwt_signature_cli"],
+    data = [
+        ":jwt_signature_cli",
+        ":jwt_signature_keysets",
+    ],
 )
diff --git a/cc/examples/jwt/CMakeLists.txt b/cc/examples/jwt/CMakeLists.txt
new file mode 100644
index 0000000..3bced16
--- /dev/null
+++ b/cc/examples/jwt/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_executable(jwt_signature_cli jwt_signature_cli.cc)
+target_include_directories(jwt_signature_cli PUBLIC
+  "${CMAKE_CURRENT_SOURCE_DIR}"
+  "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(jwt_signature_cli
+  tink::static
+  tink::jwt::jwt_signature_config
+  absl::check
+  absl::flags_parse
+  util)
+
+add_test(
+  NAME jwt_signature_cli_test
+  COMMAND "${BASH_PROGRAM}"
+    "${CMAKE_CURRENT_SOURCE_DIR}/jwt_signature_cli_test.sh"
+    "${CMAKE_CURRENT_BINARY_DIR}/jwt_signature_cli"
+    "${CMAKE_CURRENT_SOURCE_DIR}/jwt_signature_private_keyset.json"
+    "${CMAKE_CURRENT_SOURCE_DIR}/jwt_signature_public_keyset.json")
diff --git a/cc/examples/jwt/README.md b/cc/examples/jwt/README.md
deleted file mode 100644
index 6867d75..0000000
--- a/cc/examples/jwt/README.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# C++ JWT Signatures CLI
-
-This is a command-line utility for generating JSON Web Token (JWT) keys, and
-creating and verifying JWTs.
-
-It demonstrates the basic steps of using Tink, namely loading key material,
-obtaining a primitive, and using the primitive to do crypto.
-
-## Build and Run
-
-### Bazel
-
-```shell
-git clone https://github.com/google/tink
-cd tink/cc/examples
-bazel build ...
-./bazel-bin/jwt/jwt_signature_cli gen-private-key private_keyset.bin
-./bazel-bin/jwt/jwt_signature_cli get-public-key private_keyset.bin \
-    public_keyset.bin
-./bazel-bin/jwt/jwt_signature_cli sign private_keyset.bin \
-    my-audience token.txt
-./bazel-bin/jwt/jwt_signature_cli verify public_keyset.bin \
-    my-audience token.txt result.txt
-cat result.txt
-```
diff --git a/cc/examples/jwt/jwt_signature_cli.cc b/cc/examples/jwt/jwt_signature_cli.cc
index 8ffc229..34f6b05 100644
--- a/cc/examples/jwt/jwt_signature_cli.cc
+++ b/cc/examples/jwt/jwt_signature_cli.cc
@@ -13,276 +13,131 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
-
-// A command-line utility for generating JSON Web Token (JWT) keys, and creating
-// and verifying JWTs.
-//
-// The first argument is the operation and it should be one of the following:
-// gen-private-key get-public-key sign verify.
-// Additional arguments depend on the operation.
-//
-// gen-private-key
-//   Generates a new private keyset using JwtRs256_2048_F4_Template.
-//   It requires 1 additional argument:
-//     output-file: name of the file for the resulting output
-//
-// get-public-key
-//   Extracts a public keyset associated with the given private keyset.
-//   It requires 2 additional arguments:
-//     private-keyset-file: name of the file with the private keyset
-//     output-file: name of the file for the resulting output
-//
-// sign
-//   Generates and signs a token using the given private keyset.
-//   It requires 3 additional arguments:
-//     private-keyset-file: name of the file with the private keyset
-//     audience: audience claim to be put in the token.
-//     output-file: name of the file for the resulting output
-//
-// verify
-//   Verifies a token using the given public keyset.
-//   It requires 4 additional arguments:
-//     public-keyset-file: name of the file with the public keyset
-//     audience: expected audience in the token
-//     token-file: name of the file with the token
-//     output-file: name of the file for the resulting output (valid/invalid)
-
+// [START jwt-example]
+// A utility for creating, signing and verifying JSON Web Tokens (JWT).
 #include <iostream>
+#include <memory>
+#include <ostream>
 #include <string>
+#include <utility>
 
-#include "tink/jwt/jwt_key_templates.h"
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/log/check.h"
+#include "util/util.h"
 #include "tink/jwt/jwt_public_key_sign.h"
 #include "tink/jwt/jwt_public_key_verify.h"
 #include "tink/jwt/jwt_signature_config.h"
 #include "tink/jwt/jwt_validator.h"
 #include "tink/jwt/raw_jwt.h"
-#include "tink/jwt/verified_jwt.h"
-#include "jwt/util.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/status.h"
 
-// Prints usage info.
-using crypto::tink::JwtPublicKeySign;
-using crypto::tink::JwtPublicKeyVerify;
-using crypto::tink::JwtValidator;
-using crypto::tink::KeysetHandle;
-using crypto::tink::RawJwt;
-using crypto::tink::RawJwtBuilder;
+ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format");
+ABSL_FLAG(std::string, mode, "", "Mode of operation (sign|verify)");
+ABSL_FLAG(std::string, audience, "", "Expected audience in the token");
+ABSL_FLAG(std::string, token_filename, "", "Path to the token file");
 
-void PrintUsageInfo() {
-  std::clog << "Usage: operation arguments\n"
-            << "where operation is one of the following:\n"
-            << "  gen-private-key get-public-key sign verify\n"
-            << "and, depending on the operation, arguments are:\n"
-            << "  gen-private-key: output-file\n"
-            << "  get-public-key: private-keyset-file output-file\n"
-            << "  sign: private-keyset-file audience output-file\n"
-            << "  verify: public-keyset-file audience token-file "
-            << "output-file" << std::endl;
+namespace {
+
+using ::crypto::tink::JwtPublicKeySign;
+using ::crypto::tink::JwtPublicKeyVerify;
+using ::crypto::tink::JwtValidator;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::RawJwt;
+using ::crypto::tink::RawJwtBuilder;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+
+constexpr absl::string_view kSign = "sign";
+constexpr absl::string_view kVerify = "verify";
+
+void ValidateParams() {
+  // [START_EXCLUDE]
+  CHECK(absl::GetFlag(FLAGS_mode) == kSign ||
+        absl::GetFlag(FLAGS_mode) == kVerify)
+      << "Invalid mode; must be `" << kSign << "` or `" << kVerify << "`"
+      << std::endl;
+  CHECK(!absl::GetFlag(FLAGS_keyset_filename).empty())
+      << "Keyset file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_audience).empty())
+      << "Expected audience in the token must be specified";
+  CHECK(!absl::GetFlag(FLAGS_token_filename).empty())
+      << "Token file must be specified";
+  // [END_EXCLUDE]
 }
 
-// Generates a new private keyset using JwtRs256_2048_F4_Template and writes it
-// to the output file.
-void GeneratePrivateKey(absl::string_view output_filename) {
-  std::clog << "Generating a new private keyset.." << std::endl;
+}  // namespace
 
-  auto key_template = crypto::tink::JwtRs256_2048_F4_Template();
-  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
-      crypto::tink::KeysetHandle::GenerateNew(key_template);
-  if (!keyset_handle.ok()) {
-    std::clog << "Generating new keyset failed: "
-              << keyset_handle.status().message() << std::endl;
-    exit(1);
+namespace tink_cc_examples {
+
+// JWT example CLI implementation.
+Status JwtCli(absl::string_view mode, const std::string& keyset_filename,
+              absl::string_view audience, const std::string& token_filename) {
+  Status result = crypto::tink::JwtSignatureRegister();
+  if (!result.ok()) return result;
+
+  // Read the keyset from file.
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      ReadJsonCleartextKeyset(keyset_filename);
+  if (!keyset_handle.ok()) return keyset_handle.status();
+
+  if (mode == kSign) {
+    StatusOr<RawJwt> raw_jwt =
+        RawJwtBuilder()
+            .AddAudience(audience)
+            .SetExpiration(absl::Now() + absl::Seconds(100))
+            .Build();
+    if (!raw_jwt.ok()) return raw_jwt.status();
+    StatusOr<std::unique_ptr<JwtPublicKeySign>> jwt_signer =
+        (*keyset_handle)->GetPrimitive<JwtPublicKeySign>();
+    if (!jwt_signer.ok()) return jwt_signer.status();
+
+    StatusOr<std::string> token = (*jwt_signer)->SignAndEncode(*raw_jwt);
+    if (!token.ok()) return token.status();
+
+    return WriteToFile(*token, token_filename);
+  } else {  // mode == kVerify
+    // Read the token.
+    StatusOr<std::string> token = ReadFile(token_filename);
+    if (!token.ok()) return token.status();
+
+    StatusOr<JwtValidator> validator =
+        crypto::tink::JwtValidatorBuilder().ExpectAudience(audience).Build();
+    if (!validator.ok()) return validator.status();
+
+    StatusOr<std::unique_ptr<JwtPublicKeyVerify>> jwt_verifier =
+        (*keyset_handle)->GetPrimitive<JwtPublicKeyVerify>();
+    if (!jwt_verifier.ok()) return jwt_verifier.status();
+
+    return (*jwt_verifier)->VerifyAndDecode(*token, *validator).status();
   }
-  std::clog << "Writing the keyset to file " << output_filename
-            << "..." << std::endl;
-
-  WriteKeyset(**keyset_handle, output_filename);
 }
 
-// Extracts a public keyset associated with the given private keyset
-// and writes it to the output file.
-void ExtractPublicKey(absl::string_view private_keyset_filename,
-                      absl::string_view output_filename) {
-  std::clog << "Extracting a public keyset associated with the private "
-            << "keyset from file " << private_keyset_filename << "..."
-            << std::endl;
-
-  std::unique_ptr<crypto::tink::KeysetHandle> private_keyset_handle =
-      ReadKeyset(private_keyset_filename);
-
-  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
-      public_keyset_handle = private_keyset_handle->GetPublicKeysetHandle();
-  if (!public_keyset_handle.ok()) {
-    std::clog << "Getting the keyset failed: "
-              << public_keyset_handle.status().message() << std::endl;
-    exit(1);
-  }
-
-  std::clog << "Writing the keyset to file " << output_filename
-            << "..." << std::endl;
-
-  WriteKeyset(**public_keyset_handle, output_filename);
-}
-
-// Creates and signs a token with the given audience claim using the given
-// private keyset and writes the signature to the output file.
-void Sign(absl::string_view keyset_filename, absl::string_view audience,
-          absl::string_view output_filename) {
-  std::unique_ptr<crypto::tink::KeysetHandle> keyset_handle =
-      ReadKeyset(keyset_filename);
-
-  crypto::tink::util::StatusOr<std::unique_ptr<JwtPublicKeySign>>
-      jwt_public_key_sign = keyset_handle->GetPrimitive<JwtPublicKeySign>();
-  if (!jwt_public_key_sign.ok()) {
-    std::clog << "Getting JwtPublicKeySign-primitive from the factory failed: "
-              << jwt_public_key_sign.status().message() << std::endl;
-    exit(1);
-  }
-
-  std::clog << "Generating a token with audience '" << audience
-            << "' using private keyset from file " << keyset_filename << "..."
-            << std::endl;
-
-  crypto::tink::util::StatusOr<RawJwt> raw_jwt =
-      RawJwtBuilder()
-          .AddAudience(audience)
-          .SetExpiration(absl::Now() + absl::Seconds(100))
-          .Build();
-  if (!raw_jwt.ok()) {
-    std::clog << "Building RawJwt failed: " << raw_jwt.status().message()
-              << std::endl;
-    exit(1);
-  }
-
-  crypto::tink::util::StatusOr<std::string> token =
-      (*jwt_public_key_sign)->SignAndEncode(*raw_jwt);
-  if (!token.ok()) {
-    std::clog << "Error while generating the token: "
-              << token.status().message() << std::endl;
-    exit(1);
-  }
-
-  std::clog << "Writing the resulting token to file " << output_filename
-            << "..." << std::endl;
-
-  WriteFile(*token, output_filename);
-}
-
-// Verifies a token using the given public keyset and writes the result to the
-// output file.
-void Verify(absl::string_view keyset_filename,
-            absl::string_view expected_audience,
-            absl::string_view token_filename,
-            absl::string_view output_filename) {
-  std::unique_ptr<KeysetHandle> keyset_handle = ReadKeyset(keyset_filename);
-
-  crypto::tink::util::StatusOr<std::unique_ptr<JwtPublicKeyVerify>> verifier =
-      keyset_handle->GetPrimitive<crypto::tink::JwtPublicKeyVerify>();
-  if (!verifier.ok()) {
-    std::clog << "Getting JwtPublicKeyVerify-primitive from the factory "
-              << "failed: " << verifier.status().message() << std::endl;
-    exit(1);
-  }
-
-  std::clog << "Verifying token from file " << token_filename
-            << " with expected audience '" << expected_audience
-            << "' using public keyset from file " << keyset_filename << "..."
-            << std::endl;
-
-  std::string token = ReadFile(token_filename);
-
-  crypto::tink::util::StatusOr<JwtValidator> validator =
-      crypto::tink::JwtValidatorBuilder()
-          .ExpectAudience(expected_audience)
-          .Build();
-  if (!validator.ok()) {
-    std::clog << "Building validator failed: " << validator.status().message()
-              << std::endl;
-    exit(1);
-  }
-
-  std::string result;
-  crypto::tink::util::StatusOr<crypto::tink::VerifiedJwt> verified_jwt =
-      (*verifier)->VerifyAndDecode(token, *validator);
-  if (!verified_jwt.ok()) {
-    std::clog << "Error while verifying the token: "
-              << verified_jwt.status().message() << std::endl;
-    result = "invalid";
-  } else {
-    absl::Duration ttl = *verified_jwt->GetExpiration() - absl::Now();
-    std::clog << "Token is valid for " << ttl << "." << std::endl;
-    result = "valid";
-  }
-
-  std::clog << "Writing the result to file " << output_filename
-            << "..." << std::endl;
-
-  WriteFile(result, output_filename);
-}
+}  // namespace tink_cc_examples
 
 int main(int argc, char** argv) {
-  if (argc == 1) {
-    PrintUsageInfo();
-    exit(1);
-  }
+  absl::ParseCommandLine(argc, argv);
 
-  crypto::tink::util::Status status = crypto::tink::JwtSignatureRegister();
-  if (!status.ok()) {
-    std::clog << "JwtSignatureRegister() failed: " << status.message()
+  ValidateParams();
+
+  std::string mode = absl::GetFlag(FLAGS_mode);
+  std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
+  std::string audience = absl::GetFlag(FLAGS_audience);
+  std::string token_filename = absl::GetFlag(FLAGS_token_filename);
+
+  std::clog << "Using keyset in " << keyset_filename << " to ";
+  if (mode == kSign) {
+    std::clog << " generate and sign a token using audience '" << audience
+              << "'; the resulting signature is written to " << token_filename
               << std::endl;
-    exit(1);
+  } else {  // mode == kVerify
+    std::clog << " verify a token with expected audience '" << audience
+              << std::endl;
   }
 
-  std::string operation = argv[1];
-
-  if (operation == "gen-private-key") {
-    if (argc != 3) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string output_filename = argv[2];
-
-    GeneratePrivateKey(output_filename);
-  } else if (operation == "get-public-key") {
-    if (argc != 4) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string private_keyset_filename = argv[2];
-    std::string output_filename = argv[3];
-
-    ExtractPublicKey(private_keyset_filename, output_filename);
-  } else if (operation == "sign") {
-    if (argc != 5) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string keyset_filename = argv[2];
-    std::string audience = argv[3];
-    std::string output_filename = argv[4];
-
-    Sign(keyset_filename, audience, output_filename);
-  } else if (operation == "verify") {
-    if (argc != 6) {
-      PrintUsageInfo();
-      exit(1);
-    }
-
-    std::string keyset_filename = argv[2];
-    std::string audience = argv[3];
-    std::string token_filename = argv[4];
-    std::string output_filename = argv[5];
-
-    Verify(keyset_filename, audience, token_filename, output_filename);
-  } else {
-    std::clog << "Unknown operation. Supported operations are: "
-              << "gen-private-key get-public-key sign verify" << std::endl;
-    exit(1);
-  }
-
-  std::clog << "Done!" << std::endl;
-
+  CHECK_OK(tink_cc_examples::JwtCli(mode, keyset_filename, audience,
+                                    token_filename));
   return 0;
 }
+// [END jwt-example]
diff --git a/cc/examples/jwt/jwt_signature_cli_test.sh b/cc/examples/jwt/jwt_signature_cli_test.sh
index 5376edb..16999c6 100755
--- a/cc/examples/jwt/jwt_signature_cli_test.sh
+++ b/cc/examples/jwt/jwt_signature_cli_test.sh
@@ -14,124 +14,166 @@
 # limitations under the License.
 ################################################################################
 
+set -euo pipefail
 
 #############################################################################
-#### Tests for digital_signatures_cli binary.
-
-SIGNATURE_CLI="$1"
-
-PRIVATE_KEYSET_FILE="$TEST_TMPDIR/private_keyset.bin"
-PUBLIC_KEYSET_FILE="$TEST_TMPDIR/public_keyset.bin"
-TOKEN_FILE="$TEST_TMPDIR/token.bin"
-RESULT_FILE="$TEST_TMPDIR/result.txt"
-
-OTHER_PRIVATE_KEYSET_FILE="$TEST_TMPDIR/other_private_keyset.bin"
-OTHER_PUBLIC_KEYSET_FILE="$TEST_TMPDIR/other_public_keyset.bin"
-INVALID_TOKEN_FILE="$TEST_TMPDIR/invalid_token.txt"
-
-echo "invalid.token.file" > $INVALID_TOKEN_FILE
-
+# Tests for Tink C++ JWT signature example.
 #############################################################################
-#### Helper function that checks if values are equal.
 
-assert_equal() {
-  if [ "$1" == "$2" ]; then
-    echo "+++ Success: values are equal."
-  else
-    echo "--- Failure: values are different. Expected: [$1], actual: [$2]."
+: "${TEST_TMPDIR:=$(mktemp -d)}"
+
+readonly CLI="$1"
+readonly PRIVATE_KEYSET_FILE="$2"
+readonly PUBLIC_KEYSET_FILE="$3"
+readonly TOKEN_FILE="${TEST_TMPDIR}/token.json"
+readonly TEST_NAME="TinkCcExamplesJwtSignatureTest"
+
+readonly AUDIENCE="JWT audience"
+
+#######################################
+# A helper function for getting the return code of a command that may fail.
+# Temporarily disables error safety and stores return value in TEST_STATUS.
+#
+# Globals:
+#   TEST_STATUS
+# Arguments:
+#   Command to execute.
+#######################################
+test_command() {
+  set +e
+  "$@"
+  TEST_STATUS=$?
+  set -e
+}
+
+#######################################
+# Asserts that the outcome of the latest test command is 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+assert_command_succeeded() {
+  if (( TEST_STATUS != 0 )); then
+    echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
     exit 1
   fi
 }
 
-#############################################################################
-#### All good, everything should work.
-test_name="all_good"
-echo "+++ Starting test $test_name..."
+#######################################
+# Asserts that the outcome of the latest test command is not 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+assert_command_failed() {
+  if (( TEST_STATUS == 0 )); then
+      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+      exit 1
+  fi
+}
 
-#### Generate a private key and get a public key.
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
+#######################################
+# Starts a new test case; records the test case name to TEST_CASE.
+#
+# Globals:
+#   TEST_NAME
+#   TEST_CASE
+# Arguments:
+#   test_case: The name of the test case.
+#######################################
+start_test_case() {
+  TEST_CASE="$1"
+  echo "[ RUN      ] ${TEST_NAME}.${TEST_CASE}"
+}
 
-#### Sign the message.
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE audience $TOKEN_FILE || exit 1
-
-#### Verify the signature.
-$SIGNATURE_CLI verify $PUBLIC_KEYSET_FILE audience $TOKEN_FILE $RESULT_FILE || exit 1
-
-#### Check that the signature is valid.
-RESULT=$(<$RESULT_FILE)
-assert_equal "valid" "$RESULT"
+#######################################
+# Ends a test case printing a success message.
+#
+# Globals:
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+end_test_case() {
+  echo "[       OK ] ${TEST_NAME}.${TEST_CASE}"
+}
 
 #############################################################################
-#### Bad private key when getting the public key.
-test_name="get_public_key_with_bad_private_key"
-echo "+++ Starting test $test_name..."
 
-echo "abcd" >> $PRIVATE_KEYSET_FILE
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE
+start_test_case "sign_verify_all_good"
 
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
+# Sign.
+test_command "${CLI}" \
+  --mode sign \
+  --keyset_filename "${PRIVATE_KEYSET_FILE}" \
+  --audience "${AUDIENCE}" \
+  --token_filename "${TOKEN_FILE}"
+assert_command_succeeded
+
+# Verify.
+test_command "${CLI}" \
+  --mode verify \
+  --keyset_filename "${PUBLIC_KEYSET_FILE}" \
+  --audience "${AUDIENCE}" \
+  --token_filename "${TOKEN_FILE}"
+assert_command_succeeded
+
+end_test_case
 
 #############################################################################
-#### Signature verification fails because the public key is wrong.
-test_name="verify_with_different_public_key"
-echo "+++ Starting test $test_name..."
 
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI gen-private-key $OTHER_PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $OTHER_PRIVATE_KEYSET_FILE $OTHER_PUBLIC_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE audience $TOKEN_FILE || exit 1
-$SIGNATURE_CLI verify $OTHER_PUBLIC_KEYSET_FILE audience $TOKEN_FILE $RESULT_FILE || exit 1
+start_test_case "verify_fails_with_invalid_token"
 
-RESULT=$(<$RESULT_FILE)
-assert_equal "invalid" "$RESULT"
+# Sign.
+test_command "${CLI}" \
+  --mode sign \
+  --keyset_filename "${PRIVATE_KEYSET_FILE}" \
+  --audience "${AUDIENCE}" \
+  --token_filename "${TOKEN_FILE}"
+assert_command_succeeded
+
+# Invalid token.
+echo "modified" >> "${TOKEN_FILE}"
+
+# Verify.
+test_command "${CLI}" \
+  --mode verify \
+  --keyset_filename "${PUBLIC_KEYSET_FILE}" \
+  --audience "${AUDIENCE}" \
+  --token_filename "${TOKEN_FILE}"
+assert_command_failed
+
+end_test_case
 
 #############################################################################
-#### Verification fails because the audience is wrong.
-test_name="verify_with_different_message"
-echo "+++ Starting test $test_name..."
 
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE audience $TOKEN_FILE || exit 1
-$SIGNATURE_CLI verify $PUBLIC_KEYSET_FILE other_audience $TOKEN_FILE $RESULT_FILE || exit 1
+start_test_case "verify_fails_with_invalid_audience"
 
-RESULT=$(<$RESULT_FILE)
-assert_equal "invalid" "$RESULT"
+# Sign.
+test_command "${CLI}" \
+  --mode sign \
+  --keyset_filename "${PRIVATE_KEYSET_FILE}" \
+  --audience "${AUDIENCE}" \
+  --token_filename "${TOKEN_FILE}"
+assert_command_succeeded
 
-#############################################################################
-#### Verification fails because the token ist invalid.
-test_name="verify_with_different_message"
-echo "+++ Starting test $test_name..."
+# Modify audience.
+readonly INVALID_AUDIENCE="invalid audience"
 
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$SIGNATURE_CLI verify $PUBLIC_KEYSET_FILE audience $INVALID_TOKEN_FILE $RESULT_FILE || exit 1
+# Verify.
+test_command "${CLI}" \
+  --mode verify \
+  --keyset_filename "${PUBLIC_KEYSET_FILE}" \
+  --audience "${INVALID_AUDIENCE}" \
+  --token_filename "${TOKEN_FILE}"
+assert_command_failed
 
-RESULT=$(<$RESULT_FILE)
-assert_equal "invalid" "$RESULT"
+end_test_case
 
-#############################################################################
-#### Signing fails because we use the wrong key type.
-test_name="sign_with_wrong_key"
-echo "+++ Starting test $test_name..."
-
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI get-public-key $PRIVATE_KEYSET_FILE $PUBLIC_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PUBLIC_KEYSET_FILE audience $TOKEN_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
-
-#############################################################################
-#### Verification fails because we use the wrong key type.
-test_name="verify_with_wrong_key"
-echo "+++ Starting test $test_name..."
-
-$SIGNATURE_CLI gen-private-key $PRIVATE_KEYSET_FILE || exit 1
-$SIGNATURE_CLI sign $PRIVATE_KEYSET_FILE audience $TOKEN_FILE || exit 1
-$SIGNATURE_CLI verify $PRIVATE_KEYSET_FILE audience $TOKEN_FILE $RESULT_FILE
-
-EXIT_VALUE="$?"
-assert_equal 1 "$EXIT_VALUE"
diff --git a/cc/examples/jwt/jwt_signature_private_keyset.json b/cc/examples/jwt/jwt_signature_private_keyset.json
new file mode 100644
index 0000000..be5f4be
--- /dev/null
+++ b/cc/examples/jwt/jwt_signature_private_keyset.json
@@ -0,0 +1,15 @@
+{
+  "primaryKeyId": 185188009,
+  "key": [
+    {
+      "keyData": {
+        "typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey",
+        "value": "EosCEAEagQIAs9iifvWObNLbP+x7zupVIYTdHKba4VFgJEnnGtIII21R+KGddTdvNGAokd4GPrFk1GDPitHrAAoW1+NWrafsEUi2J9Sy3uwEyarsKDggewoBCNg2fcWAiZXplPjUyTlhrLvTuyrcL/mGPy+ib7bdmov+D2EP+rKUH6/ydtQGiyHRR3uurTUWfrMD1/6WaBVfngpy5Pxs2nuHXRmBHQKWmPfvErgr4abdjhKDaWIuxzSise1CSAbiWTNcxpIuFYZgPjgQzpqeh93LUXIX9YJds/bhHtXqRdxk6yTisloHOZETItK/rHCCE25dLkkaJ2Li7AtnJdBc6tEUNiuFj2JCjSIDAQABGoACT2lWxwySaQbp/N3lBUZ/dJ+AKsiaWWdfNmbTfwpCwbHhwhFKv5lMpynWgCIzS7d0uDpPKhLq20eZMpaVjXRaTn92vzuyB7DbpFiukkvGO839CvS9iueMjDP/weHlwzxtHqKJKVoRg7WAS6Iy7XUngLhT5GKNdbsooJ1GSKXyhbgWyMcspKSQe4lZXUntVMK5z4iLNmcQwsBp8yM55mZra13TXowob/E/wd+tGiABCn6CDt8G1gXzWDaoF2tt6WhSGZbXUVGagmoea/BWeAuJyKSSi5h+uPpc5SPhGvyKfSEVaCs2QeM7/UIXhzAcx2j/VqySb6y9EbSiJfy8vr49QSKBAQD+AbFCGHd9kZ5LIQrfe9caOxS9pQPdFkBJESw0C3x2uBIg8awiQsuVXMeEgyGLyWBZoi2x98OMSR9OzCuSLtb7Nv0Wqn0LUj4WPRdmg//uLeD3O2rcVRIR4db/B8WvXnK2uQsqwGDyh4BepGvprXQPYMX2uwnBGL2ccS2De53HJSqBAQC1QfOi4egjmlmXqJLpISUSN1NixkIi8EJHaZZ0YrbaRrEyiJczthcazNHFt6gzgOcosFaKaZeqps4Tet+5NgS7eh7RzLQ2+cfT4ewpT2ExJ4NsOy8XDqD6GRjliLxjGAoUf24s3B+3LLACPiQjeeZGJP0ivh384WabyXXxRgHFSTKBAQChl7gKIYCbHPHEQAAnzyQ4Js/6GinMFCTPlyI09f23lUDLPpRQs4fKvNydO8Myp+ko/NjvOH1qGPbW7WLmu+++n+wA6HNmqWqgQTtK170Q7JULE/zWsTQutitN0cb82yxFfJFTIFJM2NFc5GNWpSeJxPoMDk+VTcUK6qGW3SSyFTqBAQCeaPFA3SZAV1kNjio2zNzVOr0JijOqzUdfmgv/03Xy9e1POMjMTMuMhIygu42o1XMwwEwh037Vicp4g96aw3cHUgc1XC30DgByUPRQdit/BgV5xY+2GvbdHKoBkKrz/8Jvf58OXaLqN4frrdtvlc2GaDVC89zJcUR3ym3lW0WY4UKBAQD6MCruwXaxXJMxjtlH1YT5ow4R5neeiswNfGj4Ta/WbWyiVA60zpdNbGqH+etmiHY8+aBb/H4O9+JhOcBtlMLN4UlK1jg8wPSemZjsIPiUZXHkeIUa2RTUSz90wgz7aOqC0lYsLLFaJNWs54fC9LpZ0JzoqYDI8iDPnlE7xaag9g==",
+        "keyMaterialType": "ASYMMETRIC_PRIVATE"
+      },
+      "status": "ENABLED",
+      "keyId": 185188009,
+      "outputPrefixType": "TINK"
+    }
+  ]
+}
diff --git a/cc/examples/jwt/jwt_signature_public_keyset.json b/cc/examples/jwt/jwt_signature_public_keyset.json
new file mode 100644
index 0000000..d6fa045
--- /dev/null
+++ b/cc/examples/jwt/jwt_signature_public_keyset.json
@@ -0,0 +1,15 @@
+{
+  "primaryKeyId": 185188009,
+  "key": [
+    {
+      "keyData": {
+        "typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey",
+        "value": "EAEagQIAs9iifvWObNLbP+x7zupVIYTdHKba4VFgJEnnGtIII21R+KGddTdvNGAokd4GPrFk1GDPitHrAAoW1+NWrafsEUi2J9Sy3uwEyarsKDggewoBCNg2fcWAiZXplPjUyTlhrLvTuyrcL/mGPy+ib7bdmov+D2EP+rKUH6/ydtQGiyHRR3uurTUWfrMD1/6WaBVfngpy5Pxs2nuHXRmBHQKWmPfvErgr4abdjhKDaWIuxzSise1CSAbiWTNcxpIuFYZgPjgQzpqeh93LUXIX9YJds/bhHtXqRdxk6yTisloHOZETItK/rHCCE25dLkkaJ2Li7AtnJdBc6tEUNiuFj2JCjSIDAQAB",
+        "keyMaterialType": "ASYMMETRIC_PUBLIC"
+      },
+      "status": "ENABLED",
+      "keyId": 185188009,
+      "outputPrefixType": "TINK"
+    }
+  ]
+}
diff --git a/cc/examples/jwt/util.cc b/cc/examples/jwt/util.cc
deleted file mode 100644
index a40266b..0000000
--- a/cc/examples/jwt/util.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// 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
-//
-//     http://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 "jwt/util.h"
-
-#include <fstream>
-#include <iostream>
-#include <string>
-#include <utility>
-
-#include "absl/strings/string_view.h"
-#include "tink/binary_keyset_reader.h"
-#include "tink/binary_keyset_writer.h"
-#include "tink/cleartext_keyset_handle.h"
-#include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
-#include "tink/keyset_writer.h"
-#include "tink/util/status.h"
-
-using crypto::tink::BinaryKeysetReader;
-using crypto::tink::BinaryKeysetWriter;
-using crypto::tink::CleartextKeysetHandle;
-using crypto::tink::KeysetHandle;
-using crypto::tink::KeysetReader;
-using crypto::tink::KeysetWriter;
-
-namespace {
-
-// Returns a BinaryKeysetReader that reads from the specified file.
-// In case of errors writes a log message and aborts.
-std::unique_ptr<KeysetReader> GetBinaryKeysetReader(
-    absl::string_view filename) {
-  std::unique_ptr<std::ifstream> keyset_stream(new std::ifstream());
-  keyset_stream->open(std::string(filename), std::ifstream::in);
-  crypto::tink::util::StatusOr<std::unique_ptr<KeysetReader>> keyset_reader =
-      BinaryKeysetReader::New(std::move(keyset_stream));
-  if (!keyset_reader.ok()) {
-    std::clog << "Creation of the BinaryKeysetReader failed: "
-              << keyset_reader.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(*keyset_reader);
-}
-
-// Returns a BinaryKeysetWriter that writes from the specified file.
-// In case of errors writes a log message and aborts.
-std::unique_ptr<KeysetWriter> GetBinaryKeysetWriter(
-    absl::string_view filename) {
-  std::unique_ptr<std::ofstream> keyset_stream(new std::ofstream());
-  keyset_stream->open(std::string(filename), std::ofstream::out);
-  crypto::tink::util::StatusOr<std::unique_ptr<BinaryKeysetWriter>>
-      keyset_writer = BinaryKeysetWriter::New(std::move(keyset_stream));
-  if (!keyset_writer.ok()) {
-    std::clog << "Creation of the BinaryKeysetWriter failed: "
-              << keyset_writer.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(*keyset_writer);
-}
-
-}  // namespace
-
-std::unique_ptr<KeysetHandle> ReadKeyset(absl::string_view filename) {
-  std::unique_ptr<crypto::tink::KeysetReader> keyset_reader =
-      GetBinaryKeysetReader(filename);
-  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
-      CleartextKeysetHandle::Read(std::move(keyset_reader));
-  if (!keyset_handle.ok()) {
-    std::clog << "Reading the keyset failed: "
-              << keyset_handle.status().message() << std::endl;
-    exit(1);
-  }
-  return std::move(*keyset_handle);
-}
-
-void WriteKeyset(const crypto::tink::KeysetHandle& keyset_handle,
-                 absl::string_view filename) {
-  std::unique_ptr<crypto::tink::KeysetWriter> keyset_writer =
-      GetBinaryKeysetWriter(filename);
-  auto status =
-      CleartextKeysetHandle::Write(keyset_writer.get(), keyset_handle);
-  if (!status.ok()) {
-    std::clog << "Writing the keyset failed: " << status.message() << std::endl;
-    exit(1);
-  }
-}
-
-std::string ReadFile(absl::string_view filename) {
-  std::ifstream input_stream;
-  input_stream.open(std::string(filename), std::ifstream::in);
-  if (!input_stream.is_open()) {
-    std::clog << "Error opening input file " << std::string(filename)
-              << std::endl;
-    exit(1);
-  }
-  std::stringstream input;
-  input << input_stream.rdbuf();
-  input_stream.close();
-  return input.str();
-}
-
-void WriteFile(absl::string_view output, absl::string_view filename) {
-  std::ofstream output_stream(std::string(filename),
-                              std::ofstream::out | std::ofstream::binary);
-  if (!output_stream.is_open()) {
-    std::clog << "Error opening output file " << std::string(filename)
-              << std::endl;
-    exit(1);
-  }
-  output_stream << std::string(output);
-  output_stream.close();
-}
diff --git a/cc/examples/jwt/util.h b/cc/examples/jwt/util.h
deleted file mode 100644
index 9427faa..0000000
--- a/cc/examples/jwt/util.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_EXAMPLES_JWT_UTIL_H_
-#define TINK_EXAMPLES_JWT_UTIL_H_
-
-#include <fstream>
-#include <iostream>
-#include <string>
-
-#include "absl/strings/string_view.h"
-#include "tink/keyset_handle.h"
-
-// Helper functions for JWT Signature CLI
-
-// Reads a keyset from the specified file.
-// In case of errors writes a log message and aborts.
-std::unique_ptr<crypto::tink::KeysetHandle> ReadKeyset(
-    absl::string_view filename);
-
-// Writes the keyset to the specified file.
-// In case of errors writes a log message and aborts.
-void WriteKeyset(const crypto::tink::KeysetHandle& keyset_handle,
-                 absl::string_view filename);
-
-// Reads the specified file and returns the contents as a string.
-// In case of errors writes a log message and aborts.
-std::string ReadFile(absl::string_view filename);
-
-// Writes the given 'output' to the specified file.
-// In case of errors writes a log message and aborts.
-void WriteFile(absl::string_view output, absl::string_view filename);
-
-#endif  // TINK_EXAMPLES_JWT_UTIL_H_
diff --git a/cc/examples/key_derivation/BUILD.bazel b/cc/examples/key_derivation/BUILD.bazel
new file mode 100644
index 0000000..9f0bcd6
--- /dev/null
+++ b/cc/examples/key_derivation/BUILD.bazel
@@ -0,0 +1,34 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+cc_binary(
+    name = "key_derivation_cli",
+    srcs = ["key_derivation_cli.cc"],
+    deps = [
+        "//util",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/log:check",
+        "@tink_cc//:aead",
+        "@tink_cc//:keyset_handle",
+        "@tink_cc//aead:aead_config",
+        "@tink_cc//keyderivation:key_derivation_config",
+        "@tink_cc//keyderivation:keyset_deriver",
+        "@tink_cc//util:status",
+    ],
+)
+
+sh_test(
+    name = "key_derivation_cli_test",
+    size = "small",
+    srcs = ["key_derivation_cli_test.sh"],
+    args = [
+        "$(rootpath :key_derivation_cli)",
+        "$(rootpaths :keyset.json)",
+    ],
+    data = [
+        ":key_derivation_cli",
+        ":keyset.json",
+    ],
+)
diff --git a/cc/examples/key_derivation/CMakeLists.txt b/cc/examples/key_derivation/CMakeLists.txt
new file mode 100644
index 0000000..d81719b
--- /dev/null
+++ b/cc/examples/key_derivation/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_executable(key_derivation_cli key_derivation_cli.cc)
+target_include_directories(key_derivation_cli PUBLIC
+  "${CMAKE_CURRENT_SOURCE_DIR}"
+  "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(key_derivation_cli
+  tink::static
+  absl::check
+  absl::flags_parse
+  util)
+
+add_test(
+  NAME key_derivation_cli_test
+  COMMAND "${BASH_PROGRAM}"
+    "${CMAKE_CURRENT_SOURCE_DIR}/key_derivation_cli_test.sh"
+    "${CMAKE_CURRENT_BINARY_DIR}/key_derivation_cli"
+    "${CMAKE_CURRENT_SOURCE_DIR}/keyset.json"
diff --git a/cc/examples/key_derivation/key_derivation_cli.cc b/cc/examples/key_derivation/key_derivation_cli.cc
new file mode 100644
index 0000000..bca4ad1
--- /dev/null
+++ b/cc/examples/key_derivation/key_derivation_cli.cc
@@ -0,0 +1,143 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+// [START key-derivation-example]
+// A command-line utility for testing Tink Key Derivation.
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/log/check.h"
+#include "tink/aead.h"
+#include "tink/aead/aead_config.h"
+#include "util/util.h"
+#include "tink/keyderivation/key_derivation_config.h"
+#include "tink/keyderivation/keyset_deriver.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/status.h"
+
+ABSL_FLAG(std::string, keyset_filename, "",
+          "File in JSON format containing keyset that derives an AEAD keyset");
+ABSL_FLAG(std::string, salt_filename, "", "Salt file name");
+ABSL_FLAG(std::string, derived_keyset_filename, "", "Derived keyset file name");
+
+namespace {
+
+using ::crypto::tink::Aead;
+using ::crypto::tink::AeadConfig;
+using ::crypto::tink::KeyDerivationConfig;
+using ::crypto::tink::KeysetDeriver;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::util::OkStatus;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+
+void ValidateParams() {
+  // [START_EXCLUDE]
+  CHECK(!absl::GetFlag(FLAGS_keyset_filename).empty())
+      << "Keyset file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_salt_filename).empty())
+      << "Input file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_derived_keyset_filename).empty())
+      << "Output file must be specified";
+  // [END_EXCLUDE]
+}
+
+// Verifies `handle` contains a valid AEAD primitive.
+Status VerifyDerivedAeadKeyset(const KeysetHandle& handle) {
+  // [START_EXCLUDE]
+  StatusOr<std::unique_ptr<Aead>> aead = handle.GetPrimitive<Aead>();
+  if (!aead.ok()) return aead.status();
+
+  std::string plaintext = "plaintext";
+  std::string ad = "ad";
+  StatusOr<std::string> ciphertext = (*aead)->Encrypt(plaintext, ad);
+  if (!ciphertext.ok()) return ciphertext.status();
+
+  StatusOr<std::string> got = (*aead)->Decrypt(*ciphertext, ad);
+  if (!got.ok()) return got.status();
+
+  if (*got != plaintext) {
+    return Status(
+        absl::StatusCode::kInternal,
+        "AEAD obtained from derived keyset failed to decrypt correctly");
+  }
+  return OkStatus();
+  // [END_EXCLUDE]
+}
+
+}  // namespace
+
+namespace tink_cc_examples {
+
+Status KeyDerivationCli(const std::string& keyset_filename,
+                        const std::string& salt_filename,
+                        const std::string& derived_keyset_filename) {
+  Status result = KeyDerivationConfig::Register();
+  if (!result.ok()) return result;
+  result = AeadConfig::Register();
+  if (!result.ok()) return result;
+
+  // Read keyset from file.
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      ReadJsonCleartextKeyset(keyset_filename);
+  if (!keyset_handle.ok()) return keyset_handle.status();
+
+  // Get the primitive.
+  StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      (*keyset_handle)->GetPrimitive<KeysetDeriver>();
+  if (!deriver.ok()) return deriver.status();
+
+  // Read the salt.
+  StatusOr<std::string> salt_file_content = ReadFile(salt_filename);
+  if (!salt_file_content.ok()) return salt_file_content.status();
+
+  // Derive new keyset.
+  StatusOr<std::unique_ptr<KeysetHandle>> derived_handle =
+      (*deriver)->DeriveKeyset(*salt_file_content);
+  if (!derived_handle.ok()) return derived_handle.status();
+
+  Status status = VerifyDerivedAeadKeyset(**derived_handle);
+  if (!status.ok()) return status;
+
+  return WriteJsonCleartextKeyset(derived_keyset_filename, **derived_handle);
+}
+
+}  // namespace tink_cc_examples
+
+int main(int argc, char** argv) {
+  absl::ParseCommandLine(argc, argv);
+
+  ValidateParams();
+
+  std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
+  std::string salt_filename = absl::GetFlag(FLAGS_salt_filename);
+  std::string derived_keyset_filename =
+      absl::GetFlag(FLAGS_derived_keyset_filename);
+
+  std::clog << "Using keyset from file " << keyset_filename
+            << " to derive a new AEAD keyset with the salt in file "
+            << salt_filename << "." << std::endl;
+  std::clog << "The resulting derived keyset will be written to "
+            << derived_keyset_filename << "." << std::endl;
+
+  CHECK_OK(tink_cc_examples::KeyDerivationCli(keyset_filename, salt_filename,
+                                              derived_keyset_filename));
+  return 0;
+}
+// [END key-derivation-example]
diff --git a/cc/examples/key_derivation/key_derivation_cli_test.sh b/cc/examples/key_derivation/key_derivation_cli_test.sh
new file mode 100755
index 0000000..c59e7a7
--- /dev/null
+++ b/cc/examples/key_derivation/key_derivation_cli_test.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+# Copyright 2023 Google LLC
+#
+# 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
+#
+#      http://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.
+################################################################################
+
+set -euo pipefail
+
+#############################################################################
+# Tests for Tink C++ Key Derivation example.
+#############################################################################
+
+: "${TEST_TMPDIR:=$(mktemp -d)}"
+
+readonly CLI="$1"
+readonly KEYSET_FILE="$2"
+readonly SALT_FILE="${TEST_TMPDIR}/salt.txt"
+readonly DERIVED_KEYSET_FILE="${TEST_TMPDIR}/derived_keyset.json"
+readonly TEST_NAME="TinkCcExamplesKeyDerivationTest"
+
+echo "This is the salt used to derive keys." > "${SALT_FILE}"
+
+#######################################
+# A helper function for getting the return code of a command that may fail.
+# Temporarily disables error safety and stores return value in TEST_STATUS.
+#
+# Globals:
+#   TEST_STATUS
+# Arguments:
+#   Command to execute.
+#######################################
+test_command() {
+  set +e
+  "$@"
+  TEST_STATUS=$?
+  set -e
+}
+
+#######################################
+# Asserts that the outcome of the latest test command is 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+assert_command_succeeded() {
+  if (( TEST_STATUS != 0 )); then
+    echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+    exit 1
+  fi
+}
+
+#######################################
+# Asserts that the outcome of the latest test command is not 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+assert_command_failed() {
+  if (( TEST_STATUS == 0 )); then
+      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+      exit 1
+  fi
+}
+
+#######################################
+# Starts a new test case; records the test case name to TEST_CASE.
+#
+# Globals:
+#   TEST_NAME
+#   TEST_CASE
+# Arguments:
+#   test_case: The name of the test case.
+#######################################
+start_test_case() {
+  TEST_CASE="$1"
+  echo "[ RUN      ] ${TEST_NAME}.${TEST_CASE}"
+}
+
+#######################################
+# Ends a test case printing a success message.
+#
+# Globals:
+#   TEST_NAME
+#   TEST_CASE
+#######################################
+end_test_case() {
+  echo "[       OK ] ${TEST_NAME}.${TEST_CASE}"
+}
+
+#############################################################################
+
+start_test_case "derive_key"
+
+test_command "${CLI}" \
+  --keyset_filename "${KEYSET_FILE}" \
+  --salt_filename "${SALT_FILE}" \
+  --derived_keyset_filename "${DERIVED_KEYSET_FILE}"
+assert_command_succeeded
+
+end_test_case
diff --git a/cc/examples/key_derivation/keyset.json b/cc/examples/key_derivation/keyset.json
new file mode 100644
index 0000000..8703b1a
--- /dev/null
+++ b/cc/examples/key_derivation/keyset.json
@@ -0,0 +1,15 @@
+{
+  "primaryKeyId": 1746379508,
+  "key": [
+    {
+      "keyData": {
+        "typeUrl": "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey",
+        "value": "El0KMXR5cGUuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLmNyeXB0by50aW5rLkhrZGZQcmZLZXkSJhICCAMaIHq3492RGOyzGsJTQh6Xi6noTDSrPQxULHuBqB10zMUCGAEaOgo4CjB0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5jcnlwdG8udGluay5BZXNHY21LZXkSAhAQGAE=",
+        "keyMaterialType": "SYMMETRIC"
+      },
+      "status": "ENABLED",
+      "keyId": 1746379508,
+      "outputPrefixType": "TINK"
+    }
+  ]
+}
diff --git a/cc/examples/mac/BUILD.bazel b/cc/examples/mac/BUILD.bazel
index 456fd7c..7d3b7c1 100644
--- a/cc/examples/mac/BUILD.bazel
+++ b/cc/examples/mac/BUILD.bazel
@@ -11,17 +11,14 @@
     name = "mac_cli",
     srcs = ["mac_cli.cc"],
     deps = [
+        "//util",
         "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/flags:parse",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/log:check",
         "@com_google_absl//absl/strings",
         "@tink_cc//:cleartext_keyset_handle",
-        "@tink_cc//:json_keyset_reader",
         "@tink_cc//:keyset_handle",
-        "@tink_cc//:keyset_reader",
         "@tink_cc//:mac",
-        "@tink_cc//config:tink_config",
         "@tink_cc//mac:mac_config",
         "@tink_cc//util:status",
     ],
diff --git a/cc/examples/mac/CMakeLists.txt b/cc/examples/mac/CMakeLists.txt
new file mode 100644
index 0000000..194d7d6
--- /dev/null
+++ b/cc/examples/mac/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_executable(mac_cli mac_cli.cc)
+target_include_directories(mac_cli PUBLIC
+  "${CMAKE_CURRENT_SOURCE_DIR}"
+  "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(mac_cli
+  tink::static
+  absl::check
+  absl::flags_parse
+  util)
+
+add_test(
+  NAME mac_cli_test
+  COMMAND "${BASH_PROGRAM}"
+    "${CMAKE_CURRENT_SOURCE_DIR}/mac_cli_test.sh"
+    "${CMAKE_CURRENT_BINARY_DIR}/mac_cli"
+    "${CMAKE_CURRENT_SOURCE_DIR}/mac_test_keyset.json")
diff --git a/cc/examples/mac/mac_cli.cc b/cc/examples/mac/mac_cli.cc
index 10cd06d..3a5407f 100644
--- a/cc/examples/mac/mac_cli.cc
+++ b/cc/examples/mac/mac_cli.cc
@@ -14,25 +14,22 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 // [START mac-example]
-// A command-line utility for testing Tink MAC.
+// A command-line utility for showcasing using the Tink MAC primitive.
 
 #include <fstream>
 #include <iostream>
 #include <memory>
+#include <ostream>
 #include <sstream>
 #include <string>
 #include <utility>
 
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
-#include "absl/memory/memory.h"
-#include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
+#include "absl/log/check.h"
 #include "absl/strings/string_view.h"
-#include "tink/cleartext_keyset_handle.h"
-#include "tink/json_keyset_reader.h"
+#include "util/util.h"
 #include "tink/keyset_handle.h"
-#include "tink/keyset_reader.h"
 #include "tink/mac.h"
 #include "tink/mac/mac_config.h"
 #include "tink/util/status.h"
@@ -44,10 +41,7 @@
 
 namespace {
 
-using ::crypto::tink::CleartextKeysetHandle;
-using ::crypto::tink::JsonKeysetReader;
 using ::crypto::tink::KeysetHandle;
-using ::crypto::tink::KeysetReader;
 using ::crypto::tink::Mac;
 using ::crypto::tink::MacConfig;
 using ::crypto::tink::util::Status;
@@ -56,151 +50,89 @@
 constexpr absl::string_view kCompute = "compute";
 constexpr absl::string_view kVerify = "verify";
 
-// Creates a KeysetReader that reads a JSON-formatted keyset
-// from the given file.
-StatusOr<std::unique_ptr<KeysetReader>> GetJsonKeysetReader(
-    const std::string& filename) {
-  std::clog << "Creating a JsonKeysetReader...\n";
-  auto key_input_stream = absl::make_unique<std::ifstream>();
-  key_input_stream->open(filename, std::ifstream::in);
-  return JsonKeysetReader::New(std::move(key_input_stream));
-}
-
-// Creates a KeysetHandle that for a keyset read from the given file,
-// which is expected to contain a JSON-formatted keyset.
-StatusOr<std::unique_ptr<KeysetHandle>> ReadKeyset(
-    const std::string& filename) {
-  StatusOr<std::unique_ptr<KeysetReader>> keyset_reader =
-      GetJsonKeysetReader(filename);
-  if (!keyset_reader.ok()) {
-    return keyset_reader.status();
-  }
-  return CleartextKeysetHandle::Read(*std::move(keyset_reader));
-}
-
-// Reads `filename` and returns the read content as a string, or an error status
-// if the file does not exist.
-StatusOr<std::string> Read(const std::string& filename) {
-  std::clog << "Reading the input...\n";
-  std::ifstream input_stream;
-  input_stream.open(filename, std::ifstream::in);
-  if (!input_stream.is_open()) {
-    return Status(absl::StatusCode::kInternal,
-                  absl::StrCat("Error opening input file ", filename));
-  }
-  std::stringstream input;
-  input << input_stream.rdbuf();
-  return input.str();
-}
-
-// Writes the given `data_to_write` to the specified file `filename`.
-Status Write(const std::string& data_to_write, const std::string& filename) {
-  std::clog << "Writing the output...\n";
-  std::ofstream output_stream(filename,
-                              std::ofstream::out | std::ofstream::binary);
-  if (!output_stream.is_open()) {
-    return Status(absl::StatusCode::kInternal,
-                  absl::StrCat("Error opening output file ", filename));
-  }
-  output_stream << data_to_write;
-  return crypto::tink::util::OkStatus();
+void ValidateParams() {
+  // [START_EXCLUDE]
+  CHECK(absl::GetFlag(FLAGS_mode) == kCompute ||
+        absl::GetFlag(FLAGS_mode) == kVerify)
+      << "Invalid mode; must be `" << kCompute << "` or `" << kVerify << "`";
+  CHECK(!absl::GetFlag(FLAGS_keyset_filename).empty())
+      << "Keyset file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_data_filename).empty())
+      << "Data file must be specified";
+  CHECK(!absl::GetFlag(FLAGS_tag_filename).empty())
+      << "Tag file must be specified";
+  // [END_EXCLUDE]
 }
 
 }  // namespace
 
+namespace tink_cc_examples {
+
+// MAC example CLI implementation.
+Status MacCli(absl::string_view mode, const std::string keyset_filename,
+              const std::string& data_filename,
+              const std::string& tag_filename) {
+  Status result = MacConfig::Register();
+  if (!result.ok()) return result;
+
+  // Read the keyset from file.
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
+      ReadJsonCleartextKeyset(keyset_filename);
+  if (!keyset_handle.ok()) return keyset_handle.status();
+
+  // Get the primitive.
+  StatusOr<std::unique_ptr<Mac>> mac_primitive =
+      (*keyset_handle)->GetPrimitive<Mac>();
+  if (!mac_primitive.ok()) return mac_primitive.status();
+
+  // Read the input.
+  StatusOr<std::string> data_file_content = ReadFile(data_filename);
+  if (!data_file_content.ok()) return data_file_content.status();
+
+  std::string output;
+  if (mode == kCompute) {
+    // Compute authentication tag.
+    StatusOr<std::string> compute_result =
+        (*mac_primitive)->ComputeMac(*data_file_content);
+    if (!compute_result.ok()) return compute_result.status();
+    // Write out the authentication tag to tag file.
+    return WriteToFile(*compute_result, tag_filename);
+  } else {  // operation == kVerify.
+    // Read the authentication tag from tag file.
+    StatusOr<std::string> tag_result = ReadFile(tag_filename);
+    if (!tag_result.ok()) {
+      std::cerr << tag_result.status().message() << std::endl;
+      exit(1);
+    }
+    // Verify authentication tag.
+    Status verify_result =
+        (*mac_primitive)->VerifyMac(*tag_result, *data_file_content);
+    if (verify_result.ok()) std::clog << "Verification succeeded!" << std::endl;
+    return verify_result;
+  }
+}
+
+}  // namespace tink_cc_examples
+
 int main(int argc, char** argv) {
   absl::ParseCommandLine(argc, argv);
 
+  ValidateParams();
+
   std::string mode = absl::GetFlag(FLAGS_mode);
   std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
   std::string data_filename = absl::GetFlag(FLAGS_data_filename);
   std::string tag_filename = absl::GetFlag(FLAGS_tag_filename);
 
-  if (mode.empty()) {
-    std::cerr << "Mode must be specified with --mode=<" << kCompute << "|"
-              << kVerify << ">." << std::endl;
-    exit(1);
-  }
-
-  if (mode != kCompute && mode != kVerify) {
-    std::cerr << "Unknown mode '" << mode << "'; "
-              << "Expected either " << kCompute << " or " << kVerify << "."
-              << std::endl;
-    exit(1);
-  }
-
-  const std::string tag_file_action =
-      (mode == kCompute) ? "written to" : "read from";
   std::clog << "Using keyset from file '" << keyset_filename << "' to " << mode
             << " authentication tag from file '" << tag_filename
             << "' for data file '" << data_filename << "'." << std::endl;
-  std::clog << "The tag will be " << tag_file_action << " file '"
+  std::clog << "The tag will be "
+            << ((mode == kCompute) ? "written to" : "read from") << " file '"
             << tag_filename << "'." << std::endl;
 
-  Status result = MacConfig::Register();
-  if (!result.ok()) {
-    std::cerr << result.message() << std::endl;
-    exit(1);
-  }
-
-  // Read the keyset from file.
-  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
-      ReadKeyset(keyset_filename);
-  if (!keyset_handle.ok()) {
-    std::cerr << keyset_handle.status().message() << std::endl;
-    exit(1);
-  }
-
-  // Get the primitive.
-  StatusOr<std::unique_ptr<Mac>> mac_primitive =
-      (*keyset_handle)->GetPrimitive<Mac>();
-  if (!mac_primitive.ok()) {
-    std::cerr << mac_primitive.status().message() << std::endl;
-    exit(1);
-  }
-
-  // Read the input.
-  StatusOr<std::string> data_file_content = Read(data_filename);
-  if (!data_file_content.ok()) {
-    std::cerr << data_file_content.status().message() << std::endl;
-    exit(1);
-  }
-
-  std::string output;
-  if (mode == kCompute) {
-    // Compute authentication tag.
-    std::clog << "Computing tag...\n";
-    StatusOr<std::string> compute_result =
-        (*mac_primitive)->ComputeMac(*data_file_content);
-    if (!compute_result.ok()) {
-      std::cerr << compute_result.status().message() << std::endl;
-      exit(1);
-    }
-    // Write out the authentication tag to tag file.
-    Status write_result = Write(*compute_result, tag_filename);
-    if (!write_result.ok()) {
-      std::cerr << write_result.message() << std::endl;
-      exit(1);
-    }
-  } else {  // operation == kVerify.
-    // Read the authentication tag from tag file.
-    StatusOr<std::string> tag_result = Read(tag_filename);
-    if (!tag_result.ok()) {
-      std::cerr << tag_result.status().message() << std::endl;
-      exit(1);
-    }
-    // Verify authentication tag.
-    std::clog << "Verifying tag...\n";
-    Status verify_result =
-        (*mac_primitive)->VerifyMac(*tag_result, *data_file_content);
-    if (!verify_result.ok()) {
-      std::cerr << verify_result.message() << std::endl;
-      exit(1);
-    }
-    std::clog << "verification succeeded" << std::endl;
-  }
-
-  std::clog << "All done." << std::endl;
+  CHECK_OK(tink_cc_examples::MacCli(mode, keyset_filename, data_filename,
+                                    tag_filename));
   return 0;
 }
 // [END mac-example]
diff --git a/cc/examples/mac/mac_cli_test.sh b/cc/examples/mac/mac_cli_test.sh
index c4600c5..34f88f3 100755
--- a/cc/examples/mac/mac_cli_test.sh
+++ b/cc/examples/mac/mac_cli_test.sh
@@ -20,6 +20,8 @@
 # Tests for Tink CC MAC.
 #############################################################################
 
+: "${TEST_TMPDIR:=$(mktemp -d)}"
+
 readonly CLI="$1"
 readonly KEYSET_FILE="$2"
 readonly DATA_FILE="${TEST_TMPDIR}/example_data.txt"
@@ -44,7 +46,7 @@
 }
 
 #######################################
-# Asserts that the outcome of the latest test command was the expected one.
+# Asserts that the outcome of the latest test command is 0.
 #
 # If not, it terminates the test execution.
 #
@@ -52,23 +54,29 @@
 #   TEST_STATUS
 #   TEST_NAME
 #   TEST_CASE
-# Arguments:
-#   expected_outcome: The expected outcome.
 #######################################
-_assert_test_command_outcome() {
-  expected_outcome="$1"
-  if (( TEST_STATUS != expected_outcome )); then
-      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
-      exit 1
+assert_command_succeeded() {
+  if (( TEST_STATUS != 0 )); then
+    echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+    exit 1
   fi
 }
 
-assert_command_succeeded() {
-  _assert_test_command_outcome 0
-}
-
+#######################################
+# Asserts that the outcome of the latest test command is not 0.
+#
+# If not, it terminates the test execution.
+#
+# Globals:
+#   TEST_STATUS
+#   TEST_NAME
+#   TEST_CASE
+#######################################
 assert_command_failed() {
-  _assert_test_command_outcome 1
+  if (( TEST_STATUS == 0 )); then
+      echo "[   FAILED ] ${TEST_NAME}.${TEST_CASE}"
+      exit 1
+  fi
 }
 
 #######################################
diff --git a/cc/examples/mac/mac_test_keyset.json b/cc/examples/mac/mac_test_keyset.json
index d4ef7e1..b40e989 100644
--- a/cc/examples/mac/mac_test_keyset.json
+++ b/cc/examples/mac/mac_test_keyset.json
@@ -1,13 +1,15 @@
 {
     "primaryKeyId": 691856985,
-    "key": [{
+    "key": [
+      {
         "keyData": {
-            "typeUrl": "type.googleapis.com/google.crypto.tink.HmacKey",
-            "keyMaterialType": "SYMMETRIC",
-            "value": "EgQIAxAgGiDZsmkTufMG/XlKlk9m7bqxustjUPT2YULEVm8mOp2mSA\u003d\u003d"
+          "typeUrl": "type.googleapis.com/google.crypto.tink.HmacKey",
+          "keyMaterialType": "SYMMETRIC",
+          "value": "EgQIAxAgGiDZsmkTufMG/XlKlk9m7bqxustjUPT2YULEVm8mOp2mSA=="
         },
         "outputPrefixType": "TINK",
         "keyId": 691856985,
         "status": "ENABLED"
-    }]
-}
+      }
+    ]
+  }
diff --git a/cc/examples/util/BUILD.bazel b/cc/examples/util/BUILD.bazel
new file mode 100644
index 0000000..b872c23
--- /dev/null
+++ b/cc/examples/util/BUILD.bazel
@@ -0,0 +1,20 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+cc_library(
+    name = "util",
+    srcs = ["util.cc"],
+    hdrs = ["util.h"],
+    deps = [
+        "@com_google_absl//absl/memory",
+        "@tink_cc//:cleartext_keyset_handle",
+        "@tink_cc//:json_keyset_reader",
+        "@tink_cc//:json_keyset_writer",
+        "@tink_cc//:keyset_handle",
+        "@tink_cc//:keyset_reader",
+        "@tink_cc//:keyset_writer",
+        "@tink_cc//util:status",
+        "@tink_cc//util:statusor",
+    ],
+)
diff --git a/cc/examples/util/CMakeLists.txt b/cc/examples/util/CMakeLists.txt
new file mode 100644
index 0000000..6e5a02c
--- /dev/null
+++ b/cc/examples/util/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_library(util util.cc util.h)
+target_include_directories(util PUBLIC
+  "${CMAKE_CURRENT_SOURCE_DIR}"
+  "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(util tink::static)
diff --git a/cc/examples/util/util.cc b/cc/examples/util/util.cc
new file mode 100644
index 0000000..ca4a3d5
--- /dev/null
+++ b/cc/examples/util/util.cc
@@ -0,0 +1,105 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "util/util.h"
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/json_keyset_reader.h"
+#include "tink/json_keyset_writer.h"
+#include "tink/keyset_handle.h"
+#include "tink/keyset_reader.h"
+#include "tink/keyset_writer.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace tink_cc_examples {
+namespace {
+
+using ::crypto::tink::JsonKeysetReader;
+using ::crypto::tink::JsonKeysetWriter;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::KeysetReader;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+
+// Creates a KeysetReader that reads a JSON-formatted keyset
+// from the given file.
+StatusOr<std::unique_ptr<KeysetReader>> GetJsonKeysetReader(
+    const std::string& filename) {
+  auto input_stream = absl::make_unique<std::ifstream>();
+  input_stream->open(filename, std::ifstream::in);
+  return JsonKeysetReader::New(std::move(input_stream));
+}
+
+StatusOr<std::unique_ptr<JsonKeysetWriter>> GetJsonKeysetWriter(
+    const std::string& filename) {
+  auto output_stream = absl::make_unique<std::ofstream>();
+  output_stream->open(filename, std::ofstream::out);
+  return JsonKeysetWriter::New(std::move(output_stream));
+}
+
+}  // namespace
+
+StatusOr<std::unique_ptr<KeysetHandle>> ReadJsonCleartextKeyset(
+    const std::string& filename) {
+  StatusOr<std::unique_ptr<KeysetReader>> keyset_reader =
+      GetJsonKeysetReader(filename);
+  if (!keyset_reader.ok()) return keyset_reader.status();
+  return crypto::tink::CleartextKeysetHandle::Read(*std::move(keyset_reader));
+}
+
+Status WriteJsonCleartextKeyset(const std::string& filename,
+                                const KeysetHandle& keyset_handle) {
+  StatusOr<std::unique_ptr<JsonKeysetWriter>> keyset_writer =
+      GetJsonKeysetWriter(filename);
+  if (!keyset_writer.ok()) return keyset_writer.status();
+  return crypto::tink::CleartextKeysetHandle::Write(keyset_writer->get(),
+                                                    keyset_handle);
+}
+
+StatusOr<std::string> ReadFile(const std::string& filename) {
+  std::ifstream input_stream;
+  input_stream.open(filename, std::ifstream::in);
+  if (!input_stream.is_open()) {
+    return Status(absl::StatusCode::kInternal,
+                  absl::StrCat("Error opening input file ", filename));
+  }
+  std::stringstream input;
+  input << input_stream.rdbuf();
+  return input.str();
+}
+
+Status WriteToFile(const std::string& data_to_write,
+                   const std::string& filename) {
+  std::ofstream output_stream(filename,
+                              std::ofstream::out | std::ofstream::binary);
+  if (!output_stream.is_open()) {
+    return Status(absl::StatusCode::kInternal,
+                  absl::StrCat("Error opening output file ", filename));
+  }
+  output_stream << data_to_write;
+  return crypto::tink::util::OkStatus();
+}
+
+}  // namespace tink_cc_examples
diff --git a/cc/examples/util/util.h b/cc/examples/util/util.h
new file mode 100644
index 0000000..3f25a31
--- /dev/null
+++ b/cc/examples/util/util.h
@@ -0,0 +1,49 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_UTIL_UTIL_H_
+#define TINK_EXAMPLES_UTIL_UTIL_H_
+
+#include <memory>
+#include <string>
+
+#include "tink/keyset_handle.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace tink_cc_examples {
+
+// Reads a keyset from the given file `filename` which is expected to contain a
+// JSON-formatted keyset.
+crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::KeysetHandle>>
+ReadJsonCleartextKeyset(const std::string& filename);
+
+// Writes `keyset_handle` to the file `filename` formatted with JSON in
+// cleartext.
+crypto::tink::util::Status WriteJsonCleartextKeyset(
+    const std::string& filename,
+    const crypto::tink::KeysetHandle& keyset_handle);
+
+// Reads `filename` and returns the read content as a string, or an error status
+// if the file does not exist.
+crypto::tink::util::StatusOr<std::string> ReadFile(const std::string& filename);
+
+// Writes the given `data_to_write` to the specified file `filename`.
+crypto::tink::util::Status WriteToFile(const std::string& data_to_write,
+                                       const std::string& filename);
+
+}  // namespace tink_cc_examples
+
+#endif  // TINK_EXAMPLES_UTIL_UTIL_H_
diff --git a/cc/examples/walkthrough/BUILD.bazel b/cc/examples/walkthrough/BUILD.bazel
new file mode 100644
index 0000000..69d2010
--- /dev/null
+++ b/cc/examples/walkthrough/BUILD.bazel
@@ -0,0 +1,165 @@
+"""Walkthrough examples for using Tink."""
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+cc_library(
+    name = "test_util",
+    testonly = 1,
+    srcs = ["test_util.cc"],
+    hdrs = ["test_util.h"],
+    deps = [
+        ":load_cleartext_keyset",
+        "@com_google_absl//absl/strings",
+        "@tink_cc",
+    ],
+)
+
+cc_library(
+    name = "create_keyset",
+    srcs = ["create_keyset.cc"],
+    hdrs = ["create_keyset.h"],
+    deps = [
+        "@tink_cc",
+        "@tink_cc//:keyset_handle",
+        "@tink_cc//util:statusor",
+    ],
+)
+
+cc_test(
+    name = "create_keyset_test",
+    srcs = ["create_keyset_test.cc"],
+    deps = [
+        ":create_keyset",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc",
+        "@tink_cc//util:test_matchers",
+    ],
+)
+
+cc_library(
+    name = "load_cleartext_keyset",
+    srcs = ["load_cleartext_keyset.cc"],
+    hdrs = ["load_cleartext_keyset.h"],
+    deps = [
+        "@com_google_absl//absl/strings",
+        "@tink_cc",
+        "@tink_cc//:cleartext_keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "load_cleartext_keyset_test",
+    srcs = ["load_cleartext_keyset_test.cc"],
+    deps = [
+        ":load_cleartext_keyset",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc",
+        "@tink_cc//util:test_matchers",
+    ],
+)
+
+cc_library(
+    name = "load_encrypted_keyset",
+    srcs = ["load_encrypted_keyset.cc"],
+    hdrs = ["load_encrypted_keyset.h"],
+    deps = [
+        "@com_google_absl//absl/strings",
+        "@tink_cc",
+    ],
+)
+
+cc_test(
+    name = "load_encrypted_keyset_test",
+    srcs = ["load_encrypted_keyset_test.cc"],
+    deps = [
+        ":load_cleartext_keyset",
+        ":load_encrypted_keyset",
+        ":test_util",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc",
+        "@tink_cc//util:test_matchers",
+    ],
+)
+
+cc_library(
+    name = "write_keyset",
+    srcs = ["write_keyset.cc"],
+    hdrs = ["write_keyset.h"],
+    deps = [
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@tink_cc",
+    ],
+)
+
+cc_test(
+    name = "write_keyset_test",
+    srcs = ["write_keyset_test.cc"],
+    deps = [
+        ":load_cleartext_keyset",
+        ":load_encrypted_keyset",
+        ":test_util",
+        ":write_keyset",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc",
+        "@tink_cc//util:test_matchers",
+    ],
+)
+
+cc_library(
+    name = "obtain_and_use_a_primitive",
+    srcs = ["obtain_and_use_a_primitive.cc"],
+    hdrs = ["obtain_and_use_a_primitive.h"],
+    deps = [
+        "@com_google_absl//absl/strings",
+        "@tink_cc",
+    ],
+)
+
+cc_test(
+    name = "obtain_and_use_a_primitive_test",
+    srcs = ["obtain_and_use_a_primitive_test.cc"],
+    deps = [
+        ":load_cleartext_keyset",
+        ":obtain_and_use_a_primitive",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc",
+        "@tink_cc//util:test_matchers",
+    ],
+)
+
+cc_library(
+    name = "write_cleartext_keyset",
+    srcs = ["write_cleartext_keyset.cc"],
+    hdrs = ["write_cleartext_keyset.h"],
+    deps = [
+        "@com_google_absl//absl/strings",
+        "@tink_cc",
+        "@tink_cc//:cleartext_keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "write_cleartext_keyset_test",
+    srcs = ["write_cleartext_keyset_test.cc"],
+    deps = [
+        ":load_cleartext_keyset",
+        ":write_cleartext_keyset",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc",
+        "@tink_cc//util:test_matchers",
+    ],
+)
diff --git a/cc/examples/walkthrough/CMakeLists.txt b/cc/examples/walkthrough/CMakeLists.txt
new file mode 100644
index 0000000..db128e5
--- /dev/null
+++ b/cc/examples/walkthrough/CMakeLists.txt
@@ -0,0 +1,70 @@
+# Library targets.
+
+add_library(create_keyset create_keyset.cc create_keyset.h)
+target_include_directories(create_keyset PUBLIC
+    "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(create_keyset tink::static)
+
+add_library(load_cleartext_keyset load_cleartext_keyset.cc load_cleartext_keyset.h)
+target_include_directories(load_cleartext_keyset PUBLIC
+    "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(load_cleartext_keyset tink::static)
+
+add_library(test_util test_util.cc test_util.h)
+target_include_directories(test_util PUBLIC
+    "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(test_util load_cleartext_keyset tink::static)
+
+add_library(load_encrypted_keyset load_encrypted_keyset.cc load_encrypted_keyset.h)
+target_include_directories(load_encrypted_keyset PUBLIC
+    "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(load_encrypted_keyset tink::static)
+
+add_library(write_keyset write_keyset.cc write_keyset.h)
+target_include_directories(write_keyset PUBLIC
+    "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(write_keyset load_cleartext_keyset tink::static)
+
+add_library(obtain_and_use_a_primitive obtain_and_use_a_primitive.cc obtain_and_use_a_primitive.h)
+target_include_directories(obtain_and_use_a_primitive PUBLIC
+    "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(obtain_and_use_a_primitive tink::static)
+
+add_library(write_cleartext_keyset write_cleartext_keyset.cc write_cleartext_keyset.h)
+target_include_directories(write_cleartext_keyset PUBLIC
+    "${CMAKE_CURRENT_SOURCE_DIR}"
+    "${TINK_EXAMPLES_INCLUDE_PATH}")
+target_link_libraries(write_cleartext_keyset tink::static)
+
+# Test targets.
+# NOTE: gmock and gtest_main are already exported by Tink.
+
+add_executable(create_keyset_test create_keyset_test.cc)
+add_test(NAME create_keyset_test COMMAND create_keyset_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+target_link_libraries(create_keyset_test create_keyset gmock gtest_main)
+
+add_executable(load_cleartext_keyset_test load_cleartext_keyset_test.cc)
+add_test(NAME load_cleartext_keyset_test COMMAND load_cleartext_keyset_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+target_link_libraries(load_cleartext_keyset_test load_cleartext_keyset gmock gtest_main)
+
+add_executable(load_encrypted_keyset_test load_encrypted_keyset_test.cc)
+add_test(NAME load_encrypted_keyset_test COMMAND load_encrypted_keyset_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+target_link_libraries(load_encrypted_keyset_test test_util load_encrypted_keyset load_cleartext_keyset gmock gtest_main)
+
+add_executable(write_keyset_test write_keyset_test.cc)
+add_test(NAME write_keyset_test COMMAND write_keyset_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+target_link_libraries(write_keyset_test test_util write_keyset load_cleartext_keyset load_encrypted_keyset gmock gtest_main)
+
+add_executable(obtain_and_use_a_primitive_test obtain_and_use_a_primitive_test.cc)
+add_test(NAME obtain_and_use_a_primitive_test COMMAND obtain_and_use_a_primitive_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+target_link_libraries(obtain_and_use_a_primitive_test obtain_and_use_a_primitive load_cleartext_keyset gmock gtest_main)
+
+add_executable(write_cleartext_keyset_test write_cleartext_keyset_test.cc)
+add_test(NAME write_cleartext_keyset_test COMMAND write_cleartext_keyset_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+target_link_libraries(write_cleartext_keyset_test write_cleartext_keyset load_cleartext_keyset gmock gtest_main)
diff --git a/cc/examples/walkthrough/create_keyset.cc b/cc/examples/walkthrough/create_keyset.cc
new file mode 100644
index 0000000..d02f24b
--- /dev/null
+++ b/cc/examples/walkthrough/create_keyset.cc
@@ -0,0 +1,46 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/create_keyset.h"
+
+// [START tink_walkthrough_create_keyset]
+#include <memory>
+
+#include "tink/aead/aead_key_templates.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::util::StatusOr;
+using ::google::crypto::tink::KeyTemplate;
+
+// Creates a keyset with a single AES128-GCM key and return a handle to it.
+//
+// Prerequisites for this example:
+//  - Register AEAD implementations of Tink.
+StatusOr<std::unique_ptr<KeysetHandle>> CreateAead128GcmKeyset() {
+  // Tink provides pre-baked templates. For example, we generate a key template
+  // for AES128-GCM.
+  KeyTemplate key_template = crypto::tink::AeadKeyTemplates::Aes128Gcm();
+  // This will generate a new keyset with only *one* key and return a keyset
+  // handle to it.
+  return KeysetHandle::GenerateNew(key_template);
+}
+
+}  // namespace tink_walkthrough
+// [END tink_walkthrough_create_keyset]
diff --git a/cc/examples/walkthrough/create_keyset.h b/cc/examples/walkthrough/create_keyset.h
new file mode 100644
index 0000000..96e2a41
--- /dev/null
+++ b/cc/examples/walkthrough/create_keyset.h
@@ -0,0 +1,32 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_WALKTHROUGH_CREATE_KEYSET_H_
+#define TINK_EXAMPLES_WALKTHROUGH_CREATE_KEYSET_H_
+
+#include <memory>
+
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+// Creates a keyset with a single AES128-GCM key and return a handle to it.
+crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::KeysetHandle>>
+CreateAead128GcmKeyset();
+
+}  // namespace tink_walkthrough
+
+#endif  // TINK_EXAMPLES_WALKTHROUGH_CREATE_KEYSET_H_
diff --git a/cc/examples/walkthrough/create_keyset_test.cc b/cc/examples/walkthrough/create_keyset_test.cc
new file mode 100644
index 0000000..7970c13
--- /dev/null
+++ b/cc/examples/walkthrough/create_keyset_test.cc
@@ -0,0 +1,67 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/create_keyset.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/aead/aead_config.h"
+#include "tink/registry.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace tink_walkthrough {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::util::StatusOr;
+using ::testing::Not;
+using ::testing::Test;
+
+class CreateAead128GcmKeysetTest : public Test {
+ public:
+  void TearDown() override { crypto::tink::Registry::Reset(); }
+};
+
+TEST_F(CreateAead128GcmKeysetTest,
+       CreateAead128GcmKeysetFailsIfAeadNotRegistered) {
+  EXPECT_THAT(CreateAead128GcmKeyset(), Not(IsOk()));
+}
+
+TEST_F(CreateAead128GcmKeysetTest, CreateAead128GcmKeysetSucceeds) {
+  ASSERT_THAT(crypto::tink::AeadConfig::Register(), IsOk());
+  StatusOr<std::unique_ptr<crypto::tink::KeysetHandle>> keyset_handle =
+      CreateAead128GcmKeyset();
+  ASSERT_THAT(keyset_handle, IsOk());
+  constexpr absl::string_view plaintext = "Some plaintext";
+  constexpr absl::string_view associated_data = "Some associated_data";
+  StatusOr<std::unique_ptr<crypto::tink::Aead>> aead =
+      (*keyset_handle)->GetPrimitive<crypto::tink::Aead>();
+  ASSERT_THAT(aead, IsOk());
+  StatusOr<std::string> ciphertext =
+      (*aead)->Encrypt(plaintext, associated_data);
+  ASSERT_THAT(ciphertext, IsOk());
+  EXPECT_THAT((*aead)->Decrypt(*ciphertext, associated_data),
+              IsOkAndHolds(plaintext));
+}
+
+}  // namespace
+}  // namespace tink_walkthrough
diff --git a/cc/examples/walkthrough/load_cleartext_keyset.cc b/cc/examples/walkthrough/load_cleartext_keyset.cc
new file mode 100644
index 0000000..5ea603b
--- /dev/null
+++ b/cc/examples/walkthrough/load_cleartext_keyset.cc
@@ -0,0 +1,55 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/load_cleartext_keyset.h"
+
+// [START tink_walkthrough_load_cleartext_keyset]
+#include <iostream>
+#include <memory>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/json_keyset_reader.h"
+#include "tink/keyset_handle.h"
+#include "tink/keyset_reader.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+using ::crypto::tink::util::StatusOr;
+
+// Loads a JSON-serialized unencrypted keyset `serialized_keyset` and returns a
+// KeysetHandle.
+//
+// Prerequisites for this example:
+//  - Create an plaintext keyset in JSON, for example, using Tinkey:
+//
+//    tinkey create-key --key-template AES256_GCM \
+//      --out-format json --out keyset.json
+//
+StatusOr<std::unique_ptr<crypto::tink::KeysetHandle>> LoadKeyset(
+    absl::string_view serialized_keyset) {
+  // To load a serialized keyset we need a JSON keyset reader.
+  StatusOr<std::unique_ptr<crypto::tink::KeysetReader>> reader =
+      crypto::tink::JsonKeysetReader::New(serialized_keyset);
+  if (!reader.ok()) return reader.status();
+  // Parse and obtain the keyset using the reader.
+  return crypto::tink::CleartextKeysetHandle::Read(*std::move(reader));
+}
+
+}  // namespace tink_walkthrough
+// [END tink_walkthrough_load_cleartext_keyset]
diff --git a/cc/examples/walkthrough/load_cleartext_keyset.h b/cc/examples/walkthrough/load_cleartext_keyset.h
new file mode 100644
index 0000000..b4e1e42
--- /dev/null
+++ b/cc/examples/walkthrough/load_cleartext_keyset.h
@@ -0,0 +1,34 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_WALKTHROUGH_LOAD_CLEARTEXT_KEYSET_H_
+#define TINK_EXAMPLES_WALKTHROUGH_LOAD_CLEARTEXT_KEYSET_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+// Loads a JSON-serialized unencrypted keyset `serialized_keyset` and returns a
+// KeysetHandle.
+crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::KeysetHandle>>
+LoadKeyset(absl::string_view serialized_keyset);
+
+}  // namespace tink_walkthrough
+
+#endif  // TINK_EXAMPLES_WALKTHROUGH_LOAD_CLEARTEXT_KEYSET_H_
diff --git a/cc/examples/walkthrough/load_cleartext_keyset_test.cc b/cc/examples/walkthrough/load_cleartext_keyset_test.cc
new file mode 100644
index 0000000..ac8e5fa
--- /dev/null
+++ b/cc/examples/walkthrough/load_cleartext_keyset_test.cc
@@ -0,0 +1,77 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/load_cleartext_keyset.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/aead/aead_config.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace tink_walkthrough {
+namespace {
+
+constexpr absl::string_view kSerializedKeyset = R"json({
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
+      },
+      "keyId": 294406504,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 294406504
+})json";
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::crypto::tink::util::StatusOr;
+
+TEST(LoadKeysetTest, LoadKeysetFailsWithInvalidKeyset) {
+  EXPECT_THAT(LoadKeyset("Invalid").status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(LoadKeysetTest, LoadKeysetSucceeds) {
+  StatusOr<std::unique_ptr<crypto::tink::KeysetHandle>> keyset_handle =
+      LoadKeyset(kSerializedKeyset);
+  ASSERT_THAT(keyset_handle, IsOk());
+  ASSERT_THAT(crypto::tink::AeadConfig::Register(), IsOk());
+  // Make sure we can extract the Aead primitive and encrypt/decrypt with it.
+  constexpr absl::string_view plaintext = "Some plaintext";
+  constexpr absl::string_view associated_data = "Some associated_data";
+  StatusOr<std::unique_ptr<crypto::tink::Aead>> aead =
+      (*keyset_handle)->GetPrimitive<crypto::tink::Aead>();
+  ASSERT_THAT(aead, IsOk());
+  StatusOr<std::string> ciphertext =
+      (*aead)->Encrypt(plaintext, associated_data);
+  ASSERT_THAT(ciphertext, IsOk());
+  EXPECT_THAT((*aead)->Decrypt(*ciphertext, associated_data),
+              IsOkAndHolds(plaintext));
+}
+
+}  // namespace
+}  // namespace tink_walkthrough
diff --git a/cc/examples/walkthrough/load_encrypted_keyset.cc b/cc/examples/walkthrough/load_encrypted_keyset.cc
new file mode 100644
index 0000000..11679a7
--- /dev/null
+++ b/cc/examples/walkthrough/load_encrypted_keyset.cc
@@ -0,0 +1,72 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/load_encrypted_keyset.h"
+
+// [START tink_walkthrough_load_encrypted_keyset]
+#include <iostream>
+#include <memory>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/json_keyset_reader.h"
+#include "tink/keyset_handle.h"
+#include "tink/keyset_reader.h"
+#include "tink/kms_client.h"
+#include "tink/kms_clients.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::util::StatusOr;
+
+// Loads a JSON-serialized keyset encrypted with a KSM
+// `serialized_encrypted_keyset`. The decryption uses the KMS master key
+// `master_key_uri`.
+//
+// Prerequisites for this example:
+//  - Register AEAD implementations of Tink.
+//  - Register a KMS client for the given URI prefix using KmsClients::Add.
+//  - Create a KMS encrypted keyset, for example using Tinkey with Cloud KMS:
+//
+//    tinkey create-keyset --key-template AES128_GCM \
+//      --out-format json --out encrypted_aead_keyset.json \
+//      --master-key-uri gcp-kms://<KMS key uri> \
+//      --credentials gcp_credentials.json
+//
+StatusOr<std::unique_ptr<KeysetHandle>> LoadKeyset(
+    absl::string_view serialized_encrypted_keyset,
+    absl::string_view master_key_uri) {
+  // Get a KMS client for the given key URI.
+  StatusOr<const crypto::tink::KmsClient*> kms_client =
+      crypto::tink::KmsClients::Get(master_key_uri);
+  if (!kms_client.ok()) return kms_client.status();
+  // A KmsClient can return an Aead primitive.
+  StatusOr<std::unique_ptr<crypto::tink::Aead>> kms_aead =
+      (*kms_client)->GetAead(master_key_uri);
+  if (!kms_aead.ok()) return kms_aead.status();
+  // Use a JSON reader to read the encrypted keyset.
+  StatusOr<std::unique_ptr<crypto::tink::KeysetReader>> reader =
+      crypto::tink::JsonKeysetReader::New(serialized_encrypted_keyset);
+  if (!reader.ok()) return reader.status();
+  // Decrypt using the KMS, parse the keyset and retuns a handle to it.
+  return KeysetHandle::Read(*std::move(reader), **kms_aead);
+}
+
+}  // namespace tink_walkthrough
+// [END tink_walkthrough_load_encrypted_keyset]
diff --git a/cc/examples/walkthrough/load_encrypted_keyset.h b/cc/examples/walkthrough/load_encrypted_keyset.h
new file mode 100644
index 0000000..0c271fd
--- /dev/null
+++ b/cc/examples/walkthrough/load_encrypted_keyset.h
@@ -0,0 +1,36 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_WALKTHROUGH_LOAD_ENCRYPTED_KEYSET_H_
+#define TINK_EXAMPLES_WALKTHROUGH_LOAD_ENCRYPTED_KEYSET_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+// Loads a JSON-serialized keyset encrypted with a KSM
+// `serialized_encrypted_keyset`. The decryption uses the KMS master key
+// `master_key_uri`.
+crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::KeysetHandle>>
+LoadKeyset(absl::string_view serialized_encrypted_keyset,
+           absl::string_view master_key_uri);
+
+}  // namespace tink_walkthrough
+
+#endif  // TINK_EXAMPLES_WALKTHROUGH_LOAD_ENCRYPTED_KEYSET_H_
diff --git a/cc/examples/walkthrough/load_encrypted_keyset_test.cc b/cc/examples/walkthrough/load_encrypted_keyset_test.cc
new file mode 100644
index 0000000..4a1c42a
--- /dev/null
+++ b/cc/examples/walkthrough/load_encrypted_keyset_test.cc
@@ -0,0 +1,168 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/load_encrypted_keyset.h"
+
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/aead/aead_config.h"
+#include "walkthrough/load_cleartext_keyset.h"
+#include "walkthrough/test_util.h"
+#include "tink/kms_clients.h"
+#include "tink/registry.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace tink_walkthrough {
+namespace {
+
+constexpr absl::string_view kSerializedMasterKeyKeyset = R"json({
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
+      },
+      "keyId": 294406504,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 294406504
+})json";
+
+constexpr absl::string_view kSerializedKeysetToEncrypt = R"json({
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GhD+9l0RANZjzZEZ8PDp7LRW"
+      },
+      "keyId": 1931667682,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 1931667682
+})json";
+
+// Encryption of kSerializedKeysetToEncrypt using kSerializedMasterKeyKeyset.
+constexpr absl::string_view kEncryptedKeyset = R"json({
+  "encryptedKeyset": "ARGMSWi6YHyZ/Oqxl00XSq631a0q2UPmf+rCvCIAggSZrwCmxFF797MpY0dqgaXu1fz2eQ8zFNhlyTXv9kwg1kY6COpyhY/68zNBUkyKX4CharLYfpg1LgRl+6rMzIQa0XDHh7ZDmp1CevzecZIKnG83uDRHxxSv3h8c/Kc="
+})json";
+
+constexpr absl::string_view kFakeKmsKeyUri = "fake://some_key";
+
+using ::crypto::tink::Aead;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::Registry;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+using ::testing::Environment;
+
+// Test environment used to register KMS clients only once for the whole test.
+class LoadKeysetTestEnvironment : public Environment {
+ public:
+  ~LoadKeysetTestEnvironment() override = default;
+
+  // Register FakeKmsClient and AlwaysFailingFakeKmsClient.
+  void SetUp() override {
+    auto fake_kms =
+        absl::make_unique<FakeKmsClient>(kSerializedMasterKeyKeyset);
+    ASSERT_THAT(crypto::tink::KmsClients::Add(std::move(fake_kms)), IsOk());
+    auto failing_kms = absl::make_unique<AlwaysFailingFakeKmsClient>();
+    ASSERT_THAT(crypto::tink::KmsClients::Add(std::move(failing_kms)), IsOk());
+  }
+};
+
+// Unused.
+Environment *const test_env =
+    testing::AddGlobalTestEnvironment(new LoadKeysetTestEnvironment());
+
+class LoadKeysetTest : public ::testing::Test {
+ public:
+  void TearDown() override { Registry::Reset(); }
+};
+
+TEST_F(LoadKeysetTest, LoadKeysetFailsWhenNoKmsRegistered) {
+  StatusOr<std::unique_ptr<KeysetHandle>> expected_keyset =
+      LoadKeyset(kEncryptedKeyset, /*master_key_uri=*/"other_kms://some_key");
+  EXPECT_THAT(expected_keyset.status(), StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST_F(LoadKeysetTest, LoadKeysetFailsWhenKmsClientFails) {
+  StatusOr<std::unique_ptr<KeysetHandle>> expected_keyset =
+      LoadKeyset(kEncryptedKeyset, /*master_key_uri=*/"failing://some_key");
+  EXPECT_THAT(expected_keyset.status(),
+              StatusIs(absl::StatusCode::kUnimplemented));
+}
+
+TEST_F(LoadKeysetTest, LoadKeysetFailsWhenAeadNotRegistered) {
+  StatusOr<std::unique_ptr<KeysetHandle>> expected_keyset =
+      LoadKeyset(kEncryptedKeyset, kFakeKmsKeyUri);
+  EXPECT_THAT(expected_keyset.status(), StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST_F(LoadKeysetTest, LoadKeysetFailsWhenInvalidKeyset) {
+  ASSERT_THAT(crypto::tink::AeadConfig::Register(), IsOk());
+  StatusOr<std::unique_ptr<KeysetHandle>> expected_keyset =
+      LoadKeyset("invalid", kFakeKmsKeyUri);
+  EXPECT_THAT(expected_keyset.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  Registry::Reset();
+}
+
+TEST_F(LoadKeysetTest, LoadKeysetSucceeds) {
+  ASSERT_THAT(crypto::tink::AeadConfig::Register(), IsOk());
+  StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      LoadKeyset(kEncryptedKeyset, kFakeKmsKeyUri);
+  ASSERT_THAT(handle, IsOk());
+  StatusOr<std::unique_ptr<Aead>> aead = (*handle)->GetPrimitive<Aead>();
+  ASSERT_THAT(aead, IsOk());
+
+  StatusOr<std::unique_ptr<KeysetHandle>> expected_keyset =
+      LoadKeyset(kSerializedKeysetToEncrypt);
+  ASSERT_THAT(expected_keyset, IsOk());
+  StatusOr<std::unique_ptr<Aead>> expected_aead =
+      (*expected_keyset)->GetPrimitive<Aead>();
+  ASSERT_THAT(expected_aead, IsOk());
+
+  std::string associated_data = "Some associated data";
+  std::string plaintext = "Some plaintext";
+
+  StatusOr<std::string> ciphertext =
+      (*aead)->Encrypt(plaintext, associated_data);
+  ASSERT_THAT(ciphertext, IsOk());
+  EXPECT_THAT((*expected_aead)->Decrypt(*ciphertext, associated_data),
+              IsOkAndHolds(plaintext));
+}
+
+}  // namespace
+}  // namespace tink_walkthrough
diff --git a/cc/examples/walkthrough/obtain_and_use_a_primitive.cc b/cc/examples/walkthrough/obtain_and_use_a_primitive.cc
new file mode 100644
index 0000000..3f4ede4
--- /dev/null
+++ b/cc/examples/walkthrough/obtain_and_use_a_primitive.cc
@@ -0,0 +1,71 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/obtain_and_use_a_primitive.h"
+
+// [START tink_walkthrough_obtain_and_use_a_primitive]
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+using ::crypto::tink::Aead;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::util::StatusOr;
+
+// AEAD encrypts `plaintext` with `associated_data` and the primary key in
+// `keyset_handle`.
+//
+// Prerequisites for this example:
+//  - Register AEAD implementations of Tink.
+//  - Create a keyset and get a handle to it.
+StatusOr<std::string> AeadEncrypt(const KeysetHandle& keyset_handle,
+                                  absl::string_view palintext,
+                                  absl::string_view associated_data) {
+  // To facilitate key rotation, GetPrimitive returns an Aead primitive that
+  // "wraps" multiple Aead primitives in the keyset. When encrypting it uses the
+  // primary key.
+  StatusOr<std::unique_ptr<Aead>> aead = keyset_handle.GetPrimitive<Aead>();
+  if (!aead.ok()) return aead.status();
+  return (*aead)->Encrypt(palintext, associated_data);
+}
+
+// AEAD decrypts `ciphertext` with `associated_data` and the correct key in
+// `keyset_handle`.
+//
+// Prerequisites for this example:
+//  - Register AEAD implementations of Tink.
+//  - Create a keyset and get a handle to it.
+StatusOr<std::string> AeadDecrypt(const KeysetHandle& keyset_handle,
+                                  absl::string_view ciphertext,
+                                  absl::string_view associated_data) {
+  // To facilitate key rotation, GetPrimitive returns an Aead primitive that
+  // "wraps" multiple Aead primitives in the keyset. When decrypting it uses the
+  // key that was used to encrypt using the key ID contained in the ciphertext.
+  StatusOr<std::unique_ptr<Aead>> aead = keyset_handle.GetPrimitive<Aead>();
+  if (!aead.ok()) return aead.status();
+  return (*aead)->Decrypt(ciphertext, associated_data);
+}
+
+}  // namespace tink_walkthrough
+// [END tink_walkthrough_obtain_and_use_a_primitive]
diff --git a/cc/examples/walkthrough/obtain_and_use_a_primitive.h b/cc/examples/walkthrough/obtain_and_use_a_primitive.h
new file mode 100644
index 0000000..1f36018
--- /dev/null
+++ b/cc/examples/walkthrough/obtain_and_use_a_primitive.h
@@ -0,0 +1,42 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_WALKTHROUGH_OBTAIN_AND_USE_A_PRIMITIVE_H_
+#define TINK_EXAMPLES_WALKTHROUGH_OBTAIN_AND_USE_A_PRIMITIVE_H_
+
+#include <memory>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+// AEAD encrypts `plaintext` with `associated_data` and the primary key in
+// `keyset_handle`.
+crypto::tink::util::StatusOr<std::string> AeadEncrypt(
+    const crypto::tink::KeysetHandle& keyset_handle,
+    absl::string_view palintext, absl::string_view associated_data);
+
+// AEAD decrypts `ciphertext` with `associated_data` and the correct key in
+// `keyset_handle`.
+crypto::tink::util::StatusOr<std::string> AeadDecrypt(
+    const crypto::tink::KeysetHandle& keyset_handle,
+    absl::string_view ciphertext, absl::string_view associated_data);
+
+}  // namespace tink_walkthrough
+
+#endif  // TINK_EXAMPLES_WALKTHROUGH_OBTAIN_AND_USE_A_PRIMITIVE_H_
diff --git a/cc/examples/walkthrough/obtain_and_use_a_primitive_test.cc b/cc/examples/walkthrough/obtain_and_use_a_primitive_test.cc
new file mode 100644
index 0000000..95b3894
--- /dev/null
+++ b/cc/examples/walkthrough/obtain_and_use_a_primitive_test.cc
@@ -0,0 +1,70 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/obtain_and_use_a_primitive.h"
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "absl/strings/string_view.h"
+#include "tink/aead/aead_config.h"
+#include "walkthrough/load_cleartext_keyset.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace tink_walkthrough {
+namespace {
+
+constexpr absl::string_view kSerializedKeyset = R"string({
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
+      },
+      "keyId": 294406504,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 294406504
+})string";
+
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::util::StatusOr;
+
+TEST(LoadKeysetTest, EncryptDecrypt) {
+  ASSERT_THAT(crypto::tink::AeadConfig::Register(), IsOk());
+  StatusOr<std::unique_ptr<KeysetHandle>> master_key_keyset =
+      LoadKeyset(kSerializedKeyset);
+  ASSERT_THAT(master_key_keyset, IsOk());
+  constexpr absl::string_view kPlaintext = "Some data";
+  constexpr absl::string_view kAssociatedData = "Some associated data";
+  StatusOr<std::string> ciphertext =
+      AeadEncrypt(**master_key_keyset, kPlaintext, kAssociatedData);
+  ASSERT_THAT(ciphertext, IsOk());
+  EXPECT_THAT(AeadDecrypt(**master_key_keyset, *ciphertext, kAssociatedData),
+              IsOkAndHolds(kPlaintext));
+}
+
+}  // namespace
+}  // namespace tink_walkthrough
diff --git a/cc/examples/walkthrough/test_util.cc b/cc/examples/walkthrough/test_util.cc
new file mode 100644
index 0000000..3cb7c49
--- /dev/null
+++ b/cc/examples/walkthrough/test_util.cc
@@ -0,0 +1,53 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/test_util.h"
+
+#include <memory>
+
+#include "absl/strings/match.h"
+#include "walkthrough/load_cleartext_keyset.h"
+#include "tink/keyset_handle.h"
+
+namespace tink_walkthrough {
+
+using ::crypto::tink::Aead;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+
+bool FakeKmsClient::DoesSupport(absl::string_view key_uri) const {
+  return absl::StartsWith(key_uri, "fake://");
+}
+
+StatusOr<std::unique_ptr<Aead>> FakeKmsClient::GetAead(
+    absl::string_view key_uri) const {
+  StatusOr<std::unique_ptr<KeysetHandle>> master_key_keyset =
+      LoadKeyset(serialized_master_key_keyset_);
+  if (!master_key_keyset.ok()) return master_key_keyset.status();
+  return (*master_key_keyset)->GetPrimitive<Aead>();
+}
+
+bool AlwaysFailingFakeKmsClient::DoesSupport(absl::string_view key_uri) const {
+  return absl::StartsWith(key_uri, "failing://");
+}
+
+StatusOr<std::unique_ptr<Aead>> AlwaysFailingFakeKmsClient::GetAead(
+    absl::string_view key_uri) const {
+  return Status(absl::StatusCode::kUnimplemented, "Unimplemented");
+}
+
+}  // namespace tink_walkthrough
diff --git a/cc/examples/walkthrough/test_util.h b/cc/examples/walkthrough/test_util.h
new file mode 100644
index 0000000..f5f7312
--- /dev/null
+++ b/cc/examples/walkthrough/test_util.h
@@ -0,0 +1,57 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_WALKTHROUGH_TEST_UTIL_H_
+#define TINK_EXAMPLES_WALKTHROUGH_TEST_UTIL_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/kms_client.h"
+#include "tink/util/statusor.h"
+
+namespace tink_walkthrough {
+
+// A fake KmsClient that for every key URI always returns an aead from
+// kSerializedMasterKeyKeyset.
+class FakeKmsClient : public crypto::tink::KmsClient {
+ public:
+  explicit FakeKmsClient(absl::string_view serialized_master_key_keyset)
+      : serialized_master_key_keyset_(serialized_master_key_keyset) {}
+
+  bool DoesSupport(absl::string_view key_uri) const override;
+
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::Aead>> GetAead(
+      absl::string_view key_uri) const override;
+
+ private:
+  std::string serialized_master_key_keyset_;
+};
+
+// A fake KmsClient that always fails to return an AEAD.
+class AlwaysFailingFakeKmsClient : public crypto::tink::KmsClient {
+ public:
+  bool DoesSupport(absl::string_view key_uri) const override;
+
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::Aead>> GetAead(
+      absl::string_view key_uri) const override;
+};
+
+}  // namespace tink_walkthrough
+
+#endif  // TINK_EXAMPLES_WALKTHROUGH_TEST_UTIL_H_
diff --git a/cc/examples/walkthrough/write_cleartext_keyset.cc b/cc/examples/walkthrough/write_cleartext_keyset.cc
new file mode 100644
index 0000000..0cdc856
--- /dev/null
+++ b/cc/examples/walkthrough/write_cleartext_keyset.cc
@@ -0,0 +1,56 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/write_cleartext_keyset.h"
+
+// [START tink_walkthrough_write_keyset]
+#include <memory>
+#include <ostream>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/json_keyset_writer.h"
+#include "tink/keyset_handle.h"
+
+namespace tink_walkthrough {
+
+using ::crypto::tink::JsonKeysetWriter;
+using ::crypto::tink::util::StatusOr;
+
+// Writes a `keyset` to `output_stream` as a plaintext JSON format.
+//
+// Warning: Storing keys in cleartext is not recommended. We recommend using a
+// Key Management Service to protect your keys. See
+// https://github.com/google/tink/blob/master/cc/examples/walkthrough/write_keyset.cc
+// for an example, and
+// https://developers.google.com/tink/key-management-overview for more info on
+// how to use a KMS with Tink.
+//
+// Prerequisites for this example:
+//  - Create a keyset and obtain a KeysetHandle to it.
+crypto::tink::util::Status WriteKeyset(
+    const crypto::tink::KeysetHandle& keyset,
+    std::unique_ptr<std::ostream> output_stream) {
+  StatusOr<std::unique_ptr<JsonKeysetWriter>> keyset_writer =
+      JsonKeysetWriter::New(std::move(output_stream));
+  if (!keyset_writer.ok()) return keyset_writer.status();
+  return crypto::tink::CleartextKeysetHandle::Write((keyset_writer)->get(),
+                                                    keyset);
+}
+
+}  // namespace tink_walkthrough
+// [END tink_walkthrough_write_keyset]
diff --git a/cc/examples/walkthrough/write_cleartext_keyset.h b/cc/examples/walkthrough/write_cleartext_keyset.h
new file mode 100644
index 0000000..f0f2b8e
--- /dev/null
+++ b/cc/examples/walkthrough/write_cleartext_keyset.h
@@ -0,0 +1,37 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_WALKTHROUGH_WRITE_CLEARTEXT_KEYSET_H_
+#define TINK_EXAMPLES_WALKTHROUGH_WRITE_CLEARTEXT_KEYSET_H_
+
+#include <memory>
+#include <ostream>
+
+#include "tink/keyset_handle.h"
+
+namespace tink_walkthrough {
+
+// Writes a `keyset` to `output_stream` as a plaintext JSON format.
+//
+// Warning: Storing keys in cleartext is not recommended. We recommend using a
+// Key Management Service to protect your keys. See
+// https://developers.google.com/tink/key-management-overview.
+crypto::tink::util::Status WriteKeyset(
+    const crypto::tink::KeysetHandle& keyset,
+    std::unique_ptr<std::ostream> output_stream);
+
+}  // namespace tink_walkthrough
+
+#endif  // TINK_EXAMPLES_WALKTHROUGH_WRITE_CLEARTEXT_KEYSET_H_
diff --git a/cc/examples/walkthrough/write_cleartext_keyset_test.cc b/cc/examples/walkthrough/write_cleartext_keyset_test.cc
new file mode 100644
index 0000000..af229a2
--- /dev/null
+++ b/cc/examples/walkthrough/write_cleartext_keyset_test.cc
@@ -0,0 +1,89 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/write_cleartext_keyset.h"
+
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/aead/aead_config.h"
+#include "walkthrough/load_cleartext_keyset.h"
+#include "tink/util/test_matchers.h"
+
+namespace tink_walkthrough {
+namespace {
+
+using ::crypto::tink::Aead;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::util::StatusOr;
+
+constexpr absl::string_view kSerializedKeyset = R"string({
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
+      },
+      "keyId": 294406504,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 294406504
+})string";
+
+TEST(WriteCleartextKeysetTest, WriteKeysetSerializesCorrectly) {
+  ASSERT_THAT(crypto::tink::AeadConfig::Register(), IsOk());
+  StatusOr<std::unique_ptr<KeysetHandle>> keyset =
+      LoadKeyset(kSerializedKeyset);
+
+  std::stringbuf buffer;
+  auto output_stream = absl::make_unique<std::ostream>(&buffer);
+  ASSERT_THAT(WriteKeyset(**keyset, std::move(output_stream)), IsOk());
+
+  StatusOr<std::unique_ptr<Aead>> aead = (*keyset)->GetPrimitive<Aead>();
+
+  // Make sure the encrypted keyset was written correctly by loading it and
+  // trying to decrypt ciphertext.
+  StatusOr<std::unique_ptr<KeysetHandle>> loaded_keyset =
+      LoadKeyset(buffer.str());
+  ASSERT_THAT(loaded_keyset, IsOk());
+  StatusOr<std::unique_ptr<Aead>> loaded_keyset_aead =
+      (*loaded_keyset)->GetPrimitive<Aead>();
+  ASSERT_THAT(loaded_keyset_aead, IsOk());
+
+  constexpr absl::string_view kPlaintext = "Some plaintext";
+  constexpr absl::string_view kAssociatedData = "Some associated data";
+
+  StatusOr<std::string> ciphertext =
+      (*aead)->Encrypt(kPlaintext, kAssociatedData);
+  EXPECT_THAT((*loaded_keyset_aead)->Decrypt(*ciphertext, kAssociatedData),
+              IsOkAndHolds(kPlaintext));
+  ciphertext = (*loaded_keyset_aead)->Encrypt(kPlaintext, kAssociatedData);
+  EXPECT_THAT((*aead)->Decrypt(*ciphertext, kAssociatedData),
+              IsOkAndHolds(kPlaintext));
+}
+
+}  // namespace
+}  // namespace tink_walkthrough
diff --git a/cc/examples/walkthrough/write_keyset.cc b/cc/examples/walkthrough/write_keyset.cc
new file mode 100644
index 0000000..ddaf807
--- /dev/null
+++ b/cc/examples/walkthrough/write_keyset.cc
@@ -0,0 +1,65 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/write_keyset.h"
+
+// [START tink_walkthrough_write_keyset]
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/json_keyset_writer.h"
+#include "tink/keyset_handle.h"
+#include "tink/kms_client.h"
+#include "tink/kms_clients.h"
+
+namespace tink_walkthrough {
+
+using ::crypto::tink::JsonKeysetWriter;
+using ::crypto::tink::util::StatusOr;
+
+// Writes a `keyset` to `output_stream` in JSON format; the keyset is encrypted
+// through a KMS service using the KMS key `master_kms_key_uri`.
+//
+// Prerequisites for this example:
+//  - Register AEAD implementations of Tink.
+//  - Register a KMS client that can use `master_kms_key_uri`.
+//  - Create a keyset and obtain a KeysetHandle to it.
+crypto::tink::util::Status WriteEncryptedKeyset(
+    const crypto::tink::KeysetHandle& keyset,
+    std::unique_ptr<std::ostream> output_stream,
+    absl::string_view master_kms_key_uri) {
+  // Create a writer that will write the keyset to output_stream as JSON.
+  StatusOr<std::unique_ptr<JsonKeysetWriter>> writer =
+      JsonKeysetWriter::New(std::move(output_stream));
+  if (!writer.ok()) return writer.status();
+  // Get a KMS client for the given key URI.
+  StatusOr<const crypto::tink::KmsClient*> kms_client =
+      crypto::tink::KmsClients::Get(master_kms_key_uri);
+  if (!kms_client.ok()) return kms_client.status();
+  // Get an Aead primitive that uses the KMS service to encrypt/decrypt.
+  StatusOr<std::unique_ptr<crypto::tink::Aead>> kms_aead =
+      (*kms_client)->GetAead(master_kms_key_uri);
+  if (!kms_aead.ok()) return kms_aead.status();
+  return keyset.Write(writer->get(), **kms_aead);
+}
+
+}  // namespace tink_walkthrough
+// [END tink_walkthrough_write_keyset]
diff --git a/cc/examples/walkthrough/write_keyset.h b/cc/examples/walkthrough/write_keyset.h
new file mode 100644
index 0000000..323328c
--- /dev/null
+++ b/cc/examples/walkthrough/write_keyset.h
@@ -0,0 +1,35 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_EXAMPLES_WALKTHROUGH_WRITE_KEYSET_H_
+#define TINK_EXAMPLES_WALKTHROUGH_WRITE_KEYSET_H_
+
+#include <memory>
+#include <ostream>
+
+#include "tink/keyset_handle.h"
+
+namespace tink_walkthrough {
+
+// Writes a `keyset` to `output_stream` in JSON format; the keyset is encrypted
+// through a KMS service using the KMS key `master_kms_key_uri`.
+crypto::tink::util::Status WriteEncryptedKeyset(
+    const crypto::tink::KeysetHandle& keyset,
+    std::unique_ptr<std::ostream> output_stream,
+    absl::string_view master_kms_key_uri);
+
+}  // namespace tink_walkthrough
+
+#endif  // TINK_EXAMPLES_WALKTHROUGH_WRITE_KEYSET_H_
diff --git a/cc/examples/walkthrough/write_keyset_test.cc b/cc/examples/walkthrough/write_keyset_test.cc
new file mode 100644
index 0000000..4b82df4
--- /dev/null
+++ b/cc/examples/walkthrough/write_keyset_test.cc
@@ -0,0 +1,162 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "walkthrough/write_keyset.h"
+
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/aead/aead_config.h"
+#include "walkthrough/load_cleartext_keyset.h"
+#include "walkthrough/load_encrypted_keyset.h"
+#include "walkthrough/test_util.h"
+#include "tink/kms_clients.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace tink_walkthrough {
+namespace {
+
+constexpr absl::string_view kSerializedMasterKeyKeyset = R"json({
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
+      },
+      "keyId": 294406504,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 294406504
+})json";
+
+constexpr absl::string_view kSerializedKeysetToEncrypt = R"json({
+  "key": [
+    {
+      "keyData": {
+        "keyMaterialType": "SYMMETRIC",
+        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+        "value": "GhD+9l0RANZjzZEZ8PDp7LRW"
+      },
+      "keyId": 1931667682,
+      "outputPrefixType": "TINK",
+      "status": "ENABLED"
+    }
+  ],
+  "primaryKeyId": 1931667682
+})json";
+
+using ::crypto::tink::Aead;
+using ::crypto::tink::KeysetHandle;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+using ::testing::Not;
+
+Status InitFakeKms() {
+  static Status* status = new Status([]() {
+    Status status = crypto::tink::AeadConfig::Register();
+    if (!status.ok()) {
+      return status;
+    }
+    return crypto::tink::KmsClients::Add(
+        absl::make_unique<FakeKmsClient>(kSerializedMasterKeyKeyset));
+  }());
+  return *status;
+}
+
+class WriteKeysetTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_THAT(InitFakeKms(), IsOk());
+    StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle_to_encrypt =
+        LoadKeyset(kSerializedKeysetToEncrypt);
+    ASSERT_THAT(keyset_handle_to_encrypt, IsOk());
+    keyset_handle_to_encrypt_ = std::move(*keyset_handle_to_encrypt);
+  }
+
+  std::unique_ptr<KeysetHandle> keyset_handle_to_encrypt_;
+};
+
+TEST_F(WriteKeysetTest, WriteEncryptedKeysetFailsWithNullOutputStream) {
+  EXPECT_THAT(WriteEncryptedKeyset(*keyset_handle_to_encrypt_, nullptr,
+                                   /*master_kms_key_uri=*/"fake://some_key"),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(WriteKeysetTest, WriteEncryptedKeysetFailsWhenStreamFails) {
+  auto output_stream = absl::make_unique<std::ostream>(nullptr);
+  EXPECT_THAT(
+      WriteEncryptedKeyset(*keyset_handle_to_encrypt_, std::move(output_stream),
+                           /*master_kms_key_uri=*/"fake://some_key"),
+      Not(IsOk()));
+}
+
+TEST_F(WriteKeysetTest, WriteEncryptedKeysetFailsNoKmsAvailable) {
+  std::stringbuf buffer;
+  auto output_stream = absl::make_unique<std::ostream>(&buffer);
+  EXPECT_THAT(WriteEncryptedKeyset(
+                  *keyset_handle_to_encrypt_, std::move(output_stream),
+                  /*master_kms_key_uri=*/"does_not_exist://does_not_exist"),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST_F(WriteKeysetTest, WriteEncryptedKeysetWithValidInputs) {
+  std::stringbuf buffer;
+  auto output_stream = absl::make_unique<std::ostream>(&buffer);
+  constexpr absl::string_view master_kms_key_uri = "fake://some_key";
+  ASSERT_THAT(
+      WriteEncryptedKeyset(*keyset_handle_to_encrypt_, std::move(output_stream),
+                           master_kms_key_uri),
+      IsOk());
+  StatusOr<std::unique_ptr<Aead>> expected_aead =
+      keyset_handle_to_encrypt_->GetPrimitive<Aead>();
+  ASSERT_THAT(expected_aead, IsOk());
+  constexpr absl::string_view associated_data = "Some associated data";
+  constexpr absl::string_view plaintext = "Some plaintext";
+
+  StatusOr<std::string> ciphertext =
+      (*expected_aead)->Encrypt(plaintext, associated_data);
+  ASSERT_THAT(ciphertext, IsOk());
+
+  // Make sure the encrypted keyset was written correctly by loading it and
+  // trying to decrypt ciphertext.
+  StatusOr<std::unique_ptr<KeysetHandle>> loaded_keyset =
+      LoadKeyset(buffer.str(), master_kms_key_uri);
+  ASSERT_THAT(loaded_keyset, IsOk());
+  StatusOr<std::unique_ptr<Aead>> loaded_keyset_aead =
+      (*loaded_keyset)->GetPrimitive<Aead>();
+  ASSERT_THAT(loaded_keyset_aead, IsOk());
+  EXPECT_THAT((*loaded_keyset_aead)->Decrypt(*ciphertext, associated_data),
+              IsOkAndHolds(plaintext));
+}
+
+}  // namespace
+}  // namespace tink_walkthrough
diff --git a/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.cc b/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.cc
index badab1d..3f6139d 100644
--- a/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.cc
+++ b/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.h"
 
+#include <memory>
+#include <sstream>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.h b/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.h
index 202df39..f48aba9 100644
--- a/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.h
+++ b/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.h
@@ -37,7 +37,7 @@
       std::unique_ptr<const Cecpq2AeadHkdfDemHelper>>
   New(const google::crypto::tink::KeyTemplate& dem_key_template);
 
-  virtual ~Cecpq2AeadHkdfDemHelper() {}
+  virtual ~Cecpq2AeadHkdfDemHelper() = default;
 
   // Creates and returns a new AeadOrDaead object that uses
   // a 32-bytes or greater high-entropy seed to generate a key.
diff --git a/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper_test.cc b/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper_test.cc
index d9c9c33..b6ca7bd 100644
--- a/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper_test.cc
+++ b/cc/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/kem/cecpq2_aead_hkdf_dem_helper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/kem/cecpq2_hybrid_key_templates.cc b/cc/experimental/pqcrypto/kem/cecpq2_hybrid_key_templates.cc
index c51a426..f6a24f7 100644
--- a/cc/experimental/pqcrypto/kem/cecpq2_hybrid_key_templates.cc
+++ b/cc/experimental/pqcrypto/kem/cecpq2_hybrid_key_templates.cc
@@ -29,7 +29,6 @@
 namespace tink {
 namespace {
 
-using google::crypto::tink::Cecpq2AeadHkdfKeyFormat;
 using google::crypto::tink::EcPointFormat;
 using google::crypto::tink::EllipticCurveType;
 using google::crypto::tink::HashType;
diff --git a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt.cc b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt.cc
index 933eb4a..2ed06d8 100644
--- a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt.cc
+++ b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt_test.cc b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt_test.cc
index d141836..ffa659a 100644
--- a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt_test.cc
+++ b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt_test.cc
@@ -16,8 +16,10 @@
 
 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_decrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt.cc b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt.cc
index eb0c665..9d405b9 100644
--- a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt.cc
+++ b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt_test.cc b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt_test.cc
index 42d5b03..299e2c2 100644
--- a/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt_test.cc
+++ b/cc/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_aead_hkdf_hybrid_encrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_recipient_kem_boringssl.cc b/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_recipient_kem_boringssl.cc
index f8e7baa..5eca870 100644
--- a/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_recipient_kem_boringssl.cc
+++ b/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_recipient_kem_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_recipient_kem_boringssl.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/memory/memory.h"
diff --git a/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.cc b/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.cc
index a67c8b5..7f21162 100644
--- a/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.cc
+++ b/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.h b/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.h
index e24e8b8..be57c87 100644
--- a/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.h
+++ b/cc/experimental/pqcrypto/kem/subtle/cecpq2_hkdf_sender_kem_boringssl.h
@@ -89,9 +89,8 @@
   // curves is trivial.
   static crypto::tink::util::StatusOr<
       std::unique_ptr<const Cecpq2HkdfSenderKemBoringSsl>>
-  New(EllipticCurveType curve, const absl::string_view ec_pubx,
-      const absl::string_view ec_puby,
-      const absl::string_view marshalled_hrss_pub);
+  New(EllipticCurveType curve, absl::string_view ec_pubx,
+      absl::string_view ec_puby, absl::string_view marshalled_hrss_pub);
 
   // Generates ephemeral key pairs, computes ECC's shared secret based on
   // generated ephemeral key and recipient's public key, generate a random
@@ -114,9 +113,8 @@
   // must be a big-endian byte array, and recipient's HRSS public key.
   static crypto::tink::util::StatusOr<
       std::unique_ptr<const Cecpq2HkdfSenderKemBoringSsl>>
-  New(EllipticCurveType curve, const absl::string_view pubx,
-      const absl::string_view puby,
-      const absl::string_view marshalled_hrss_pub);
+  New(EllipticCurveType curve, absl::string_view pubx, absl::string_view puby,
+      absl::string_view marshalled_hrss_pub);
 
   // Generates an ephemeral X25519 key pair, computes the X25519's shared secret
   // based on the ephemeral key and recipient's public key, generates a random
@@ -136,8 +134,7 @@
   // curve is not provided as a parameter here because the curve validation has
   // already been made in the New() method defined above.
   explicit Cecpq2HkdfX25519SenderKemBoringSsl(
-      const absl::string_view peer_ec_pubx,
-      const absl::string_view marshalled_hrss_pub);
+      absl::string_view peer_ec_pubx, absl::string_view marshalled_hrss_pub);
 
   // X25519 and HRSS public key containers. We note that the BoringSSL
   // implementation of HRSS requires that the HRSS public key is stored in the
diff --git a/cc/experimental/pqcrypto/kem/util/test_util.cc b/cc/experimental/pqcrypto/kem/util/test_util.cc
index ff4e43b..d019189 100644
--- a/cc/experimental/pqcrypto/kem/util/test_util.cc
+++ b/cc/experimental/pqcrypto/kem/util/test_util.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/kem/util/test_util.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/kem/util/test_util_test.cc b/cc/experimental/pqcrypto/kem/util/test_util_test.cc
index ee7c3ca..98c48d6 100644
--- a/cc/experimental/pqcrypto/kem/util/test_util_test.cc
+++ b/cc/experimental/pqcrypto/kem/util/test_util_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/kem/util/test_util.h"
 
+#include <vector>
+
 #include "gtest/gtest.h"
 #include "tink/aead/aes_gcm_key_manager.h"
 #include "tink/hybrid_encrypt.h"
diff --git a/cc/experimental/pqcrypto/signature/dilithium_key_template.cc b/cc/experimental/pqcrypto/signature/dilithium_key_template.cc
index adbaf94..14f52a3 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_key_template.cc
+++ b/cc/experimental/pqcrypto/signature/dilithium_key_template.cc
@@ -40,7 +40,7 @@
 using google::crypto::tink::KeyTemplate;
 using google::crypto::tink::OutputPrefixType;
 
-KeyTemplate* NewDilithiumKeyTemplate(int32 key_size,
+KeyTemplate* NewDilithiumKeyTemplate(int32_t key_size,
                                      DilithiumSeedExpansion seed_expansion) {
   KeyTemplate* key_template = new KeyTemplate;
   key_template->set_type_url(
diff --git a/cc/experimental/pqcrypto/signature/dilithium_key_template.h b/cc/experimental/pqcrypto/signature/dilithium_key_template.h
index cce59e2..c3acff2 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_key_template.h
+++ b/cc/experimental/pqcrypto/signature/dilithium_key_template.h
@@ -14,8 +14,8 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_EXPERIMENTAL_SIGNATURE_DILITHIUM_KEY_TEMPLATE_H_
-#define TINK_EXPERIMENTAL_SIGNATURE_DILITHIUM_KEY_TEMPLATE_H_
+#ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_KEY_TEMPLATE_H_
+#define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_KEY_TEMPLATE_H_
 
 #include "proto/tink.pb.h"
 
@@ -38,4 +38,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_EXPERIMENTAL_SIGNATURE_DILITHIUM_KEY_TEMPLATE_H_
+#endif  // TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_KEY_TEMPLATE_H_
diff --git a/cc/experimental/pqcrypto/signature/dilithium_key_template_test.cc b/cc/experimental/pqcrypto/signature/dilithium_key_template_test.cc
index f4b2858..ac8c9c8 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_key_template_test.cc
+++ b/cc/experimental/pqcrypto/signature/dilithium_key_template_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/dilithium_key_template.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -50,7 +51,7 @@
 
 struct DilithiumKeyTemplateTestCase {
   std::string test_name;
-  int32 key_size;
+  int32_t key_size;
   DilithiumSeedExpansion seed_expansion;
   KeyTemplate key_template;
 };
diff --git a/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.cc b/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.cc
index 3acb09b..2ff7e1b 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.cc
+++ b/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.cc
@@ -16,6 +16,9 @@
 
 #include "tink/experimental/pqcrypto/signature/dilithium_sign_key_manager.h"
 
+#include <memory>
+#include <utility>
+
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
diff --git a/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.h b/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.h
index 6e83592..4f5abb0 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.h
+++ b/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_SIGN_KEY_MANAGER_H_
 #define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager_test.cc b/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager_test.cc
index 92f5423..bca0d26 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager_test.cc
+++ b/cc/experimental/pqcrypto/signature/dilithium_sign_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/dilithium_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -70,7 +71,7 @@
 
 // Helper function that returns a valid dilithium key format.
 StatusOr<DilithiumKeyFormat> CreateValidKeyFormat(
-    int32 private_key_size, DilithiumSeedExpansion seed_expansion) {
+    int32_t private_key_size, DilithiumSeedExpansion seed_expansion) {
   DilithiumKeyFormat key_format;
   DilithiumParams* params = key_format.mutable_params();
   params->set_key_size(private_key_size);
diff --git a/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.cc b/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.cc
index 740e6f5..66d3c75 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.cc
+++ b/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/signature/dilithium_verify_key_manager.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
diff --git a/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.h b/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.h
index b139ab8..4893220 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.h
+++ b/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager.h
@@ -14,9 +14,10 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SUBTLE_DILITHIUM_VERIFY_KEY_MANAGER_H_
-#define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SUBTLE_DILITHIUM_VERIFY_KEY_MANAGER_H_
+#ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_VERIFY_KEY_MANAGER_H_
+#define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
@@ -71,4 +72,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SUBTLE_DILITHIUM_VERIFY_KEY_MANAGER_H_
+#endif  // TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_DILITHIUM_VERIFY_KEY_MANAGER_H_
diff --git a/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager_test.cc b/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager_test.cc
index e9c1695..a093fea 100644
--- a/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager_test.cc
+++ b/cc/experimental/pqcrypto/signature/dilithium_verify_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/dilithium_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -70,7 +71,7 @@
 
 // Helper function that returns a valid dilithium private key.
 StatusOr<DilithiumPrivateKey> CreateValidPrivateKey(
-    int32 private_key_size, DilithiumSeedExpansion seed_expansion) {
+    int32_t private_key_size, DilithiumSeedExpansion seed_expansion) {
   DilithiumKeyFormat key_format;
   DilithiumParams* params = key_format.mutable_params();
   params->set_key_size(private_key_size);
@@ -81,7 +82,7 @@
 
 // Helper function that returns a valid dilithium public key.
 StatusOr<DilithiumPublicKey> CreateValidPublicKey(
-    int32 private_key_size, DilithiumSeedExpansion seed_expansion) {
+    int32_t private_key_size, DilithiumSeedExpansion seed_expansion) {
   StatusOr<DilithiumPrivateKey> private_key =
       CreateValidPrivateKey(private_key_size, seed_expansion);
 
diff --git a/cc/experimental/pqcrypto/signature/falcon_key_template.cc b/cc/experimental/pqcrypto/signature/falcon_key_template.cc
index 668259b..bd33a70 100644
--- a/cc/experimental/pqcrypto/signature/falcon_key_template.cc
+++ b/cc/experimental/pqcrypto/signature/falcon_key_template.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/signature/falcon_key_template.h"
 
+#include <memory>
+
 #include "tink/experimental/pqcrypto/signature/subtle/falcon_subtle_utils.h"
 #include "tink/util/constants.h"
 #include "proto/experimental/pqcrypto/falcon.pb.h"
diff --git a/cc/experimental/pqcrypto/signature/falcon_key_template_test.cc b/cc/experimental/pqcrypto/signature/falcon_key_template_test.cc
index 8c9c167..2af3a5c 100644
--- a/cc/experimental/pqcrypto/signature/falcon_key_template_test.cc
+++ b/cc/experimental/pqcrypto/signature/falcon_key_template_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/falcon_key_template.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
diff --git a/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.cc b/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.cc
index 9d1d3ed..0b431c5 100644
--- a/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.cc
+++ b/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/signature/falcon_sign_key_manager.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
diff --git a/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.h b/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.h
index 0ce6cc2..32a6f14 100644
--- a/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.h
+++ b/cc/experimental/pqcrypto/signature/falcon_sign_key_manager.h
@@ -17,7 +17,7 @@
 #ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_FALCON_SIGN_KEY_MANAGER_H_
 #define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_FALCON_SIGN_KEY_MANAGER_H_
 
-
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/experimental/pqcrypto/signature/falcon_sign_key_manager_test.cc b/cc/experimental/pqcrypto/signature/falcon_sign_key_manager_test.cc
index eccd0bb..06ff16a 100644
--- a/cc/experimental/pqcrypto/signature/falcon_sign_key_manager_test.cc
+++ b/cc/experimental/pqcrypto/signature/falcon_sign_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/falcon_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -54,7 +55,7 @@
 using FalconSignKeyManagerTest = testing::TestWithParam<FalconTestCase>;
 
 // Helper function that returns a valid falcon key format.
-StatusOr<FalconKeyFormat> CreateValidKeyFormat(int32 private_key_size) {
+StatusOr<FalconKeyFormat> CreateValidKeyFormat(int32_t private_key_size) {
   FalconKeyFormat key_format;
   key_format.set_key_size(private_key_size);
 
diff --git a/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.cc b/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.cc
index 26dab4b..125c802 100644
--- a/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.cc
+++ b/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/signature/falcon_verify_key_manager.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
diff --git a/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.h b/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.h
index 9956700..dabe237 100644
--- a/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.h
+++ b/cc/experimental/pqcrypto/signature/falcon_verify_key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_FALCON_VERIFY_KEY_MANAGER_H_
 #define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_FALCON_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/experimental/pqcrypto/signature/falcon_verify_key_manager_test.cc b/cc/experimental/pqcrypto/signature/falcon_verify_key_manager_test.cc
index c486288..3f89de5 100644
--- a/cc/experimental/pqcrypto/signature/falcon_verify_key_manager_test.cc
+++ b/cc/experimental/pqcrypto/signature/falcon_verify_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/falcon_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -54,7 +55,7 @@
 using FalconVerifyKeyManagerTest = testing::TestWithParam<FalconTestCase>;
 
 // Helper function that returns a valid falcon private key.
-StatusOr<FalconPrivateKey> CreateValidPrivateKey(int32 private_key_size) {
+StatusOr<FalconPrivateKey> CreateValidPrivateKey(int32_t private_key_size) {
   FalconKeyFormat key_format;
   key_format.set_key_size(private_key_size);
 
@@ -62,7 +63,7 @@
 }
 
 // Helper function that returns a valid falcon public key.
-StatusOr<FalconPublicKey> CreateValidPublicKey(int32 private_key_size) {
+StatusOr<FalconPublicKey> CreateValidPublicKey(int32_t private_key_size) {
   StatusOr<FalconPrivateKey> private_key =
       CreateValidPrivateKey(private_key_size);
 
diff --git a/cc/experimental/pqcrypto/signature/signature_config_test.cc b/cc/experimental/pqcrypto/signature/signature_config_test.cc
index 2526126..11605e1 100644
--- a/cc/experimental/pqcrypto/signature/signature_config_test.cc
+++ b/cc/experimental/pqcrypto/signature/signature_config_test.cc
@@ -19,13 +19,13 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
-#include "tink/config/tink_fips.h"
 #include "tink/experimental/pqcrypto/signature/dilithium_sign_key_manager.h"
 #include "tink/experimental/pqcrypto/signature/dilithium_verify_key_manager.h"
-#include "tink/experimental/pqcrypto/signature/sphincs_sign_key_manager.h"
-#include "tink/experimental/pqcrypto/signature/sphincs_verify_key_manager.h"
 #include "tink/experimental/pqcrypto/signature/falcon_sign_key_manager.h"
 #include "tink/experimental/pqcrypto/signature/falcon_verify_key_manager.h"
+#include "tink/experimental/pqcrypto/signature/sphincs_sign_key_manager.h"
+#include "tink/experimental/pqcrypto/signature/sphincs_verify_key_manager.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
 #include "tink/registry.h"
@@ -45,7 +45,7 @@
 };
 
 TEST_F(PcqSignatureConfigTest, CheckDilithium) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used";
   }
 
@@ -69,7 +69,7 @@
 }
 
 TEST_F(PcqSignatureConfigTest, CheckSphincs) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used";
   }
 
@@ -93,7 +93,7 @@
 }
 
 TEST_F(PcqSignatureConfigTest, CheckFalcon) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used";
   }
 
diff --git a/cc/experimental/pqcrypto/signature/signature_config_util_test.cc b/cc/experimental/pqcrypto/signature/signature_config_util_test.cc
index 13bc155..f72e470 100644
--- a/cc/experimental/pqcrypto/signature/signature_config_util_test.cc
+++ b/cc/experimental/pqcrypto/signature/signature_config_util_test.cc
@@ -14,16 +14,16 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "tink/experimental/pqcrypto/signature/signature_config.h"
-
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "tink/config/tink_fips.h"
 #include "tink/experimental/pqcrypto/signature/dilithium_sign_key_manager.h"
 #include "tink/experimental/pqcrypto/signature/dilithium_verify_key_manager.h"
+#include "tink/experimental/pqcrypto/signature/signature_config.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
 #include "tink/registry.h"
@@ -45,7 +45,7 @@
 };
 
 TEST_F(PcqSignatureConfigTest, CheckStatus) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used";
   }
 
@@ -55,7 +55,7 @@
 // Tests that the PublicKeySignWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(PcqSignatureConfigTest, PublicKeySignWrapperRegistered) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used";
   }
 
@@ -91,7 +91,7 @@
 // Tests that the PublicKeyVerifyWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(PcqSignatureConfigTest, PublicKeyVerifyWrapperRegistered) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used";
   }
 
diff --git a/cc/experimental/pqcrypto/signature/sphincs_key_template.cc b/cc/experimental/pqcrypto/signature/sphincs_key_template.cc
index 038b9c2..fb07ad9 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_key_template.cc
+++ b/cc/experimental/pqcrypto/signature/sphincs_key_template.cc
@@ -72,7 +72,7 @@
 using ::google::crypto::tink::SphincsSignatureType;
 using ::google::crypto::tink::SphincsVariant;
 
-KeyTemplate* NewSphincsKeyTemplate(int32 private_key_size,
+KeyTemplate* NewSphincsKeyTemplate(int32_t private_key_size,
                                    SphincsHashType hash_type,
                                    SphincsVariant variant,
                                    SphincsSignatureType type) {
diff --git a/cc/experimental/pqcrypto/signature/sphincs_key_template_test.cc b/cc/experimental/pqcrypto/signature/sphincs_key_template_test.cc
index 071feb4..90cfdd4 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_key_template_test.cc
+++ b/cc/experimental/pqcrypto/signature/sphincs_key_template_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/sphincs_key_template.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
diff --git a/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.cc b/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.cc
index c481ae4..b5087ad 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.cc
+++ b/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/signature/sphincs_sign_key_manager.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
diff --git a/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.h b/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.h
index 03655e7..9c9d096 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.h
+++ b/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SPHINCS_SIGN_KEY_MANAGER_H_
 #define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SPHINCS_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager_test.cc b/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager_test.cc
index 7005f48..4b15c60 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager_test.cc
+++ b/cc/experimental/pqcrypto/signature/sphincs_sign_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/sphincs_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -104,7 +105,7 @@
 using SphincsSignKeyManagerTest = testing::TestWithParam<SphincsTestCase>;
 
 // Helper function that returns a valid sphincs key format.
-StatusOr<SphincsKeyFormat> CreateValidKeyFormat(int32 private_key_size,
+StatusOr<SphincsKeyFormat> CreateValidKeyFormat(int32_t private_key_size,
                                                 SphincsHashType hash_type,
                                                 SphincsVariant variant,
                                                 SphincsSignatureType type) {
diff --git a/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.cc b/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.cc
index 438ccf3..0acf19c 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.cc
+++ b/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/experimental/pqcrypto/signature/sphincs_verify_key_manager.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
diff --git a/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.h b/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.h
index d0d2283..70fe0b3 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.h
+++ b/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SPHINCS_VERIFY_KEY_MANAGER_H_
 #define TINK_EXPERIMENTAL_PQCRYPTO_SIGNATURE_SPHINCS_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager_test.cc b/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager_test.cc
index b17f57c..f8b8d56 100644
--- a/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager_test.cc
+++ b/cc/experimental/pqcrypto/signature/sphincs_verify_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/sphincs_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -102,7 +103,7 @@
 using SphincsVerifyKeyManagerTest = testing::TestWithParam<SphincsTestCase>;
 
 // Helper function that returns a valid sphincs private key.
-StatusOr<SphincsPrivateKey> CreateValidPrivateKey(int32 private_key_size,
+StatusOr<SphincsPrivateKey> CreateValidPrivateKey(int32_t private_key_size,
                                                   SphincsHashType hash_type,
                                                   SphincsVariant variant,
                                                   SphincsSignatureType type) {
@@ -117,7 +118,7 @@
 }
 
 // Helper function that returns a valid sphincs public key.
-StatusOr<SphincsPublicKey> CreateValidPublicKey(int32 private_key_size,
+StatusOr<SphincsPublicKey> CreateValidPublicKey(int32_t private_key_size,
                                                 SphincsHashType hash_type,
                                                 SphincsVariant variant,
                                                 SphincsSignatureType type) {
diff --git a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign.cc b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign.cc
index 6fd6d82..3e7e66a 100644
--- a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign.cc
@@ -20,6 +20,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <iterator>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign_test.cc b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign_test.cc
index a0dcba5..fb45802 100644
--- a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign_test.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/dilithium_avx2_sign.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify.cc b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify.cc
index b12c44f..d984a75 100644
--- a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <cstddef>
 #include <iterator>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify_test.cc b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify_test.cc
index 6d90fc3..9caf4d1 100644
--- a/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify_test.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/dilithium_avx2_verify.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/falcon_sign.cc b/cc/experimental/pqcrypto/signature/subtle/falcon_sign.cc
index 63196a2..af83e92 100644
--- a/cc/experimental/pqcrypto/signature/subtle/falcon_sign.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/falcon_sign.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/falcon_sign.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/falcon_sign_test.cc b/cc/experimental/pqcrypto/signature/subtle/falcon_sign_test.cc
index 639a69a..a23a1f6 100644
--- a/cc/experimental/pqcrypto/signature/subtle/falcon_sign_test.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/falcon_sign_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/falcon_sign.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils.h b/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils.h
index 302803a..81b1e08 100644
--- a/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils.h
+++ b/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils.h
@@ -102,7 +102,7 @@
 // This is an utility function that generates a new Falcon key pair.
 // This function is expected to be called from a key manager class.
 crypto::tink::util::StatusOr<FalconKeyPair> GenerateFalconKeyPair(
-    int32 private_key_size);
+    int32_t private_key_size);
 
 // Validates whether the private key size is safe to use for falcon signature.
 crypto::tink::util::Status ValidateFalconPrivateKeySize(int32_t key_size);
diff --git a/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils_test.cc b/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils_test.cc
index 0b05363..552a463 100644
--- a/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils_test.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/falcon_subtle_utils_test.cc
@@ -19,6 +19,7 @@
 #include <climits>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/experimental/pqcrypto/signature/subtle/falcon_verify.cc b/cc/experimental/pqcrypto/signature/subtle/falcon_verify.cc
index 1284c30..6ad208f 100644
--- a/cc/experimental/pqcrypto/signature/subtle/falcon_verify.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/falcon_verify.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/falcon_verify.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/falcon_verify_test.cc b/cc/experimental/pqcrypto/signature/subtle/falcon_verify_test.cc
index c1cad8d..0372e2d 100644
--- a/cc/experimental/pqcrypto/signature/subtle/falcon_verify_test.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/falcon_verify_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/falcon_verify.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/sphincs_helper_pqclean.h b/cc/experimental/pqcrypto/signature/subtle/sphincs_helper_pqclean.h
index 4e8c99e..12d71ea 100644
--- a/cc/experimental/pqcrypto/signature/subtle/sphincs_helper_pqclean.h
+++ b/cc/experimental/pqcrypto/signature/subtle/sphincs_helper_pqclean.h
@@ -34,7 +34,7 @@
 
   SphincsHelperPqclean(const SphincsHelperPqclean &other) = delete;
   SphincsHelperPqclean &operator=(const SphincsHelperPqclean &other) = delete;
-  virtual ~SphincsHelperPqclean() {}
+  virtual ~SphincsHelperPqclean() = default;
 
   // Arguments:
   //   sig - output signature (allocated buffer of size at least
diff --git a/cc/experimental/pqcrypto/signature/subtle/sphincs_sign.cc b/cc/experimental/pqcrypto/signature/subtle/sphincs_sign.cc
index ee8257d..48d96f1 100644
--- a/cc/experimental/pqcrypto/signature/subtle/sphincs_sign.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/sphincs_sign.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/sphincs_sign.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -51,7 +52,7 @@
 }
 
 util::StatusOr<std::string> SphincsSign::Sign(absl::string_view data) const {
-  util::StatusOr<int32> key_size_index =
+  util::StatusOr<int32_t> key_size_index =
       SphincsKeySizeToIndex(key_.GetKey().size());
   if (!key_size_index.ok()) {
     return key_size_index.status();
diff --git a/cc/experimental/pqcrypto/signature/subtle/sphincs_sign_test.cc b/cc/experimental/pqcrypto/signature/subtle/sphincs_sign_test.cc
index 58df17c..070190b 100644
--- a/cc/experimental/pqcrypto/signature/subtle/sphincs_sign_test.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/sphincs_sign_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/sphincs_sign.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/experimental/pqcrypto/signature/subtle/sphincs_subtle_utils.h b/cc/experimental/pqcrypto/signature/subtle/sphincs_subtle_utils.h
index 95fa9e9..964638c 100644
--- a/cc/experimental/pqcrypto/signature/subtle/sphincs_subtle_utils.h
+++ b/cc/experimental/pqcrypto/signature/subtle/sphincs_subtle_utils.h
@@ -62,7 +62,7 @@
   SphincsHashType hash_type;
   SphincsVariant variant;
   SphincsSignatureType sig_length_type;
-  int32 private_key_size;
+  int32_t private_key_size;
 };
 
 // Representation of the Sphincs private key.
@@ -127,10 +127,10 @@
     SphincsParamsPqclean params);
 
 // Validates whether the private key size is safe to use for sphincs signature.
-crypto::tink::util::Status ValidatePrivateKeySize(int32 key_size);
+crypto::tink::util::Status ValidatePrivateKeySize(int32_t key_size);
 
 // Validates whether the public key size is safe to use for sphincs signature.
-crypto::tink::util::Status ValidatePublicKeySize(int32 key_size);
+crypto::tink::util::Status ValidatePublicKeySize(int32_t key_size);
 
 // Validates whether the parameters are safe to use for sphincs signature.
 crypto::tink::util::Status ValidateParams(SphincsParamsPqclean params);
@@ -138,7 +138,7 @@
 
 // Convert the sphincs private key size to the appropiate index in the
 // pqclean functions array.
-crypto::tink::util::StatusOr<int32> SphincsKeySizeToIndex(int32 key_size);
+crypto::tink::util::StatusOr<int32_t> SphincsKeySizeToIndex(int32_t key_size);
 
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/experimental/pqcrypto/signature/subtle/sphincs_verify.cc b/cc/experimental/pqcrypto/signature/subtle/sphincs_verify.cc
index 88379fa..6582b86 100644
--- a/cc/experimental/pqcrypto/signature/subtle/sphincs_verify.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/sphincs_verify.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/sphincs_verify.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -54,7 +55,7 @@
 util::Status SphincsVerify::Verify(absl::string_view signature,
                                    absl::string_view data) const {
   SphincsParamsPqclean params = key_.GetParams();
-  util::StatusOr<int32> key_size_index =
+  util::StatusOr<int32_t> key_size_index =
       SphincsKeySizeToIndex(params.private_key_size);
   if (!key_size_index.ok()) {
     return key_size_index.status();
diff --git a/cc/experimental/pqcrypto/signature/subtle/sphincs_verify_test.cc b/cc/experimental/pqcrypto/signature/subtle/sphincs_verify_test.cc
index 544db98..f0a48c5 100644
--- a/cc/experimental/pqcrypto/signature/subtle/sphincs_verify_test.cc
+++ b/cc/experimental/pqcrypto/signature/subtle/sphincs_verify_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/experimental/pqcrypto/signature/subtle/sphincs_verify.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/extensions.bzl b/cc/extensions.bzl
new file mode 100644
index 0000000..6ab6fc7
--- /dev/null
+++ b/cc/extensions.bzl
@@ -0,0 +1,30 @@
+# Copyright 2023 Google LLC
+#
+# 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
+#
+#      http://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.
+
+"""Tink C++ Bazel Module extensions."""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+def _wycheproof_impl(_ctx):
+    # Commit from 2019-12-17.
+    http_archive(
+        name = "wycheproof",
+        strip_prefix = "wycheproof-d8ed1ba95ac4c551db67f410c06131c3bc00a97c",
+        url = "https://github.com/google/wycheproof/archive/d8ed1ba95ac4c551db67f410c06131c3bc00a97c.zip",
+        sha256 = "eb1d558071acf1aa6d677d7f1cabec2328d1cf8381496c17185bd92b52ce7545",
+    )
+
+wycheproof_extension = module_extension(
+    implementation = _wycheproof_impl,
+)
diff --git a/cc/hybrid/BUILD.bazel b/cc/hybrid/BUILD.bazel
index 27c119e..209de7b 100644
--- a/cc/hybrid/BUILD.bazel
+++ b/cc/hybrid/BUILD.bazel
@@ -149,22 +149,27 @@
     deps = [
         "//:aead",
         "//:deterministic_aead",
-        "//:key_manager",
-        "//:registry",
+        "//aead:aes_ctr_hmac_aead_key_manager",
         "//daead/subtle:aead_or_daead",
         "//proto:aes_ctr_cc_proto",
         "//proto:aes_ctr_hmac_aead_cc_proto",
         "//proto:aes_gcm_cc_proto",
         "//proto:aes_siv_cc_proto",
+        "//proto:common_cc_proto",
         "//proto:hmac_cc_proto",
         "//proto:tink_cc_proto",
         "//proto:xchacha20_poly1305_cc_proto",
+        "//subtle:aes_gcm_boringssl",
+        "//subtle:aes_siv_boringssl",
+        "//subtle:xchacha20_poly1305_boringssl",
         "//util:errors",
         "//util:protobuf_helper",
         "//util:secret_data",
         "//util:statusor",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
     ],
 )
 
@@ -284,15 +289,15 @@
         ":ecies_aead_hkdf_public_key_manager",
         ":hybrid_config",
         ":hybrid_key_templates",
-        "//:config",
         "//:hybrid_decrypt",
         "//:hybrid_encrypt",
         "//:keyset_handle",
         "//:registry",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
+        "@boringssl//:crypto",
         "@com_google_absl//absl/status",
         "@com_google_googletest//:gtest_main",
     ],
@@ -372,7 +377,6 @@
         ":ecies_aead_hkdf_public_key_manager",
         ":hybrid_config",
         ":hybrid_decrypt_factory",
-        "//:config",
         "//:crypto_format",
         "//:hybrid_decrypt",
         "//:hybrid_encrypt",
@@ -394,7 +398,6 @@
     deps = [
         ":hybrid_config",
         ":hybrid_encrypt_factory",
-        "//:config",
         "//:crypto_format",
         "//:hybrid_encrypt",
         "//:keyset_handle",
@@ -433,7 +436,6 @@
     srcs = ["ecies_aead_hkdf_dem_helper_test.cc"],
     deps = [
         ":ecies_aead_hkdf_dem_helper",
-        "//:registry",
         "//aead:aes_gcm_key_manager",
         "//daead:aes_siv_key_manager",
         "//util:secret_data",
@@ -452,7 +454,6 @@
         ":ecies_aead_hkdf_hybrid_decrypt",
         ":ecies_aead_hkdf_hybrid_encrypt",
         "//:hybrid_decrypt",
-        "//:registry",
         "//aead:aes_ctr_hmac_aead_key_manager",
         "//aead:aes_gcm_key_manager",
         "//aead:xchacha20_poly1305_key_manager",
@@ -479,7 +480,6 @@
     deps = [
         ":ecies_aead_hkdf_hybrid_encrypt",
         "//:hybrid_encrypt",
-        "//:registry",
         "//aead:aes_gcm_key_manager",
         "//internal:ec_util",
         "//proto:common_cc_proto",
diff --git a/cc/hybrid/BUILD.gn b/cc/hybrid/BUILD.gn
index f796359..9a507bc 100644
--- a/cc/hybrid/BUILD.gn
+++ b/cc/hybrid/BUILD.gn
@@ -176,18 +176,23 @@
   public_deps = [
     "//third_party/abseil-cpp/absl/memory:memory",
     "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/status:statusor",
+    "//third_party/abseil-cpp/absl/strings:strings",
     "//third_party/tink/cc:aead",
     "//third_party/tink/cc:deterministic_aead",
-    "//third_party/tink/cc:key_manager",
-    "//third_party/tink/cc:registry",
+    "//third_party/tink/cc/aead:aes_ctr_hmac_aead_key_manager",
     "//third_party/tink/cc/daead/subtle:aead_or_daead",
     "//third_party/tink/cc/proto:aes_ctr_hmac_aead_proto",
     "//third_party/tink/cc/proto:aes_ctr_proto",
     "//third_party/tink/cc/proto:aes_gcm_proto",
     "//third_party/tink/cc/proto:aes_siv_proto",
+    "//third_party/tink/cc/proto:common_proto",
     "//third_party/tink/cc/proto:hmac_proto",
     "//third_party/tink/cc/proto:tink_proto",
     "//third_party/tink/cc/proto:xchacha20_poly1305_proto",
+    "//third_party/tink/cc/subtle:aes_gcm_boringssl",
+    "//third_party/tink/cc/subtle:aes_siv_boringssl",
+    "//third_party/tink/cc/subtle:xchacha20_poly1305_boringssl",
     "//third_party/tink/cc/util:errors",
     "//third_party/tink/cc/util:protobuf_helper",
     "//third_party/tink/cc/util:secret_data",
diff --git a/cc/hybrid/CMakeLists.txt b/cc/hybrid/CMakeLists.txt
index 67e17b7..cd213c8 100644
--- a/cc/hybrid/CMakeLists.txt
+++ b/cc/hybrid/CMakeLists.txt
@@ -137,11 +137,15 @@
   DEPS
     absl::memory
     absl::status
+    absl::statusor
+    absl::strings
     tink::core::aead
     tink::core::deterministic_aead
-    tink::core::key_manager
-    tink::core::registry
+    tink::aead::aes_ctr_hmac_aead_key_manager
     tink::daead::subtle::aead_or_daead
+    tink::subtle::aes_gcm_boringssl
+    tink::subtle::aes_siv_boringssl
+    tink::subtle::xchacha20_poly1305_boringssl
     tink::util::errors
     tink::util::protobuf_helper
     tink::util::secret_data
@@ -150,6 +154,7 @@
     tink::proto::aes_ctr_hmac_aead_cc_proto
     tink::proto::aes_gcm_cc_proto
     tink::proto::aes_siv_cc_proto
+    tink::proto::common_cc_proto
     tink::proto::hmac_cc_proto
     tink::proto::tink_cc_proto
     tink::proto::xchacha20_poly1305_cc_proto
@@ -258,12 +263,12 @@
     tink::hybrid::hybrid_key_templates
     gmock
     absl::status
-    tink::core::config
+    crypto
     tink::core::hybrid_decrypt
     tink::core::hybrid_encrypt
     tink::core::keyset_handle
     tink::core::registry
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::status
     tink::util::test_matchers
     tink::util::test_util
@@ -343,7 +348,6 @@
     tink::hybrid::hybrid_decrypt_factory
     gmock
     absl::memory
-    tink::core::config
     tink::core::crypto_format
     tink::core::hybrid_decrypt
     tink::core::hybrid_encrypt
@@ -363,7 +367,6 @@
     tink::hybrid::hybrid_config
     tink::hybrid::hybrid_encrypt_factory
     gmock
-    tink::core::config
     tink::core::crypto_format
     tink::core::hybrid_encrypt
     tink::core::keyset_handle
@@ -403,7 +406,6 @@
     tink::hybrid::ecies_aead_hkdf_dem_helper
     gmock
     absl::status
-    tink::core::registry
     tink::aead::aes_gcm_key_manager
     tink::daead::aes_siv_key_manager
     tink::util::secret_data
@@ -421,7 +423,6 @@
     gmock
     absl::memory
     tink::core::hybrid_decrypt
-    tink::core::registry
     tink::aead::aes_ctr_hmac_aead_key_manager
     tink::aead::aes_gcm_key_manager
     tink::aead::xchacha20_poly1305_key_manager
@@ -447,7 +448,6 @@
     gmock
     absl::memory
     tink::core::hybrid_encrypt
-    tink::core::registry
     tink::aead::aes_gcm_key_manager
     tink::internal::ec_util
     tink::util::enums
diff --git a/cc/hybrid/ecies_aead_hkdf_dem_helper.cc b/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
index 9c169e1..6e46f4a 100644
--- a/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
+++ b/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
@@ -16,15 +16,22 @@
 
 #include "tink/hybrid/ecies_aead_hkdf_dem_helper.h"
 
+#include <stdint.h>
+
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
 #include "tink/deterministic_aead.h"
-#include "tink/key_manager.h"
-#include "tink/registry.h"
+#include "tink/subtle/aes_gcm_boringssl.h"
+#include "tink/subtle/aes_siv_boringssl.h"
+#include "tink/subtle/xchacha20_poly1305_boringssl.h"
 #include "tink/util/errors.h"
 #include "tink/util/protobuf_helper.h"
 #include "tink/util/statusor.h"
@@ -43,69 +50,28 @@
 using ::crypto::tink::subtle::AeadOrDaead;
 using ::google::crypto::tink::AesCtrHmacAeadKey;
 using ::google::crypto::tink::AesCtrHmacAeadKeyFormat;
-using ::google::crypto::tink::AesGcmKey;
 using ::google::crypto::tink::AesGcmKeyFormat;
-using ::google::crypto::tink::AesSivKey;
 using ::google::crypto::tink::AesSivKeyFormat;
 using ::google::crypto::tink::KeyTemplate;
-using ::google::crypto::tink::XChaCha20Poly1305Key;
 using ::google::crypto::tink::XChaCha20Poly1305KeyFormat;
 
-// Internal implementaton of the EciesAeadHkdfDemHelper class, paremetrized by
-// the Primitive used for data encapsulation (i.e Aead or DeterministicAead).
-template <class EncryptionPrimitive>
-class EciesAeadHkdfDemHelperImpl : public EciesAeadHkdfDemHelper {
- public:
-  static util::StatusOr<std::unique_ptr<const EciesAeadHkdfDemHelper>> New(
-      const google::crypto::tink::KeyTemplate& dem_key_template,
-      const DemKeyParams& key_params, const std::string& dem_type_url) {
-    auto key_manager_or =
-        Registry::get_key_manager<EncryptionPrimitive>(dem_type_url);
-    if (!key_manager_or.ok()) {
-      return ToStatusF(
-          absl::StatusCode::kFailedPrecondition,
-          "No manager for DEM key type '%s' found in the registry.",
-          dem_type_url);
-    }
-    const KeyManager<EncryptionPrimitive>* key_manager = key_manager_or.value();
-    return {absl::make_unique<EciesAeadHkdfDemHelperImpl<EncryptionPrimitive>>(
-        key_manager, dem_key_template, key_params)};
+crypto::tink::util::StatusOr<std::unique_ptr<AeadOrDaead>> Wrap(
+    crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::Aead>> aead_or) {
+  if (!aead_or.ok()) {
+    return aead_or.status();
   }
+  return std::make_unique<AeadOrDaead>(std::move(aead_or.value()));
+}
 
-  EciesAeadHkdfDemHelperImpl(
-      const KeyManager<EncryptionPrimitive>* key_manager,
-      const google::crypto::tink::KeyTemplate& key_template,
-      DemKeyParams key_params)
-      : EciesAeadHkdfDemHelper(key_template, key_params),
-        key_manager_(key_manager) {}
-
- protected:
-  crypto::tink::util::StatusOr<
-      std::unique_ptr<crypto::tink::subtle::AeadOrDaead>>
-  GetAeadOrDaead(const util::SecretData& symmetric_key_value) const override {
-    if (symmetric_key_value.size() != key_params_.key_size_in_bytes) {
-      return util::Status(absl::StatusCode::kInternal,
-                          "Wrong length of symmetric key.");
-    }
-    auto key_or = key_manager_->get_key_factory().NewKey(key_template_.value());
-    if (!key_or.ok()) return key_or.status();
-    auto key = std::move(key_or).value();
-    if (!ReplaceKeyBytes(symmetric_key_value, key.get())) {
-      return util::Status(absl::StatusCode::kInternal,
-                          "Generation of DEM-key failed.");
-    }
-
-    util::StatusOr<std::unique_ptr<EncryptionPrimitive>> primitive_or =
-        key_manager_->GetPrimitive(*key);
-    ZeroKeyBytes(key.get());
-
-    if (!primitive_or.ok()) return primitive_or.status();
-    return absl::make_unique<AeadOrDaead>(std::move(primitive_or.value()));
+crypto::tink::util::StatusOr<std::unique_ptr<AeadOrDaead>> Wrap(
+    crypto::tink::util::StatusOr<
+        std::unique_ptr<crypto::tink::DeterministicAead>>
+        daead_or) {
+  if (!daead_or.ok()) {
+    return daead_or.status();
   }
-
- private:
-  const KeyManager<EncryptionPrimitive>* key_manager_;  // not owned
-};
+  return std::make_unique<AeadOrDaead>(std::move(daead_or.value()));
+}
 
 }  // namespace
 
@@ -129,7 +95,10 @@
     uint32_t dem_key_size = key_format.aes_ctr_key_format().key_size() +
                             key_format.hmac_key_format().key_size();
     return {{AES_CTR_HMAC_AEAD_KEY, dem_key_size,
-             key_format.aes_ctr_key_format().key_size()}};
+             key_format.aes_ctr_key_format().key_size(),
+             key_format.aes_ctr_key_format().params().iv_size(),
+             key_format.hmac_key_format().params().hash(),
+             key_format.hmac_key_format().params().tag_size()}};
   }
   if (type_url ==
       "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key") {
@@ -158,86 +127,41 @@
   auto key_params_or = GetKeyParams(dem_key_template);
   if (!key_params_or.ok()) return key_params_or.status();
   DemKeyParams key_params = key_params_or.value();
-  const std::string& dem_type_url = dem_key_template.type_url();
-
-  if (key_params.key_type == AES_SIV_KEY) {
-    return EciesAeadHkdfDemHelperImpl<DeterministicAead>::New(
-        dem_key_template, key_params, dem_type_url);
-  } else {
-    return EciesAeadHkdfDemHelperImpl<Aead>::New(dem_key_template, key_params,
-                                                 dem_type_url);
-  }
+  return absl::WrapUnique<const EciesAeadHkdfDemHelper>(
+      new EciesAeadHkdfDemHelper(dem_key_template, key_params));
 }
 
-bool EciesAeadHkdfDemHelper::ReplaceKeyBytes(
-    const util::SecretData& key_bytes,
-    portable_proto::MessageLite* proto) const {
+crypto::tink::util::StatusOr<std::unique_ptr<AeadOrDaead>>
+EciesAeadHkdfDemHelper::GetAeadOrDaead(
+    const util::SecretData& symmetric_key_value) const {
+  if (symmetric_key_value.size() != key_params_.key_size_in_bytes) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Wrong length of symmetric key.");
+  }
   switch (key_params_.key_type) {
-    case AES_GCM_KEY: {
-      AesGcmKey* key = static_cast<AesGcmKey*>(proto);
-      key->set_key_value(std::string(util::SecretDataAsStringView(key_bytes)));
-      return true;
-    }
+    case AES_GCM_KEY:
+      return Wrap(subtle::AesGcmBoringSsl::New(symmetric_key_value));
     case AES_CTR_HMAC_AEAD_KEY: {
-      AesCtrHmacAeadKey* key = static_cast<AesCtrHmacAeadKey*>(proto);
-      auto aes_ctr_key = key->mutable_aes_ctr_key();
+      AesCtrHmacAeadKey key;
+      auto aes_ctr_key = key.mutable_aes_ctr_key();
+      aes_ctr_key->mutable_params()->set_iv_size(
+          key_params_.aes_ctr_key_iv_size_in_bytes);
       aes_ctr_key->set_key_value(
-          std::string(util::SecretDataAsStringView(key_bytes).substr(
-              0, key_params_.aes_ctr_key_size_in_bytes)));
-      auto hmac_key = key->mutable_hmac_key();
+          std::string(util::SecretDataAsStringView(symmetric_key_value)
+                          .substr(0, key_params_.aes_ctr_key_size_in_bytes)));
+      auto hmac_key = key.mutable_hmac_key();
+      hmac_key->mutable_params()->set_tag_size(
+          key_params_.hmac_key_tag_size_in_bytes);
+      hmac_key->mutable_params()->set_hash(key_params_.hmac_key_hash);
       hmac_key->set_key_value(
-          std::string(util::SecretDataAsStringView(key_bytes).substr(
-              key_params_.aes_ctr_key_size_in_bytes)));
-      return true;
+          std::string(util::SecretDataAsStringView(symmetric_key_value)
+                          .substr(key_params_.aes_ctr_key_size_in_bytes)));
+      return Wrap(AesCtrHmacAeadKeyManager().GetPrimitive<Aead>(key));
     }
-    case XCHACHA20_POLY1305_KEY: {
-      XChaCha20Poly1305Key* key = static_cast<XChaCha20Poly1305Key*>(proto);
-      key->set_key_value(std::string(util::SecretDataAsStringView(key_bytes)));
-      return true;
-    }
-    case AES_SIV_KEY: {
-      AesSivKey* key = static_cast<AesSivKey*>(proto);
-      key->set_key_value(std::string(util::SecretDataAsStringView(key_bytes)));
-      return true;
-    }
-  }
-  return false;
-}
-
-void EciesAeadHkdfDemHelper::ZeroKeyBytes(
-    portable_proto::MessageLite* proto) const {
-  switch (key_params_.key_type) {
-    case AES_GCM_KEY: {
-      AesGcmKey* key = static_cast<AesGcmKey*>(proto);
-      std::unique_ptr<std::string> key_value =
-          absl::WrapUnique(key->release_key_value());
-      util::SafeZeroString(key_value.get());
-      break;
-    }
-    case AES_CTR_HMAC_AEAD_KEY: {
-      AesCtrHmacAeadKey* key = static_cast<AesCtrHmacAeadKey*>(proto);
-      std::unique_ptr<std::string> aes_ctr_key_value =
-          absl::WrapUnique(key->mutable_aes_ctr_key()->release_key_value());
-      util::SafeZeroString(aes_ctr_key_value.get());
-      std::unique_ptr<std::string> hmac_key_value =
-          absl::WrapUnique(key->mutable_hmac_key()->release_key_value());
-      util::SafeZeroString(hmac_key_value.get());
-      break;
-    }
-    case XCHACHA20_POLY1305_KEY: {
-      XChaCha20Poly1305Key* key = static_cast<XChaCha20Poly1305Key*>(proto);
-      std::unique_ptr<std::string> key_value =
-          absl::WrapUnique(key->release_key_value());
-      util::SafeZeroString(key_value.get());
-      break;
-    }
-    case AES_SIV_KEY: {
-      AesSivKey* key = static_cast<AesSivKey*>(proto);
-      std::unique_ptr<std::string> key_value =
-          absl::WrapUnique(key->release_key_value());
-      util::SafeZeroString(key_value.get());
-      break;
-    }
+    case XCHACHA20_POLY1305_KEY:
+      return Wrap(subtle::XChacha20Poly1305BoringSsl::New(symmetric_key_value));
+    case AES_SIV_KEY:
+      return Wrap(subtle::AesSivBoringSsl::New(symmetric_key_value));
   }
 }
 
diff --git a/cc/hybrid/ecies_aead_hkdf_dem_helper.h b/cc/hybrid/ecies_aead_hkdf_dem_helper.h
index 0f6d160..c00970b 100644
--- a/cc/hybrid/ecies_aead_hkdf_dem_helper.h
+++ b/cc/hybrid/ecies_aead_hkdf_dem_helper.h
@@ -17,14 +17,16 @@
 #ifndef TINK_HYBRID_ECIES_AEAD_HKDF_DEM_HELPER_H_
 #define TINK_HYBRID_ECIES_AEAD_HKDF_DEM_HELPER_H_
 
+#include <stdint.h>
+
 #include <memory>
 
 #include "tink/aead.h"
 #include "tink/daead/subtle/aead_or_daead.h"
-#include "tink/key_manager.h"
 #include "tink/util/protobuf_helper.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/statusor.h"
+#include "proto/common.pb.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
@@ -38,7 +40,7 @@
   crypto::tink::util::StatusOr<std::unique_ptr<const EciesAeadHkdfDemHelper>>
       New(const google::crypto::tink::KeyTemplate& dem_key_template);
 
-  virtual ~EciesAeadHkdfDemHelper() {}
+  virtual ~EciesAeadHkdfDemHelper() = default;
 
   // Returns the size of the DEM-key in bytes.
   uint32_t dem_key_size_in_bytes() const {
@@ -50,7 +52,7 @@
   // be of length dem_key_size_in_bytes().
   virtual crypto::tink::util::StatusOr<
       std::unique_ptr<crypto::tink::subtle::AeadOrDaead>>
-  GetAeadOrDaead(const util::SecretData& symmetric_key_value) const = 0;
+  GetAeadOrDaead(const util::SecretData& symmetric_key_value) const;
 
  protected:
   enum DemKeyType {
@@ -64,6 +66,9 @@
     DemKeyType key_type;
     uint32_t key_size_in_bytes;
     uint32_t aes_ctr_key_size_in_bytes;
+    uint32_t aes_ctr_key_iv_size_in_bytes;
+    google::crypto::tink::HashType hmac_key_hash;
+    uint32_t hmac_key_tag_size_in_bytes;
   };
 
   EciesAeadHkdfDemHelper(const google::crypto::tink::KeyTemplate& key_template,
@@ -73,11 +78,6 @@
   static util::StatusOr<DemKeyParams> GetKeyParams(
       const ::google::crypto::tink::KeyTemplate& key_template);
 
-  bool ReplaceKeyBytes(const util::SecretData& key_bytes,
-                       portable_proto::MessageLite* proto) const;
-
-  void ZeroKeyBytes(portable_proto::MessageLite* proto) const;
-
   const google::crypto::tink::KeyTemplate key_template_;
   const DemKeyParams key_params_;
 };
diff --git a/cc/hybrid/ecies_aead_hkdf_dem_helper_test.cc b/cc/hybrid/ecies_aead_hkdf_dem_helper_test.cc
index 64393ad..a2bbd3a 100644
--- a/cc/hybrid/ecies_aead_hkdf_dem_helper_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_dem_helper_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/ecies_aead_hkdf_dem_helper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -24,7 +25,6 @@
 #include "absl/status/status.h"
 #include "tink/aead/aes_gcm_key_manager.h"
 #include "tink/daead/aes_siv_key_manager.h"
-#include "tink/registry.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
@@ -69,8 +69,6 @@
   key_format.set_key_size(16);
   std::unique_ptr<AesGcmKeyManager> key_manager(new AesGcmKeyManager());
   std::string dem_key_type = key_manager->get_key_type();
-  ASSERT_THAT(Registry::RegisterKeyTypeManager(std::move(key_manager), true),
-              IsOk());
 
   google::crypto::tink::KeyTemplate dem_key_template;
   dem_key_template.set_type_url(dem_key_type);
@@ -96,8 +94,6 @@
   key_format.set_key_size(64);
   std::unique_ptr<AesSivKeyManager> key_manager(new AesSivKeyManager());
   std::string dem_key_type = key_manager->get_key_type();
-  ASSERT_THAT(Registry::RegisterKeyTypeManager(std::move(key_manager), true),
-              IsOk());
 
   google::crypto::tink::KeyTemplate dem_key_template;
   dem_key_template.set_type_url(dem_key_type);
diff --git a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt.cc b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt.cc
index 497b95f..d0c1377 100644
--- a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt.cc
+++ b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/ecies_aead_hkdf_hybrid_decrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
index 44b79c2..6bfc624 100644
--- a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
@@ -16,8 +16,10 @@
 
 #include "tink/hybrid/ecies_aead_hkdf_hybrid_decrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
@@ -29,7 +31,6 @@
 #include "tink/hybrid_decrypt.h"
 #include "tink/internal/ec_util.h"
 #include "tink/internal/ssl_util.h"
-#include "tink/registry.h"
 #include "tink/subtle/random.h"
 #include "tink/util/enums.h"
 #include "tink/util/statusor.h"
@@ -215,21 +216,6 @@
   }
 }
 
-TEST_F(EciesAeadHkdfHybridDecryptTest, testGettingHybridEncryptWithoutManager) {
-  // Prepare an ECIES key.
-  Registry::Reset();
-  auto ecies_key = test::GetEciesAesGcmHkdfTestKey(EllipticCurveType::NIST_P256,
-                                                   EcPointFormat::UNCOMPRESSED,
-                                                   HashType::SHA256, 32);
-
-  // Try to get a HybridEncrypt primitive without DEM key manager.
-  auto bad_result(EciesAeadHkdfHybridDecrypt::New(ecies_key));
-  EXPECT_FALSE(bad_result.ok());
-  EXPECT_EQ(absl::StatusCode::kFailedPrecondition, bad_result.status().code());
-  EXPECT_PRED_FORMAT2(testing::IsSubstring, "No manager for DEM",
-                      std::string(bad_result.status().message()));
-}
-
 TEST_F(EciesAeadHkdfHybridDecryptTest, testAesGcmHybridDecryption) {
   // Register DEM key manager.
   std::string dem_key_type = AesGcmKeyManager().get_key_type();
diff --git a/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt.cc b/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt.cc
index 2f70a7c..a8af6c6 100644
--- a/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt.cc
+++ b/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/ecies_aead_hkdf_hybrid_encrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc b/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc
index f2c4277..610fa5a 100644
--- a/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/ecies_aead_hkdf_hybrid_encrypt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -24,7 +25,6 @@
 #include "tink/aead/aes_gcm_key_manager.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/internal/ec_util.h"
-#include "tink/registry.h"
 #include "tink/util/enums.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_util.h"
@@ -113,19 +113,6 @@
       HashType::SHA256,
       32);
 
-  // Try to get a HybridEncrypt primitive without DEM key manager.
-  auto bad_result(EciesAeadHkdfHybridEncrypt::New(ecies_key.public_key()));
-  EXPECT_FALSE(bad_result.ok());
-  EXPECT_EQ(absl::StatusCode::kFailedPrecondition, bad_result.status().code());
-  EXPECT_PRED_FORMAT2(testing::IsSubstring, "No manager for DEM",
-                      std::string(bad_result.status().message()));
-
-  // Register DEM key manager.
-  ASSERT_TRUE(Registry::RegisterKeyTypeManager(
-                  absl::make_unique<AesGcmKeyManager>(), true)
-                  .ok());
-  std::string dem_key_type = AesGcmKeyManager().get_key_type();
-
   // Generate and test many keys with various parameters.
   std::string plaintext = "some plaintext";
   std::string context_info = "some context info";
diff --git a/cc/hybrid/ecies_aead_hkdf_private_key_manager.h b/cc/hybrid/ecies_aead_hkdf_private_key_manager.h
index 810f37c..ec82996 100644
--- a/cc/hybrid/ecies_aead_hkdf_private_key_manager.h
+++ b/cc/hybrid/ecies_aead_hkdf_private_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_HYBRID_ECIES_AEAD_HKDF_PRIVATE_KEY_MANAGER_H_
 #define TINK_HYBRID_ECIES_AEAD_HKDF_PRIVATE_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/hybrid/ecies_aead_hkdf_public_key_manager.h b/cc/hybrid/ecies_aead_hkdf_public_key_manager.h
index 10ecedf..9c6d671 100644
--- a/cc/hybrid/ecies_aead_hkdf_public_key_manager.h
+++ b/cc/hybrid/ecies_aead_hkdf_public_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_HYBRID_ECIES_AEAD_HKDF_PUBLIC_KEY_MANAGER_H_
 #define TINK_HYBRID_ECIES_AEAD_HKDF_PUBLIC_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/hybrid/failing_hybrid.cc b/cc/hybrid/failing_hybrid.cc
index 0496d17..6cda606 100644
--- a/cc/hybrid/failing_hybrid.cc
+++ b/cc/hybrid/failing_hybrid.cc
@@ -15,11 +15,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/hybrid/failing_hybrid.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
-#include "tink/hybrid_encrypt.h"
 #include "absl/strings/string_view.h"
+#include "tink/hybrid_encrypt.h"
 
 namespace crypto {
 namespace tink {
diff --git a/cc/hybrid/failing_hybrid.h b/cc/hybrid/failing_hybrid.h
index 89e35ff..9009d2c 100644
--- a/cc/hybrid/failing_hybrid.h
+++ b/cc/hybrid/failing_hybrid.h
@@ -16,6 +16,7 @@
 #ifndef TINK_HYBRID_FAILING_HYBRID_H_
 #define TINK_HYBRID_FAILING_HYBRID_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
diff --git a/cc/hybrid/hybrid_config.cc b/cc/hybrid/hybrid_config.cc
index 8a3bb0e..bb7dcac 100644
--- a/cc/hybrid/hybrid_config.cc
+++ b/cc/hybrid/hybrid_config.cc
@@ -28,20 +28,13 @@
 #include "tink/util/status.h"
 #include "proto/config.pb.h"
 
-using google::crypto::tink::RegistryConfig;
-
 namespace crypto {
 namespace tink {
 
 // static
-const RegistryConfig& HybridConfig::Latest() {
-  static const RegistryConfig* config = new RegistryConfig();
-  return *config;
-}
-
-// static
 util::Status HybridConfig::Register() {
   auto status = AeadConfig::Register();
+  if (!status.ok()) return status;
 
   // Register primitive wrappers.
   status = Registry::RegisterPrimitiveWrapper(
diff --git a/cc/hybrid/hybrid_config.h b/cc/hybrid/hybrid_config.h
index fdfbc37..adea356 100644
--- a/cc/hybrid/hybrid_config.h
+++ b/cc/hybrid/hybrid_config.h
@@ -37,16 +37,6 @@
 //
 class HybridConfig {
  public:
-  static constexpr char kHybridDecryptCatalogueName[] = "TinkHybridDecrypt";
-  static constexpr char kHybridDecryptPrimitiveName[] = "HybridDecrypt";
-  static constexpr char kHybridEncryptCatalogueName[] = "TinkHybridEncrypt";
-  static constexpr char kHybridEncryptPrimitiveName[] = "HybridEncrypt";
-
-  // Returns config with implementations of HybridEncrypt and HybridDecrypt
-  // supported in the current Tink release.
-  ABSL_DEPRECATED("This is not supported anymore.")
-  static const google::crypto::tink::RegistryConfig& Latest();
-
   // Registers HybridEncrypt and HybridDecrypt primitive wrappers, and key
   // managers for all implementations of HybridEncrypt and HybridDecrypt from
   // the current Tink release.
diff --git a/cc/hybrid/hybrid_config_test.cc b/cc/hybrid/hybrid_config_test.cc
index d09a281..bb5467c 100644
--- a/cc/hybrid/hybrid_config_test.cc
+++ b/cc/hybrid/hybrid_config_test.cc
@@ -23,13 +23,13 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
-#include "tink/config.h"
-#include "tink/config/tink_fips.h"
+#include "openssl/crypto.h"
 #include "tink/hybrid/ecies_aead_hkdf_private_key_manager.h"
 #include "tink/hybrid/ecies_aead_hkdf_public_key_manager.h"
 #include "tink/hybrid/hybrid_key_templates.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/hybrid_encrypt.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
@@ -51,7 +51,7 @@
 };
 
 TEST_F(HybridConfigTest, Basic) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
@@ -77,7 +77,7 @@
 // Tests that the HybridEncryptWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(HybridConfigTest, EncryptWrapperRegistered) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
@@ -112,7 +112,7 @@
 // Tests that the HybridDecryptWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(HybridConfigTest, DecryptWrapperRegistered) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
@@ -146,8 +146,8 @@
 
 // FIPS-only mode tests
 TEST_F(HybridConfigTest, RegisterNonFipsTemplates) {
-  if (!IsFipsModeEnabled()) {
-    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode with BoringCrypto";
   }
 
   EXPECT_THAT(HybridConfig::Register(), IsOk());
diff --git a/cc/hybrid/hybrid_decrypt_factory.cc b/cc/hybrid/hybrid_decrypt_factory.cc
index 401d257..0cb9d26 100644
--- a/cc/hybrid/hybrid_decrypt_factory.cc
+++ b/cc/hybrid/hybrid_decrypt_factory.cc
@@ -16,15 +16,16 @@
 
 #include "tink/hybrid/hybrid_decrypt_factory.h"
 
+#include <memory>
+
+#include "tink/hybrid/hybrid_decrypt_wrapper.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
-#include "tink/hybrid/hybrid_decrypt_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
-
 namespace crypto {
 namespace tink {
 
diff --git a/cc/hybrid/hybrid_decrypt_factory.h b/cc/hybrid/hybrid_decrypt_factory.h
index 5218697..d2e3e43 100644
--- a/cc/hybrid/hybrid_decrypt_factory.h
+++ b/cc/hybrid/hybrid_decrypt_factory.h
@@ -17,6 +17,8 @@
 #ifndef TINK_HYBRID_HYBRID_DECRYPT_FACTORY_H_
 #define TINK_HYBRID_HYBRID_DECRYPT_FACTORY_H_
 
+#include <memory>
+
 #include "absl/base/macros.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/key_manager.h"
diff --git a/cc/hybrid/hybrid_decrypt_factory_test.cc b/cc/hybrid/hybrid_decrypt_factory_test.cc
index 2dce36a..c2b405a 100644
--- a/cc/hybrid/hybrid_decrypt_factory_test.cc
+++ b/cc/hybrid/hybrid_decrypt_factory_test.cc
@@ -16,12 +16,12 @@
 
 #include "tink/hybrid/hybrid_decrypt_factory.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
-#include "tink/config.h"
 #include "tink/crypto_format.h"
 #include "tink/hybrid/ecies_aead_hkdf_public_key_manager.h"
 #include "tink/hybrid/hybrid_config.h"
@@ -34,7 +34,6 @@
 #include "proto/ecies_aead_hkdf.pb.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::TestKeysetHandle;
 using crypto::tink::test::AddRawKey;
 using crypto::tink::test::AddTinkKey;
 using google::crypto::tink::EciesAeadHkdfPrivateKey;
diff --git a/cc/hybrid/hybrid_decrypt_wrapper.cc b/cc/hybrid/hybrid_decrypt_wrapper.cc
index 295f9e6..f07440b 100644
--- a/cc/hybrid/hybrid_decrypt_wrapper.cc
+++ b/cc/hybrid/hybrid_decrypt_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/hybrid_decrypt_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -51,7 +52,7 @@
       absl::string_view ciphertext,
       absl::string_view context_info) const override;
 
-  ~HybridDecryptSetWrapper() override {}
+  ~HybridDecryptSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<HybridDecrypt>> hybrid_decrypt_set_;
diff --git a/cc/hybrid/hybrid_decrypt_wrapper.h b/cc/hybrid/hybrid_decrypt_wrapper.h
index 0d2ea55..14ea1d7 100644
--- a/cc/hybrid/hybrid_decrypt_wrapper.h
+++ b/cc/hybrid/hybrid_decrypt_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_HYBRID_HYBRID_DECRYPT_WRAPPER_H_
 #define TINK_HYBRID_HYBRID_DECRYPT_WRAPPER_H_
 
+#include <memory>
+
 #include "absl/strings/string_view.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/primitive_set.h"
diff --git a/cc/hybrid/hybrid_encrypt_factory.cc b/cc/hybrid/hybrid_encrypt_factory.cc
index 9e2d92f..57e8c06 100644
--- a/cc/hybrid/hybrid_encrypt_factory.cc
+++ b/cc/hybrid/hybrid_encrypt_factory.cc
@@ -16,15 +16,16 @@
 
 #include "tink/hybrid/hybrid_encrypt_factory.h"
 
+#include <memory>
+
+#include "tink/hybrid/hybrid_encrypt_wrapper.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
-#include "tink/hybrid/hybrid_encrypt_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
-
 namespace crypto {
 namespace tink {
 
diff --git a/cc/hybrid/hybrid_encrypt_factory.h b/cc/hybrid/hybrid_encrypt_factory.h
index 05f776c..23b6f9d 100644
--- a/cc/hybrid/hybrid_encrypt_factory.h
+++ b/cc/hybrid/hybrid_encrypt_factory.h
@@ -17,6 +17,8 @@
 #ifndef TINK_HYBRID_HYBRID_ENCRYPT_FACTORY_H_
 #define TINK_HYBRID_HYBRID_ENCRYPT_FACTORY_H_
 
+#include <memory>
+
 #include "absl/base/macros.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/key_manager.h"
diff --git a/cc/hybrid/hybrid_encrypt_factory_test.cc b/cc/hybrid/hybrid_encrypt_factory_test.cc
index 415c786..14bd2d4 100644
--- a/cc/hybrid/hybrid_encrypt_factory_test.cc
+++ b/cc/hybrid/hybrid_encrypt_factory_test.cc
@@ -20,7 +20,6 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "tink/config.h"
 #include "tink/crypto_format.h"
 #include "tink/hybrid/hybrid_config.h"
 #include "tink/hybrid_encrypt.h"
@@ -31,7 +30,6 @@
 #include "proto/ecies_aead_hkdf.pb.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::TestKeysetHandle;
 using crypto::tink::test::AddRawKey;
 using crypto::tink::test::AddTinkKey;
 using google::crypto::tink::EciesAeadHkdfPublicKey;
diff --git a/cc/hybrid/hybrid_encrypt_wrapper.cc b/cc/hybrid/hybrid_encrypt_wrapper.cc
index d941084..72f3b78 100644
--- a/cc/hybrid/hybrid_encrypt_wrapper.cc
+++ b/cc/hybrid/hybrid_encrypt_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/hybrid_encrypt_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -25,8 +26,8 @@
 #include "tink/internal/monitoring_util.h"
 #include "tink/internal/registry_impl.h"
 #include "tink/internal/util.h"
-#include "tink/primitive_set.h"
 #include "tink/monitoring/monitoring.h"
+#include "tink/primitive_set.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -66,7 +67,7 @@
       absl::string_view plaintext,
       absl::string_view context_info) const override;
 
-  ~HybridEncryptSetWrapper() override {}
+  ~HybridEncryptSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<HybridEncrypt>> hybrid_encrypt_set_;
diff --git a/cc/hybrid/hybrid_encrypt_wrapper.h b/cc/hybrid/hybrid_encrypt_wrapper.h
index ad78604..4d100da 100644
--- a/cc/hybrid/hybrid_encrypt_wrapper.h
+++ b/cc/hybrid/hybrid_encrypt_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_HYBRID_HYBRID_ENCRYPT_WRAPPER_H_
 #define TINK_HYBRID_HYBRID_ENCRYPT_WRAPPER_H_
 
+#include <memory>
+
 #include "absl/strings/string_view.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/primitive_set.h"
diff --git a/cc/hybrid/internal/CMakeLists.txt b/cc/hybrid/internal/CMakeLists.txt
index 6dc37fa..182b12c 100644
--- a/cc/hybrid/internal/CMakeLists.txt
+++ b/cc/hybrid/internal/CMakeLists.txt
@@ -201,6 +201,7 @@
     tink::util::status
     tink::util::statusor
     tink::proto::hpke_cc_proto
+  TESTONLY
   TAGS
     exclude_if_openssl
 )
@@ -253,6 +254,7 @@
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
+  TESTONLY
   TAGS
     exclude_if_openssl
 )
diff --git a/cc/hybrid/internal/hpke_context_boringssl.cc b/cc/hybrid/internal/hpke_context_boringssl.cc
index 47a51ed..42374a1 100644
--- a/cc/hybrid/internal/hpke_context_boringssl.cc
+++ b/cc/hybrid/internal/hpke_context_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_context_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/internal/hpke_context_boringssl_test.cc b/cc/hybrid/internal/hpke_context_boringssl_test.cc
index 54f5f20..cded6bc 100644
--- a/cc/hybrid/internal/hpke_context_boringssl_test.cc
+++ b/cc/hybrid/internal/hpke_context_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_context_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/internal/hpke_context_test.cc b/cc/hybrid/internal/hpke_context_test.cc
index 1338712..8592558 100644
--- a/cc/hybrid/internal/hpke_context_test.cc
+++ b/cc/hybrid/internal/hpke_context_test.cc
@@ -16,7 +16,9 @@
 
 #include "tink/hybrid/internal/hpke_context.h"
 
+#include <memory>
 #include <string>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/hybrid/internal/hpke_decrypt_boringssl.cc b/cc/hybrid/internal/hpke_decrypt_boringssl.cc
index ec529d9..46f0796 100644
--- a/cc/hybrid/internal/hpke_decrypt_boringssl.cc
+++ b/cc/hybrid/internal/hpke_decrypt_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_decrypt_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/internal/hpke_decrypt_boringssl_test.cc b/cc/hybrid/internal/hpke_decrypt_boringssl_test.cc
index afdf7e3..05106fa 100644
--- a/cc/hybrid/internal/hpke_decrypt_boringssl_test.cc
+++ b/cc/hybrid/internal/hpke_decrypt_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_decrypt_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/internal/hpke_encrypt_boringssl.cc b/cc/hybrid/internal/hpke_encrypt_boringssl.cc
index ec7a010..198ce16 100644
--- a/cc/hybrid/internal/hpke_encrypt_boringssl.cc
+++ b/cc/hybrid/internal/hpke_encrypt_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_encrypt_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/internal/hpke_encrypt_boringssl_test.cc b/cc/hybrid/internal/hpke_encrypt_boringssl_test.cc
index 9f6ad14..7f668a3 100644
--- a/cc/hybrid/internal/hpke_encrypt_boringssl_test.cc
+++ b/cc/hybrid/internal/hpke_encrypt_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_encrypt_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/hybrid/internal/hpke_key_boringssl.cc b/cc/hybrid/internal/hpke_key_boringssl.cc
index 0448bee..5c8b198 100644
--- a/cc/hybrid/internal/hpke_key_boringssl.cc
+++ b/cc/hybrid/internal/hpke_key_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_key_boringssl.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
diff --git a/cc/hybrid/internal/hpke_key_boringssl_test.cc b/cc/hybrid/internal/hpke_key_boringssl_test.cc
index 802058d..83840d6 100644
--- a/cc/hybrid/internal/hpke_key_boringssl_test.cc
+++ b/cc/hybrid/internal/hpke_key_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/hybrid/internal/hpke_key_boringssl.h"
 
+#include <memory>
 #include <string>
 
 #include "gtest/gtest.h"
diff --git a/cc/hybrid/internal/hpke_private_key_manager.cc b/cc/hybrid/internal/hpke_private_key_manager.cc
index da0d663..5fc4047 100644
--- a/cc/hybrid/internal/hpke_private_key_manager.cc
+++ b/cc/hybrid/internal/hpke_private_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/hybrid/internal/hpke_private_key_manager.h"
 
+#include <memory>
+
 #include "absl/status/status.h"
 #include "tink/hybrid/internal/hpke_key_manager_util.h"
 #include "tink/internal/ec_util.h"
diff --git a/cc/hybrid/internal/hpke_private_key_manager.h b/cc/hybrid/internal/hpke_private_key_manager.h
index 745f6f3..ef2e6b9 100644
--- a/cc/hybrid/internal/hpke_private_key_manager.h
+++ b/cc/hybrid/internal/hpke_private_key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_HYBRID_INTERNAL_HPKE_PRIVATE_KEY_MANAGER_H_
 #define TINK_HYBRID_INTERNAL_HPKE_PRIVATE_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "tink/core/key_type_manager.h"
diff --git a/cc/hybrid/internal/hpke_private_key_manager_test.cc b/cc/hybrid/internal/hpke_private_key_manager_test.cc
index 9076409..116cf04 100644
--- a/cc/hybrid/internal/hpke_private_key_manager_test.cc
+++ b/cc/hybrid/internal/hpke_private_key_manager_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/hybrid/internal/hpke_private_key_manager.h"
 
+#include <memory>
+
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "tink/hybrid/internal/hpke_encrypt.h"
diff --git a/cc/hybrid/internal/hpke_public_key_manager.h b/cc/hybrid/internal/hpke_public_key_manager.h
index 1d43df8..ebb7cd5 100644
--- a/cc/hybrid/internal/hpke_public_key_manager.h
+++ b/cc/hybrid/internal/hpke_public_key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_HYBRID_INTERNAL_HPKE_PUBLIC_KEY_MANAGER_H_
 #define TINK_HYBRID_INTERNAL_HPKE_PUBLIC_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/hybrid/internal/hpke_test_util.h b/cc/hybrid/internal/hpke_test_util.h
index 5a7ea1a..10532c2 100644
--- a/cc/hybrid/internal/hpke_test_util.h
+++ b/cc/hybrid/internal/hpke_test_util.h
@@ -18,6 +18,7 @@
 #define TINK_HYBRID_INTERNAL_HPKE_TEST_UTIL_H_
 
 #include <string>
+#include <vector>
 
 #include "absl/strings/escaping.h"
 #include "tink/hybrid/internal/hpke_util.h"
diff --git a/cc/hybrid_decrypt.h b/cc/hybrid_decrypt.h
index f685d64..6d553c3 100644
--- a/cc/hybrid_decrypt.h
+++ b/cc/hybrid_decrypt.h
@@ -61,7 +61,7 @@
   virtual crypto::tink::util::StatusOr<std::string> Decrypt(
       absl::string_view ciphertext, absl::string_view context_info) const = 0;
 
-  virtual ~HybridDecrypt() {}
+  virtual ~HybridDecrypt() = default;
 };
 
 }  // namespace tink
diff --git a/cc/hybrid_encrypt.h b/cc/hybrid_encrypt.h
index 67b18dd..20f54dd 100644
--- a/cc/hybrid_encrypt.h
+++ b/cc/hybrid_encrypt.h
@@ -61,7 +61,7 @@
   virtual crypto::tink::util::StatusOr<std::string> Encrypt(
       absl::string_view plaintext, absl::string_view context_info) const = 0;
 
-  virtual ~HybridEncrypt() {}
+  virtual ~HybridEncrypt() = default;
 };
 
 }  // namespace tink
diff --git a/cc/input_stream.h b/cc/input_stream.h
index f1ac7f1..cd832b0 100644
--- a/cc/input_stream.h
+++ b/cc/input_stream.h
@@ -27,8 +27,8 @@
 // Protocol Buffers' google::protobuf::io::ZeroCopyInputStream.
 class InputStream {
  public:
-  InputStream() {}
-  virtual ~InputStream() {}
+  InputStream() = default;
+  virtual ~InputStream() = default;
 
   // Obtains a chunk of data from the stream.
   //
diff --git a/cc/insecure_secret_key_access.h b/cc/insecure_secret_key_access.h
index 90067f2..915b4f3 100644
--- a/cc/insecure_secret_key_access.h
+++ b/cc/insecure_secret_key_access.h
@@ -14,8 +14,8 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_INSECURE_KEY_ACCESS_H_
-#define TINK_INSECURE_KEY_ACCESS_H_
+#ifndef TINK_INSECURE_SECRET_KEY_ACCESS_H_
+#define TINK_INSECURE_SECRET_KEY_ACCESS_H_
 
 #include "tink/secret_key_access_token.h"
 
@@ -39,4 +39,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_INSECURE_KEY_ACCESS_H_
+#endif  // TINK_INSECURE_SECRET_KEY_ACCESS_H_
diff --git a/cc/integration/awskms/.bazelrc b/cc/integration/awskms/.bazelrc
new file mode 100644
index 0000000..0fe2c3f
--- /dev/null
+++ b/cc/integration/awskms/.bazelrc
@@ -0,0 +1,4 @@
+# Minumum C++ version. Override it building this project with
+# `bazel build --cxxopt='-std=c++<XY>' --host_cxxopt='c++<XY>' ...`
+# (Both -std and --host_cxxopt must be set to force the desired version.)
+build --cxxopt='-std=c++14' --host_cxxopt='-std=c++14'
diff --git a/cc/integration/awskms/.bazelversion b/cc/integration/awskms/.bazelversion
new file mode 100644
index 0000000..09b254e
--- /dev/null
+++ b/cc/integration/awskms/.bazelversion
@@ -0,0 +1 @@
+6.0.0
diff --git a/cc/integration/awskms/BUILD.bazel b/cc/integration/awskms/BUILD.bazel
index 0367300..1c81f7e 100644
--- a/cc/integration/awskms/BUILD.bazel
+++ b/cc/integration/awskms/BUILD.bazel
@@ -3,24 +3,6 @@
 licenses(["notice"])
 
 cc_library(
-    name = "aws_crypto",
-    srcs = [
-        "aws_crypto.cc",
-    ],
-    hdrs = [
-        "aws_crypto.h",
-    ],
-    include_prefix = "tink/integration/awskms",
-    visibility = ["//visibility:public"],
-    deps = [
-        "@aws_cpp_sdk//:aws_sdk_core",
-        "@boringssl//:crypto",
-        "@com_google_absl//absl/base",
-    ],
-    alwayslink = 1,
-)
-
-cc_library(
     name = "aws_kms_aead",
     srcs = ["aws_kms_aead.cc"],
     hdrs = ["aws_kms_aead.h"],
@@ -44,9 +26,9 @@
     include_prefix = "tink/integration/awskms",
     visibility = ["//visibility:public"],
     deps = [
-        ":aws_crypto",
         ":aws_kms_aead",
         "@aws_cpp_sdk//:aws_sdk_core",
+        "@com_google_absl//absl/base",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/synchronization",
@@ -58,34 +40,34 @@
     alwayslink = 1,
 )
 
-# tests
-
 cc_test(
-    name = "aws_kms_aead_test",
-    size = "medium",
-    srcs = ["aws_kms_aead_test.cc"],
-    copts = ["-Iexternal/gtest/include"],
+    name = "aws_kms_aead_integration_test",
+    size = "small",
+    srcs = ["aws_kms_aead_integration_test.cc"],
+    data = ["//testdata/aws:credentials"],
+    # This target requires valid credentials to interact with the AWS KMS.
+    tags = ["manual"],
     deps = [
         ":aws_kms_aead",
-        "@aws_cpp_sdk//:aws_sdk_core",
+        ":aws_kms_client",
+        "//tink/integration/awskms/internal:test_file_util",
+        "@bazel_tools//tools/cpp/runfiles",
+        "@com_google_absl//absl/log:check",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
-        "@tink_cc//util:status",
         "@tink_cc//util:statusor",
+        "@tink_cc//util:test_matchers",
     ],
 )
 
 cc_test(
     name = "aws_kms_client_test",
-    size = "medium",
+    size = "small",
     srcs = ["aws_kms_client_test.cc"],
-    copts = ["-Iexternal/gtest/include"],
-    data = [
-        "//testdata/aws:credentials",
-        "//testdata/gcp:credentials",
-    ],
+    data = ["//testdata/aws:credentials"],
     deps = [
         ":aws_kms_client",
+        "//tink/integration/awskms/internal:test_file_util",
         "@aws_cpp_sdk//:aws_sdk_core",
         "@com_google_absl//absl/status",
         "@com_google_googletest//:gtest_main",
diff --git a/cc/integration/awskms/aws_crypto.cc b/cc/integration/awskms/aws_crypto.cc
deleted file mode 100644
index c73eac4..0000000
--- a/cc/integration/awskms/aws_crypto.cc
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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
-//
-//     http://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 "tink/integration/awskms/aws_crypto.h"
-
-#include "aws/core/Aws.h"
-#include "aws/core/utils/Outcome.h"
-#include "aws/core/utils/crypto/Factories.h"
-#include "aws/core/utils/crypto/HashResult.h"
-#include "aws/core/utils/crypto/HMAC.h"
-#include "aws/core/utils/crypto/Hash.h"
-
-#include "absl/base/attributes.h"
-#include "openssl/hmac.h"
-#include "openssl/sha.h"
-
-namespace crypto {
-namespace tink {
-namespace integration {
-namespace awskms {
-
-ABSL_CONST_INIT const char* kAwsCryptoAllocationTag = "AwsCryptoAllocation";
-
-class AwsSha256HmacOpenSslImpl : public Aws::Utils::Crypto::HMAC {
- public:
-  AwsSha256HmacOpenSslImpl() {}
-
-  virtual ~AwsSha256HmacOpenSslImpl() = default;
-
-  Aws::Utils::Crypto::HashResult Calculate(
-      const Aws::Utils::ByteBuffer& toSign,
-      const Aws::Utils::ByteBuffer& secret) override {
-    unsigned int length = SHA256_DIGEST_LENGTH;
-    Aws::Utils::ByteBuffer digest(length);
-    memset(digest.GetUnderlyingData(), 0, length);
-
-    HMAC_CTX ctx;
-    HMAC_CTX_init(&ctx);
-
-    HMAC_Init_ex(&ctx, secret.GetUnderlyingData(),
-                 static_cast<int>(secret.GetLength()), EVP_sha256(), NULL);
-    HMAC_Update(&ctx, toSign.GetUnderlyingData(), toSign.GetLength());
-    HMAC_Final(&ctx, digest.GetUnderlyingData(), &length);
-    HMAC_CTX_cleanup(&ctx);
-
-    return Aws::Utils::Crypto::HashResult(std::move(digest));
-  }
-};
-
-class AwsSha256OpenSslImpl : public Aws::Utils::Crypto::Hash {
- public:
-  AwsSha256OpenSslImpl() {}
-
-  virtual ~AwsSha256OpenSslImpl() = default;
-
-  Aws::Utils::Crypto::HashResult Calculate(const Aws::String& str) override {
-    SHA256_CTX sha256;
-    SHA256_Init(&sha256);
-    SHA256_Update(&sha256, str.data(), str.size());
-
-    Aws::Utils::ByteBuffer hash(SHA256_DIGEST_LENGTH);
-    SHA256_Final(hash.GetUnderlyingData(), &sha256);
-
-    return Aws::Utils::Crypto::HashResult(std::move(hash));
-  }
-
-  Aws::Utils::Crypto::HashResult Calculate(Aws::IStream& stream) override {
-    SHA256_CTX sha256;
-    SHA256_Init(&sha256);
-
-    auto currentPos = stream.tellg();
-    if (currentPos == std::streampos(std::streamoff(-1))) {
-      currentPos = 0;
-      stream.clear();
-    }
-
-    stream.seekg(0, stream.beg);
-
-    char streamBuffer
-        [Aws::Utils::Crypto::Hash::INTERNAL_HASH_STREAM_BUFFER_SIZE];
-    while (stream.good()) {
-      stream.read(streamBuffer,
-                  Aws::Utils::Crypto::Hash::INTERNAL_HASH_STREAM_BUFFER_SIZE);
-      auto bytesRead = stream.gcount();
-
-      if (bytesRead > 0) {
-        SHA256_Update(&sha256, streamBuffer, static_cast<size_t>(bytesRead));
-      }
-    }
-
-    stream.clear();
-    stream.seekg(currentPos, stream.beg);
-
-    Aws::Utils::ByteBuffer hash(SHA256_DIGEST_LENGTH);
-    SHA256_Final(hash.GetUnderlyingData(), &sha256);
-
-    return Aws::Utils::Crypto::HashResult(std::move(hash));
-  }
-};
-
-std::shared_ptr<Aws::Utils::Crypto::Hash>
-AwsSha256Factory::CreateImplementation() const {
-  return Aws::MakeShared<AwsSha256OpenSslImpl>(kAwsCryptoAllocationTag);
-}
-
-std::shared_ptr<Aws::Utils::Crypto::HMAC>
-AwsSha256HmacFactory::CreateImplementation() const {
-  return Aws::MakeShared<AwsSha256HmacOpenSslImpl>(kAwsCryptoAllocationTag);
-}
-
-
-}  // namespace awskms
-}  // namespace integration
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/integration/awskms/aws_crypto.h b/cc/integration/awskms/aws_crypto.h
deleted file mode 100644
index 01f8c92..0000000
--- a/cc/integration/awskms/aws_crypto.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_INTEGRATION_AWSKMS_AWS_CRYPTO_H_
-#define TINK_INTEGRATION_AWSKMS_AWS_CRYPTO_H_
-
-#include "aws/core/Aws.h"
-#include "aws/core/utils/crypto/Factories.h"
-#include "aws/core/utils/crypto/HMAC.h"
-#include "aws/core/utils/crypto/Hash.h"
-
-namespace crypto {
-namespace tink {
-namespace integration {
-namespace awskms {
-
-// Helpers for building AWS C++ Client without OpenSSL, to avoid
-// collisions with BoringSSL used by Tink.
-// These were "borrowed" from TensorFlow (https://github.com/tensorflow/).
-//
-// With these helpers the initialization of AWS API looks as follows:
-//
-//   Aws::SDKOptions options;
-//   options.cryptoOptions.sha256Factory_create_fn = []() {
-//       return Aws::MakeShared<AwsSha256Factory>(kAwsCryptoAllocationTag);
-//   };
-//   options.cryptoOptions.sha256HMACFactory_create_fn = []() {
-//       return Aws::MakeShared<AwsSha256HmacFactory>(kAwsCryptoAllocationTag);
-//   };
-//   Aws::InitAPI(options);
-//
-///////////////////////////////////////////////////////////////////////////////
-
-extern const char* kAwsCryptoAllocationTag;
-
-class AwsSha256Factory : public Aws::Utils::Crypto::HashFactory {
- public:
-  std::shared_ptr<Aws::Utils::Crypto::Hash> CreateImplementation()
-      const override;
-};
-
-class AwsSha256HmacFactory : public Aws::Utils::Crypto::HMACFactory {
- public:
-  std::shared_ptr<Aws::Utils::Crypto::HMAC> CreateImplementation()
-      const override;
-};
-
-}  // namespace awskms
-}  // namespace integration
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // TINK_INTEGRATION_AWSKMS_AWS_CRYPTO_H_
diff --git a/cc/integration/awskms/aws_kms_aead.cc b/cc/integration/awskms/aws_kms_aead.cc
index e231b9a..a34b2e2 100644
--- a/cc/integration/awskms/aws_kms_aead.cc
+++ b/cc/integration/awskms/aws_kms_aead.cc
@@ -16,11 +16,6 @@
 
 #include "tink/integration/awskms/aws_kms_aead.h"
 
-#include "absl/status/status.h"
-#include "absl/strings/escaping.h"
-#include "absl/strings/match.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
 #include "aws/core/auth/AWSCredentialsProvider.h"
 #include "aws/core/client/AWSClient.h"
 #include "aws/core/utils/Outcome.h"
@@ -31,6 +26,11 @@
 #include "aws/kms/model/DecryptResult.h"
 #include "aws/kms/model/EncryptRequest.h"
 #include "aws/kms/model/EncryptResult.h"
+#include "absl/status/status.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -39,24 +39,8 @@
 namespace tink {
 namespace integration {
 namespace awskms {
-
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
-
 namespace {
 
-// TODO: pull out hex-helpers from test_util and remove the copy below.
-std::string HexEncode(absl::string_view bytes) {
-  std::string hexchars = "0123456789abcdef";
-  std::string res(bytes.size() * 2, static_cast<char>(255));
-  for (size_t i = 0; i < bytes.size(); ++i) {
-    uint8_t c = static_cast<uint8_t>(bytes[i]);
-    res[2 * i] = hexchars[c / 16];
-    res[2 * i + 1] = hexchars[c % 16];
-  }
-  return res;
-}
-
 std::string AwsErrorToString(Aws::Client::AWSError<Aws::KMS::KMSErrors> err) {
   return absl::StrCat("AWS error code: ", err.GetErrorType(), ", ",
                       err.GetExceptionName(), ": ", err.GetMessage());
@@ -64,28 +48,21 @@
 
 }  // namespace
 
-AwsKmsAead::AwsKmsAead(absl::string_view key_arn,
-                       std::shared_ptr<Aws::KMS::KMSClient> aws_client) :
-    key_arn_(key_arn), aws_client_(aws_client) {
-}
-
-// static
-StatusOr<std::unique_ptr<Aead>>
-AwsKmsAead::New(absl::string_view key_arn,
-                std::shared_ptr<Aws::KMS::KMSClient> aws_client) {
+util::StatusOr<std::unique_ptr<Aead>> AwsKmsAead::New(
+    absl::string_view key_arn,
+    std::shared_ptr<Aws::KMS::KMSClient> aws_client) {
   if (key_arn.empty()) {
-    return Status(absl::StatusCode::kInvalidArgument,
-                  "Key ARN cannot be empty.");
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Key ARN cannot be empty.");
   }
   if (aws_client == nullptr) {
-    return Status(absl::StatusCode::kInvalidArgument,
-                  "AWS KMS client cannot be null.");
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "AWS KMS client cannot be null.");
   }
-  std::unique_ptr<Aead> aead(new AwsKmsAead(key_arn, aws_client));
-  return std::move(aead);
+  return {absl::WrapUnique(new AwsKmsAead(key_arn, aws_client))};
 }
 
-StatusOr<std::string> AwsKmsAead::Encrypt(
+util::StatusOr<std::string> AwsKmsAead::Encrypt(
     absl::string_view plaintext, absl::string_view associated_data) const {
   Aws::KMS::Model::EncryptRequest req;
   req.SetKeyId(key_arn_.c_str());
@@ -95,23 +72,23 @@
   req.SetPlaintext(plaintext_buffer);
   if (!associated_data.empty()) {
     req.AddEncryptionContext("associatedData",
-                             HexEncode(associated_data).c_str());
+                             absl::BytesToHexString(associated_data).c_str());
   }
   auto outcome = aws_client_->Encrypt(req);
-  if (outcome.IsSuccess()) {
-    auto& blob = outcome.GetResult().GetCiphertextBlob();
-    std::string ciphertext(
-        reinterpret_cast<const char*>(blob.GetUnderlyingData()),
-        blob.GetLength());
-    return ciphertext;
+  if (!outcome.IsSuccess()) {
+    auto& err = outcome.GetError();
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("AWS KMS encryption failed with error: ",
+                                     AwsErrorToString(err)));
   }
-  auto& err = outcome.GetError();
-  return util::Status(absl::StatusCode::kInvalidArgument,
-                      absl::StrCat("AWS KMS encryption failed with error: ",
-                                   AwsErrorToString(err)));
+  auto& blob = outcome.GetResult().GetCiphertextBlob();
+  std::string ciphertext(
+      reinterpret_cast<const char*>(blob.GetUnderlyingData()),
+      blob.GetLength());
+  return ciphertext;
 }
 
-StatusOr<std::string> AwsKmsAead::Decrypt(
+util::StatusOr<std::string> AwsKmsAead::Decrypt(
     absl::string_view ciphertext, absl::string_view associated_data) const {
   Aws::KMS::Model::DecryptRequest req;
   req.SetKeyId(key_arn_.c_str());
@@ -121,24 +98,20 @@
   req.SetCiphertextBlob(ciphertext_buffer);
   if (!associated_data.empty()) {
     req.AddEncryptionContext("associatedData",
-                             HexEncode(associated_data).c_str());
+                             absl::BytesToHexString(associated_data).c_str());
   }
   auto outcome = aws_client_->Decrypt(req);
-  if (outcome.IsSuccess()) {
-    if (outcome.GetResult().GetKeyId() != Aws::String(key_arn_.c_str())) {
-      return util::Status(absl::StatusCode::kInvalidArgument,
-                          "AWS KMS decryption failed: wrong key ARN.");
-    }
-    auto& buffer = outcome.GetResult().GetPlaintext();
-    std::string plaintext(
-        reinterpret_cast<const char*>(buffer.GetUnderlyingData()),
-        buffer.GetLength());
-    return plaintext;
+  if (!outcome.IsSuccess()) {
+    auto& err = outcome.GetError();
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("AWS KMS decryption failed with error: ",
+                                     AwsErrorToString(err)));
   }
-  auto& err = outcome.GetError();
-  return util::Status(absl::StatusCode::kInvalidArgument,
-                      absl::StrCat("AWS KMS decryption failed with error: ",
-                                   AwsErrorToString(err)));
+  auto& buffer = outcome.GetResult().GetPlaintext();
+  std::string plaintext(
+      reinterpret_cast<const char*>(buffer.GetUnderlyingData()),
+      buffer.GetLength());
+  return plaintext;
 }
 
 }  // namespace awskms
diff --git a/cc/integration/awskms/aws_kms_aead.h b/cc/integration/awskms/aws_kms_aead.h
index be400ab..31c9157 100644
--- a/cc/integration/awskms/aws_kms_aead.h
+++ b/cc/integration/awskms/aws_kms_aead.h
@@ -17,8 +17,8 @@
 #ifndef TINK_INTEGRATION_AWSKMS_AWS_KMS_AEAD_H_
 #define TINK_INTEGRATION_AWSKMS_AWS_KMS_AEAD_H_
 
-#include "absl/strings/string_view.h"
 #include "aws/kms/KMSClient.h"
+#include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/util/statusor.h"
 
@@ -27,15 +27,20 @@
 namespace integration {
 namespace awskms {
 
-// AwsKmsAead is an implementation of AEAD that forwards
-// encryption/decryption requests to a key managed by
-// <a href="https://aws.amazon.com/kms/">AWS KMS</a>.
+// AwsKmsAead is an implementation of AEAD that forwards encryption/decryption
+// requests to a key managed by the AWS KMS (https://aws.amazon.com/kms).
 class AwsKmsAead : public Aead {
  public:
-  // Creates a new AwsKmsAead that is bound to the key specified in 'key_arn',
+  // Move only.
+  AwsKmsAead(AwsKmsAead&& other) = default;
+  AwsKmsAead& operator=(AwsKmsAead&& other) = default;
+  AwsKmsAead(const AwsKmsAead&) = delete;
+  AwsKmsAead& operator=(const AwsKmsAead&) = delete;
+
+  // Creates a new AwsKmsAead that is bound to the key specified in `key_arn`,
   // and that uses the given client when communicating with the KMS.
-  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  New(absl::string_view key_arn,
+  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>> New(
+      absl::string_view key_arn,
       std::shared_ptr<Aws::KMS::KMSClient> aws_client);
 
   crypto::tink::util::StatusOr<std::string> Encrypt(
@@ -46,16 +51,15 @@
       absl::string_view ciphertext,
       absl::string_view associated_data) const override;
 
-  virtual ~AwsKmsAead() {}
-
  private:
   AwsKmsAead(absl::string_view key_arn,
-             std::shared_ptr<Aws::KMS::KMSClient> aws_client);
+             std::shared_ptr<Aws::KMS::KMSClient> aws_client)
+      : key_arn_(key_arn), aws_client_(aws_client) {}
+
   std::string key_arn_;  // The location of a crypto key in AWS KMS.
   std::shared_ptr<Aws::KMS::KMSClient> aws_client_;
 };
 
-
 }  // namespace awskms
 }  // namespace integration
 }  // namespace tink
diff --git a/cc/integration/awskms/aws_kms_aead_integration_test.cc b/cc/integration/awskms/aws_kms_aead_integration_test.cc
new file mode 100644
index 0000000..a549cff
--- /dev/null
+++ b/cc/integration/awskms/aws_kms_aead_integration_test.cc
@@ -0,0 +1,91 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "tink/integration/awskms/aws_kms_aead.h"
+#include "tink/integration/awskms/aws_kms_client.h"
+#include "tink/integration/awskms/internal/test_file_util.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace awskms {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+
+constexpr absl::string_view kAwsKmsKeyUri =
+    "aws-kms://arn:aws:kms:us-east-2:235739564943:key/"
+    "3ee50705-5a82-4f5b-9753-05c4f473922f";
+
+constexpr absl::string_view kAwsKmsKeyAliasUri =
+    "aws-kms://arn:aws:kms:us-east-2:235739564943:alias/"
+    "unit-and-integration-testing";
+
+
+TEST(AwsKmsAeadTest, EncryptDecrypt) {
+  std::string credentials =
+      internal::RunfilesPath("testdata/aws/credentials.ini");
+  util::StatusOr<std::unique_ptr<AwsKmsClient>> client =
+      AwsKmsClient::New(/*key_uri=*/"", credentials);
+  ASSERT_THAT(client, IsOk());
+
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      (*client)->GetAead(kAwsKmsKeyUri);
+  ASSERT_THAT(aead, IsOk());
+
+  constexpr absl::string_view kPlaintext = "plaintext";
+  constexpr absl::string_view kAssociatedData = "aad";
+
+  util::StatusOr<std::string> ciphertext =
+      (*aead)->Encrypt(kPlaintext, kAssociatedData);
+  ASSERT_THAT(ciphertext, IsOk());
+  EXPECT_THAT((*aead)->Decrypt(*ciphertext, kAssociatedData),
+              IsOkAndHolds(kPlaintext));
+}
+
+TEST(AwsKmsAeadTest, EncryptDecryptWithKeyAlias) {
+  std::string credentials =
+      internal::RunfilesPath("testdata/aws/credentials.ini");
+  util::StatusOr<std::unique_ptr<AwsKmsClient>> client =
+      AwsKmsClient::New(/*key_uri=*/"", credentials);
+  ASSERT_THAT(client, IsOk());
+
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      (*client)->GetAead(kAwsKmsKeyAliasUri);
+  ASSERT_THAT(aead, IsOk());
+
+  constexpr absl::string_view kPlaintext = "plaintext";
+  constexpr absl::string_view kAssociatedData = "aad";
+
+  util::StatusOr<std::string> ciphertext =
+      (*aead)->Encrypt(kPlaintext, kAssociatedData);
+  ASSERT_THAT(ciphertext, IsOk());
+  EXPECT_THAT((*aead)->Decrypt(*ciphertext, kAssociatedData),
+              IsOkAndHolds(kPlaintext));
+}
+
+}  // namespace
+}  // namespace awskms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/integration/awskms/aws_kms_aead_test.cc b/cc/integration/awskms/aws_kms_aead_test.cc
deleted file mode 100644
index 99d42e3..0000000
--- a/cc/integration/awskms/aws_kms_aead_test.cc
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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
-//
-//      http://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 "tink/integration/awskms/aws_kms_aead.h"
-
-#include <string>
-#include <vector>
-
-#include "absl/strings/str_cat.h"
-#include "aws/core/Aws.h"
-#include "aws/kms/KMSClient.h"
-#include "tink/util/status.h"
-#include "tink/util/statusor.h"
-#include "gtest/gtest.h"
-
-namespace crypto {
-namespace tink {
-namespace subtle {
-namespace {
-
-using crypto::tink::integration::awskms::AwsKmsAead;
-
-class AwsKmsAeadTest : public ::testing::Test {
-  // TODO(przydatek): add a test with a mock KMSClient.
-};
-
-
-}  // namespace
-}  // namespace subtle
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/integration/awskms/aws_kms_client.cc b/cc/integration/awskms/aws_kms_client.cc
index 9b57dc5..158bced 100644
--- a/cc/integration/awskms/aws_kms_client.cc
+++ b/cc/integration/awskms/aws_kms_client.cc
@@ -19,13 +19,6 @@
 #include <iostream>
 #include <sstream>
 
-#include "absl/status/status.h"
-#include "absl/strings/match.h"
-#include "absl/strings/ascii.h"
-#include "absl/strings/escaping.h"
-#include "absl/strings/str_split.h"
-#include "absl/strings/string_view.h"
-#include "absl/synchronization/mutex.h"
 #include "aws/core/Aws.h"
 #include "aws/core/auth/AWSCredentialsProvider.h"
 #include "aws/core/auth/AWSCredentialsProviderChain.h"
@@ -33,7 +26,14 @@
 #include "aws/core/utils/crypto/Factories.h"
 #include "aws/core/utils/memory/AWSMemory.h"
 #include "aws/kms/KMSClient.h"
-#include "tink/integration/awskms/aws_crypto.h"
+#include "absl/base/call_once.h"
+#include "absl/status/status.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
 #include "tink/integration/awskms/aws_kms_aead.h"
 #include "tink/kms_client.h"
 #include "tink/util/status.h"
@@ -46,9 +46,10 @@
 namespace {
 
 constexpr absl::string_view kKeyUriPrefix = "aws-kms://";
+constexpr char kTinkAwsKmsAllocationTag[] = "tink::integration::awskms";
 
 // Returns AWS key ARN contained in `key_uri`. If `key_uri` does not refer to an
-// AWS key, returns an empty string.
+// AWS key, returns an error.
 util::StatusOr<std::string> GetKeyArn(absl::string_view key_uri) {
   if (!absl::StartsWithIgnoreCase(key_uri, kKeyUriPrefix)) {
     return util::Status(absl::StatusCode::kInvalidArgument,
@@ -61,8 +62,8 @@
 // `key_arn`.
 // An AWS key ARN is of the form
 // arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab.
-util::StatusOr<Aws::Client::ClientConfiguration>
-    GetAwsClientConfig(absl::string_view key_arn) {
+util::StatusOr<Aws::Client::ClientConfiguration> GetAwsClientConfig(
+    absl::string_view key_arn) {
   std::vector<std::string> key_arn_parts = absl::StrSplit(key_arn, ':');
   if (key_arn_parts.size() < 6) {
     return util::Status(absl::StatusCode::kInvalidArgument,
@@ -97,9 +98,9 @@
                                      absl::string_view line) {
   std::vector<std::string> parts = absl::StrSplit(line, '=');
   if (parts.size() != 2 || absl::StripAsciiWhitespace(parts[0]) != name) {
-    return util::Status(
-        absl::StatusCode::kInvalidArgument,
-        absl::StrCat("Expected line in format ", name, " = value"));
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("Expected line to have the format: ", name,
+                                     " = value. Found: ", line));
   }
   return std::string(absl::StripAsciiWhitespace(parts[1]));
 }
@@ -131,68 +132,56 @@
 // Aws::Auth::ProfileConfigFileAWSCredentialsProvider.
 util::StatusOr<Aws::Auth::AWSCredentials> GetAwsCredentials(
     absl::string_view credentials_path) {
-  if (!credentials_path.empty()) {  // Read credentials from given file.
-    auto creds_result = ReadFile(std::string(credentials_path));
-    if (!creds_result.ok()) {
-      return creds_result.status();
-    }
-    std::vector<std::string> creds_lines =
-        absl::StrSplit(creds_result.value(), '\n');
-    if (creds_lines.size() < 3) {
-      return util::Status(absl::StatusCode::kInvalidArgument,
-                          absl::StrCat("Invalid format of credentials in file ",
-                                       credentials_path));
-    }
-    auto key_id_result = GetValue("aws_access_key_id", creds_lines[1]);
-    if (!key_id_result.ok()) {
-      return util::Status(absl::StatusCode::kInvalidArgument,
-                          absl::StrCat("Invalid format of credentials in file ",
-                                       credentials_path, " : ",
-                                       key_id_result.status().message()));
-    }
-    auto secret_key_result = GetValue("aws_secret_access_key", creds_lines[2]);
-    if (!secret_key_result.ok()) {
-      return util::Status(
-          absl::StatusCode::kInvalidArgument,
-          absl::StrCat("Invalid format of credentials in file ",
-                       credentials_path, " : ",
-                       secret_key_result.status().message()));
-    }
-    return Aws::Auth::AWSCredentials(key_id_result.value().c_str(),
-                                     secret_key_result.value().c_str());
+  if (credentials_path.empty()) {
+    // Get default credentials.
+    Aws::Auth::DefaultAWSCredentialsProviderChain provider_chain;
+    return provider_chain.GetAWSCredentials();
   }
+  // Read credentials from the given file.
+  util::StatusOr<std::string> creds_result =
+      ReadFile(std::string(credentials_path));
+  if (!creds_result.ok()) {
+    return creds_result.status();
+  }
+  std::vector<std::string> creds_lines =
+      absl::StrSplit(creds_result.value(), '\n');
+  if (creds_lines.size() < 3) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("Invalid format of credentials in file ",
+                                     credentials_path));
+  }
+  util::StatusOr<std::string> key_id_result =
+      GetValue("aws_access_key_id", creds_lines[1]);
+  if (!key_id_result.ok()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Invalid format of credentials in file ", credentials_path,
+                     " : ", key_id_result.status().message()));
+  }
+  util::StatusOr<std::string> secret_key_result =
+      GetValue("aws_secret_access_key", creds_lines[2]);
+  if (!secret_key_result.ok()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Invalid format of credentials in file ", credentials_path,
+                     " : ", secret_key_result.status().message()));
+  }
+  return Aws::Auth::AWSCredentials(key_id_result.value().c_str(),
+                                   secret_key_result.value().c_str());
+}
 
-  // Get default credentials.
-  Aws::Auth::DefaultAWSCredentialsProviderChain provider_chain;
-  return provider_chain.GetAWSCredentials();
+void InitAwsApi() {
+  Aws::SDKOptions options;
+  Aws::InitAPI(options);
 }
 
 }  // namespace
 
-bool AwsKmsClient::aws_api_is_initialized_;
-absl::Mutex AwsKmsClient::aws_api_init_mutex_;
-
-void AwsKmsClient::InitAwsApi() {
-  absl::MutexLock lock(&aws_api_init_mutex_);
-  if (aws_api_is_initialized_) {
-    return;
-  }
-  Aws::SDKOptions options;
-  options.cryptoOptions.sha256Factory_create_fn = []() {
-    return Aws::MakeShared<AwsSha256Factory>(kAwsCryptoAllocationTag);
-  };
-  options.cryptoOptions.sha256HMACFactory_create_fn = []() {
-    return Aws::MakeShared<AwsSha256HmacFactory>(kAwsCryptoAllocationTag);
-  };
-  Aws::InitAPI(options);
-  aws_api_is_initialized_ = true;
-}
+static absl::once_flag aws_initialization_once;
 
 util::StatusOr<std::unique_ptr<AwsKmsClient>> AwsKmsClient::New(
     absl::string_view key_uri, absl::string_view credentials_path) {
-  if (!aws_api_is_initialized_) {
-    InitAwsApi();
-  }
+  absl::call_once(aws_initialization_once, []() { InitAwsApi(); });
   // Read credentials.
   util::StatusOr<Aws::Auth::AWSCredentials> credentials =
       GetAwsCredentials(credentials_path);
@@ -217,7 +206,7 @@
   auto client = absl::WrapUnique(new AwsKmsClient(*key_arn, *credentials));
   // Create AWS KMSClient.
   client->aws_client_ = Aws::MakeShared<Aws::KMS::KMSClient>(
-      kAwsCryptoAllocationTag, client->credentials_, *client_config);
+      kTinkAwsKmsAllocationTag, client->credentials_, *client_config);
   return std::move(client);
 }
 
@@ -230,33 +219,29 @@
   return key_arn_.empty() ? true : key_arn_ == *key_arn;
 }
 
-util::StatusOr<std::unique_ptr<Aead>>
-AwsKmsClient::GetAead(absl::string_view key_uri) const {
-  if (!DoesSupport(key_uri)) {
-    if (!key_arn_.empty()) {
-      return util::Status(absl::StatusCode::kInvalidArgument,
-          absl::StrCat("This client is bound to ", key_arn_,
-                       " and cannot use key ", key_uri));
-    }
-    return util::Status(
-        absl::StatusCode::kInvalidArgument,
-        absl::StrCat("This client does not support key ", key_uri));
+util::StatusOr<std::unique_ptr<Aead>> AwsKmsClient::GetAead(
+    absl::string_view key_uri) const {
+  util::StatusOr<std::string> key_arn = GetKeyArn(key_uri);
+  if (!key_arn.ok()) {
+    return key_arn.status();
   }
-
   // This client is bound to a specific key.
   if (!key_arn_.empty()) {
+    if (key_arn_ != *key_arn) {
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          absl::StrCat("This client is bound to ", key_arn_,
+                                       " and cannot use key ", key_uri));
+    }
     return AwsKmsAead::New(key_arn_, aws_client_);
   }
 
-  // Create an Aws::KMS::KMSClient for the given key.
-  util::StatusOr<std::string> key_arn = GetKeyArn(key_uri);
   util::StatusOr<Aws::Client::ClientConfiguration> client_config =
       GetAwsClientConfig(*key_arn);
   if (!client_config.ok()) {
     return client_config.status();
   }
   auto aws_client = Aws::MakeShared<Aws::KMS::KMSClient>(
-      kAwsCryptoAllocationTag, credentials_, *client_config);
+      kTinkAwsKmsAllocationTag, credentials_, *client_config);
   return AwsKmsAead::New(*key_arn, aws_client);
 }
 
diff --git a/cc/integration/awskms/aws_kms_client.h b/cc/integration/awskms/aws_kms_client.h
index b97e627..2dac113 100644
--- a/cc/integration/awskms/aws_kms_client.h
+++ b/cc/integration/awskms/aws_kms_client.h
@@ -19,10 +19,10 @@
 
 #include <memory>
 
-#include "absl/strings/string_view.h"
-#include "absl/synchronization/mutex.h"
 #include "aws/core/auth/AWSCredentialsProvider.h"
 #include "aws/kms/KMSClient.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
 #include "tink/aead.h"
 #include "tink/kms_client.h"
 #include "tink/kms_clients.h"
@@ -34,17 +34,23 @@
 namespace integration {
 namespace awskms {
 
-// AwsKmsClient is an implementation of KmsClient for
-// <a href="https://aws.amazon.com/kms/">AWS KMS</a>
+// AwsKmsClient is an implementation of KmsClient for AWS KMS
+// (https://aws.amazon.com/kms/).
 class AwsKmsClient : public crypto::tink::KmsClient {
  public:
+  // Move only.
+  AwsKmsClient(AwsKmsClient&& other) = default;
+  AwsKmsClient& operator=(AwsKmsClient&& other) = default;
+  AwsKmsClient(const AwsKmsClient&) = delete;
+  AwsKmsClient& operator=(const AwsKmsClient&) = delete;
+
   // Creates a new AwsKmsClient that is bound to the key specified in `key_uri`,
   // if not empty, and that uses the credentials in `credentials_path`, if not
   // empty, or the default ones to authenticate to the KMS.
   //
   // If `key_uri` is empty, then the client is not bound to any particular key.
-  static crypto::tink::util::StatusOr<std::unique_ptr<AwsKmsClient>>
-  New(absl::string_view key_uri, absl::string_view credentials_path);
+  static crypto::tink::util::StatusOr<std::unique_ptr<AwsKmsClient>> New(
+      absl::string_view key_uri, absl::string_view credentials_path);
 
   // Creates a new client and registers it in KMSClients.
   static crypto::tink::util::Status RegisterNewClient(
@@ -55,10 +61,8 @@
   // to a specific key.
   bool DoesSupport(absl::string_view key_uri) const override;
 
-  // Returns an Aead-primitive backed by KMS key specified by `key_uri`,
-  // provided that this KmsClient does support `key_uri`.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  GetAead(absl::string_view key_uri) const override;
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetAead(
+      absl::string_view key_uri) const override;
 
  private:
   AwsKmsClient(absl::string_view key_arn, Aws::Auth::AWSCredentials credentials)
@@ -66,11 +70,6 @@
   AwsKmsClient(Aws::Auth::AWSCredentials credentials)
       : credentials_(credentials) {}
 
-  // Initializes AWS API.
-  static void InitAwsApi();
-  static bool aws_api_is_initialized_;
-  static absl::Mutex aws_api_init_mutex_;
-
   std::string key_arn_;
   Aws::Auth::AWSCredentials credentials_;
   std::shared_ptr<Aws::KMS::KMSClient> aws_client_;
diff --git a/cc/integration/awskms/aws_kms_client_test.cc b/cc/integration/awskms/aws_kms_client_test.cc
index 490cfc1..042d76b 100644
--- a/cc/integration/awskms/aws_kms_client_test.cc
+++ b/cc/integration/awskms/aws_kms_client_test.cc
@@ -17,6 +17,8 @@
 #include "tink/integration/awskms/aws_kms_client.h"
 
 #include <cstdlib>
+#include <fstream>
+#include <ios>
 #include <string>
 #include <vector>
 
@@ -25,6 +27,7 @@
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
+#include "tink/integration/awskms/internal/test_file_util.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
@@ -41,6 +44,7 @@
 using ::crypto::tink::test::IsOkAndHolds;
 using ::crypto::tink::test::StatusIs;
 using ::testing::IsNull;
+using ::testing::IsSubstring;
 using ::testing::Not;
 
 constexpr absl::string_view kAwsKey1 =
@@ -51,7 +55,7 @@
 
 TEST(AwsKmsClientTest, CreateClientNotBoundToSpecificKeySupportsAllValidKeys) {
   std::string creds_file =
-      absl::StrCat(getenv("TEST_SRCDIR"), "/tink_cc_awskms/testdata/aws/credentials.ini");
+      internal::RunfilesPath("testdata/aws/credentials.ini");
   util::StatusOr<std::unique_ptr<AwsKmsClient>> client =
       AwsKmsClient::New(/*key_uri=*/"", creds_file);
   ASSERT_THAT(client, IsOk());
@@ -64,7 +68,7 @@
 // different key URI.
 TEST(AwsKmsClientTest, CreateClientBoundToSpecificKeySupportOnlyOneKey) {
   std::string creds_file =
-      absl::StrCat(getenv("TEST_SRCDIR"), "/tink_cc_awskms/testdata/aws/credentials.ini");
+      internal::RunfilesPath("testdata/aws/credentials.ini");
   util::StatusOr<std::unique_ptr<AwsKmsClient>> client =
       AwsKmsClient::New(kAwsKey1, creds_file);
   ASSERT_THAT(client, IsOk());
@@ -75,18 +79,43 @@
 
 TEST(AwsKmsClientTest, RegisterKmsClient) {
   std::string creds_file =
-      absl::StrCat(getenv("TEST_SRCDIR"), "/tink_cc_awskms/testdata/aws/credentials.ini");
+      internal::RunfilesPath("testdata/aws/credentials.ini");
   ASSERT_THAT(AwsKmsClient::RegisterNewClient(kAwsKey1, creds_file), IsOk());
   util::StatusOr<const KmsClient*> kms_client = KmsClients::Get(kAwsKey1);
   EXPECT_THAT(kms_client, IsOkAndHolds(Not(IsNull())));
 }
 
 TEST(AwsKmsClientTest, RegisterKmsClientFailsWhenKeyIsInvalid) {
-  std::string creds_file = absl::StrCat(getenv("TEST_SRCDIR"),
-                                        "/tink_cc_awskms/testdata/gcp/credentials.json");
-  auto client = AwsKmsClient::RegisterNewClient(
-      "gcp-kms://projects/someProject/.../cryptoKeys/key1", creds_file);
+  util::Status client = AwsKmsClient::RegisterNewClient(
+      "gcp-kms://projects/someProject/.../cryptoKeys/key1",
+      internal::RunfilesPath("testdata/aws/credentials.ini"));
   EXPECT_THAT(client, StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_PRED_FORMAT2(IsSubstring, "Invalid key URI",
+                      std::string(client.message()));
+}
+
+TEST(AwsKmsClientTest, RegisterKmsClientFailsWhenCredentialsDoNotExist) {
+  util::Status client =
+      AwsKmsClient::RegisterNewClient(kAwsKey1, "this/file/does/not/exist.ini");
+  EXPECT_THAT(client, StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_PRED_FORMAT2(IsSubstring, "Error opening file",
+                      std::string(client.message()));
+}
+
+TEST(AwsKmsClientTest, RegisterKmsClientFailsWhenMalformedCredentials) {
+  // Create an invalid credentials file.
+  std::string malformed_content = "These are malformed credentials.";
+  std::string invalid_credentials_file =
+      internal::RunfilesPath("testdata/aws/invalid.ini");
+  std::ofstream out_stream(invalid_credentials_file, std::ios::binary);
+  out_stream.write(malformed_content.data(), malformed_content.size());
+  out_stream.close();
+
+  util::Status client =
+      AwsKmsClient::RegisterNewClient(kAwsKey1, invalid_credentials_file);
+  EXPECT_THAT(client, StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_PRED_FORMAT2(IsSubstring, "Invalid format",
+                      std::string(client.message()));
 }
 
 }  // namespace
diff --git a/cc/integration/awskms/internal/BUILD.bazel b/cc/integration/awskms/internal/BUILD.bazel
new file mode 100644
index 0000000..2f41114
--- /dev/null
+++ b/cc/integration/awskms/internal/BUILD.bazel
@@ -0,0 +1,17 @@
+package(default_visibility = ["//:__subpackages__"])
+
+licenses(["notice"])
+
+cc_library(
+    name = "test_file_util",
+    srcs = ["test_file_util_bazel.cc"],
+    hdrs = ["test_file_util.h"],
+    include_prefix = "tink/integration/awskms/internal",
+    testonly = 1,
+    deps = [
+        "@bazel_tools//tools/cpp/runfiles",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings",
+    ],
+    alwayslink = 1,
+)
diff --git a/cc/integration/awskms/internal/test_file_util.h b/cc/integration/awskms/internal/test_file_util.h
new file mode 100644
index 0000000..80aef6d
--- /dev/null
+++ b/cc/integration/awskms/internal/test_file_util.h
@@ -0,0 +1,36 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_INTEGRATION_AWSKMS_INTERNAL_TEST_FILE_UTIL_H_
+#define TINK_INTEGRATION_AWSKMS_INTERNAL_TEST_FILE_UTIL_H_
+
+#include "absl/strings/string_view.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace awskms {
+namespace internal {
+
+// Returns the path of the specified file in the runfiles directory.
+std::string RunfilesPath(absl::string_view path);
+
+}  // namespace internal
+}  // namespace awskms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTEGRATION_AWSKMS_INTERNAL_TEST_FILE_UTIL_H_
diff --git a/cc/integration/awskms/internal/test_file_util_bazel.cc b/cc/integration/awskms/internal/test_file_util_bazel.cc
new file mode 100644
index 0000000..e59bf04
--- /dev/null
+++ b/cc/integration/awskms/internal/test_file_util_bazel.cc
@@ -0,0 +1,45 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/integration/awskms/internal/test_file_util.h"
+
+#include "absl/log/check.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "tools/cpp/runfiles/runfiles.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace awskms {
+namespace internal {
+
+using ::bazel::tools::cpp::runfiles::Runfiles;
+
+std::string RunfilesPath(absl::string_view path) {
+  std::string error;
+  std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest(&error));
+  CHECK(runfiles != nullptr) << "Unable to determine runfile path: ";
+  const char* workspace_dir = getenv("TEST_WORKSPACE");
+  CHECK(workspace_dir != nullptr && workspace_dir[0] != '\0')
+      << "Unable to determine workspace name.";
+  return runfiles->Rlocation(absl::StrCat(workspace_dir, "/", path));
+}
+
+}  // namespace internal
+}  // namespace awskms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/integration/awskms/testdata/README.md b/cc/integration/awskms/testdata/README.md
deleted file mode 100644
index 9d362c7..0000000
--- a/cc/integration/awskms/testdata/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-This folder contains AWS and GCP credentials that are used for testing Tink.
-
-# AWS
-
-For security reasons, all AWS credentials in this folder are invalid. If you
-want to run tests that depend on them, please create your own
-[AWS access keys](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html).
-The credentials are required in several formats expected by different APIs. For
-example, Java expects the credentials as a
-[properties file](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html).
-In order to cover all tests across all languages you will have to replace
-`aws/credentials.cred`, `aws/credentials.csv` and `aws/credentials.ini`. These
-can be generated in a similar way to this
-[script](https://github.com/google/tink/blob/master/kokoro/copy_credentials.sh).
-
-# GCP
-
-For security reasons, all GCP credentials in this folder are invalid. If you
-want to run tests that depend on them, please create your own
-[Cloud KMS key](https://cloud.google.com/kms/docs/creating-keys), and copy the
-credentials to `gcp/credential.json` and the key URI to `gcp/key_name.txt`.
diff --git a/cc/integration/awskms/testdata/aws/README.md b/cc/integration/awskms/testdata/aws/README.md
new file mode 100644
index 0000000..ca15b34
--- /dev/null
+++ b/cc/integration/awskms/testdata/aws/README.md
@@ -0,0 +1,16 @@
+This folder contains AWS credentials that are used for testing Tink.
+
+For security reasons, all credentials in this folder are invalid. If you want to
+run tests that depend on them, please create your own [AWS access
+keys][aws-access-keys].
+
+The credentials are required in several formats expected by different APIs. For
+example, Java expects the credentials as a [properties file][properties-file].
+In order to cover all tests across all languages you have to replace
+`aws/credentials.cred`, `aws/credentials.csv` and `aws/credentials.ini`. These
+can be generated in a similar way to this [credential copying
+script][copy-credentials-script].
+
+[aws-access-keys]: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
+[properties-file]: https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html
+[copy-credentials-script]: https://github.com/google/tink/blob/master/kokoro/copy_credentials.sh
diff --git a/cc/integration/awskms/testdata/gcp/README.md b/cc/integration/awskms/testdata/gcp/README.md
new file mode 100644
index 0000000..81b5de0
--- /dev/null
+++ b/cc/integration/awskms/testdata/gcp/README.md
@@ -0,0 +1,7 @@
+This folder contains GCP credentials that are used for testing Tink.
+
+For security reasons, all credentials in this folder are invalid.
+
+If you want to run tests that depend on them, please create your own
+[Cloud KMS key](https://cloud.google.com/kms/docs/creating-keys), and copy the
+credentials to `gcp/credential.json` and the key URI to `gcp/key_name.txt`.
diff --git a/cc/integration/awskms/third_party/aws_checksums.BUILD.bazel b/cc/integration/awskms/third_party/aws_checksums.BUILD.bazel
index 25fc636..916a28b 100644
--- a/cc/integration/awskms/third_party/aws_checksums.BUILD.bazel
+++ b/cc/integration/awskms/third_party/aws_checksums.BUILD.bazel
@@ -3,9 +3,12 @@
 cc_library(
     name = "aws_checksums",
     srcs = glob([
-        "source/intel/*.c",
         "source/*.c",
-    ]),
+    ]) + select({
+        "@platforms//cpu:x86_64": glob(["source/intel/*.c"]),
+        "@platforms//cpu:aarch64": glob(["source/arm/*.c"]),
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
     hdrs = glob([
         "include/**/*.h"
     ]),
diff --git a/cc/integration/awskms/third_party/curl.BUILD.bazel b/cc/integration/awskms/third_party/curl.BUILD.bazel
index 210b632..5a6530d 100644
--- a/cc/integration/awskms/third_party/curl.BUILD.bazel
+++ b/cc/integration/awskms/third_party/curl.BUILD.bazel
@@ -3,24 +3,6 @@
 
 licenses(["notice"])  # MIT/X derivative license
 
-# Settings for building in various environments.
-config_setting(
-    name = "linux_x86_64",
-    values = {"cpu": "k8"},
-)
-
-config_setting(
-    name = "mac_x86_64",
-    values = {"cpu": "darwin"},
-)
-
-config_setting(
-    name = "darwin_arm64",
-    values = {
-        "cpu": "darwin_arm64",
-    },
-)
-
 cc_library(
     name = "curl",
     srcs = [
@@ -220,15 +202,9 @@
         "lib/wildcard.h",
         "lib/x509asn1.h",
     ] + select({
-        ":mac_x86_64": [
-            "lib/vtls/darwinssl.c",
-        ],
-        ":darwin_arm64": [
-            "lib/vtls/darwinssl.c",
-        ],
-        ":linux_x86_64": [
-            "lib/vtls/openssl.c",
-        ],
+        "@platforms//os:macos": ["lib/vtls/darwinssl.c"],
+        "@platforms//os:linux": ["lib/vtls/openssl.c"],
+        "//conditions:default": ["@platforms//:incompatible"],
     }),
     hdrs = [
         ":configure",
@@ -252,34 +228,21 @@
         "-DHAVE_ZLIB_H",
         "-Wno-string-plus-int",
     ] + select({
-        ":mac_x86_64": [
-            "-fno-constant-cfstrings",
-        ],
-        ":darwin_arm64": [
-            "-fno-constant-cfstrings",
-        ],
-        ":linux_x86_64": [
-            "-DCURL_MAX_WRITE_SIZE=65536",
-        ],
+        "@platforms//os:macos": ["-fno-constant-cfstrings"],
+        "@platforms//os:linux": ["-DCURL_MAX_WRITE_SIZE=65536"],
+        "//conditions:default": ["@platforms//:incompatible"],
     }),
     defines = ["CURL_STATICLIB"],
     includes = ["include"],
     linkopts = select({
-        ":mac_x86_64": [
+        "@platforms//os:macos": [
             "-Wl,-framework",
             "-Wl,CoreFoundation",
             "-Wl,-framework",
             "-Wl,Security",
         ],
-        ":darwin_arm64": [
-            "-Wl,-framework",
-            "-Wl,CoreFoundation",
-            "-Wl,-framework",
-            "-Wl,Security",
-        ],
-        ":linux_x86_64": [
-            "-lrt",
-        ],
+        "@platforms//os:linux": ["-lrt"],
+        "//conditions:default": ["@platforms//:incompatible"],
     }),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/cc/integration/gcpkms/.bazelrc b/cc/integration/gcpkms/.bazelrc
new file mode 100644
index 0000000..0fe2c3f
--- /dev/null
+++ b/cc/integration/gcpkms/.bazelrc
@@ -0,0 +1,4 @@
+# Minumum C++ version. Override it building this project with
+# `bazel build --cxxopt='-std=c++<XY>' --host_cxxopt='c++<XY>' ...`
+# (Both -std and --host_cxxopt must be set to force the desired version.)
+build --cxxopt='-std=c++14' --host_cxxopt='-std=c++14'
diff --git a/cc/integration/gcpkms/.bazelversion b/cc/integration/gcpkms/.bazelversion
new file mode 100644
index 0000000..09b254e
--- /dev/null
+++ b/cc/integration/gcpkms/.bazelversion
@@ -0,0 +1 @@
+6.0.0
diff --git a/cc/integration/gcpkms/BUILD.bazel b/cc/integration/gcpkms/BUILD.bazel
index 86d878f..4e9139d 100644
--- a/cc/integration/gcpkms/BUILD.bazel
+++ b/cc/integration/gcpkms/BUILD.bazel
@@ -39,17 +39,25 @@
     ],
 )
 
-# tests
-
 cc_test(
-    name = "gcp_kms_aead_test",
+    name = "gcp_kms_aead_integration_test",
     size = "medium",
-    srcs = ["gcp_kms_aead_test.cc"],
+    srcs = ["gcp_kms_aead_integration_test.cc"],
+    data = [
+      "//testdata/gcp:credentials",
+      "@google_root_pem//file"
+    ],
+    # This target requires valid credentials to interact with the Google Cloud
+    # KMS.
+    tags = ["manual"],
     deps = [
         ":gcp_kms_aead",
+        ":gcp_kms_client",
+        "@bazel_tools//tools/cpp/runfiles",
+        "@com_google_absl//absl/log:check",
         "@com_google_googletest//:gtest_main",
-        "@tink_cc//util:status",
         "@tink_cc//util:statusor",
+        "@tink_cc//util:test_matchers",
     ],
 )
 
diff --git a/cc/integration/gcpkms/gcp_kms_aead.cc b/cc/integration/gcpkms/gcp_kms_aead.cc
index ae57ff1..1d879a4 100644
--- a/cc/integration/gcpkms/gcp_kms_aead.cc
+++ b/cc/integration/gcpkms/gcp_kms_aead.cc
@@ -16,13 +16,12 @@
 
 #include "tink/integration/gcpkms/gcp_kms_aead.h"
 
+#include <memory>
 #include <string>
-#include <utility>
 
 #include "google/cloud/kms/v1/service.grpc.pb.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
-#include "absl/strings/match.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
@@ -34,37 +33,27 @@
 namespace integration {
 namespace gcpkms {
 
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
-using google::cloud::kms::v1::DecryptRequest;
-using google::cloud::kms::v1::DecryptResponse;
-using google::cloud::kms::v1::EncryptRequest;
-using google::cloud::kms::v1::EncryptResponse;
-using google::cloud::kms::v1::KeyManagementService;
-using grpc::ClientContext;
+using ::google::cloud::kms::v1::DecryptRequest;
+using ::google::cloud::kms::v1::DecryptResponse;
+using ::google::cloud::kms::v1::EncryptRequest;
+using ::google::cloud::kms::v1::EncryptResponse;
+using ::google::cloud::kms::v1::KeyManagementService;
 
-GcpKmsAead::GcpKmsAead(
+util::StatusOr<std::unique_ptr<Aead>> GcpKmsAead::New(
     absl::string_view key_name,
-    std::shared_ptr<KeyManagementService::Stub> kms_stub)
-    : key_name_(key_name), kms_stub_(kms_stub) {}
-
-// static
-StatusOr<std::unique_ptr<Aead>>
-GcpKmsAead::New(absl::string_view key_name,
-                std::shared_ptr<KeyManagementService::Stub> kms_stub) {
+    std::shared_ptr<KeyManagementService::Stub> kms_stub) {
   if (key_name.empty()) {
-    return Status(absl::StatusCode::kInvalidArgument,
+    return util::Status(absl::StatusCode::kInvalidArgument,
                         "Key URI cannot be empty.");
   }
   if (kms_stub == nullptr) {
-    return Status(absl::StatusCode::kInvalidArgument,
+    return util::Status(absl::StatusCode::kInvalidArgument,
                         "KMS stub cannot be null.");
   }
-  std::unique_ptr<Aead> aead(new GcpKmsAead(key_name, kms_stub));
-  return std::move(aead);
+  return absl::WrapUnique(new GcpKmsAead(key_name, kms_stub));
 }
 
-StatusOr<std::string> GcpKmsAead::Encrypt(
+util::StatusOr<std::string> GcpKmsAead::Encrypt(
     absl::string_view plaintext, absl::string_view associated_data) const {
   EncryptRequest req;
   req.set_name(key_name_);
@@ -72,19 +61,21 @@
   req.set_additional_authenticated_data(std::string(associated_data));
 
   EncryptResponse resp;
-  ClientContext context;
+  grpc::ClientContext context;
   context.AddMetadata("x-goog-request-params",
                       absl::StrCat("name=", key_name_));
 
-  auto status =  kms_stub_->Encrypt(&context, req, &resp);
+  grpc::Status status = kms_stub_->Encrypt(&context, req, &resp);
 
-  if (status.ok()) return resp.ciphertext();
-  return Status(
-      absl::StatusCode::kInvalidArgument,
-      absl::StrCat("GCP KMS encryption failed: ", status.error_message()));
+  if (!status.ok()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("GCP KMS encryption failed: ", status.error_message()));
+  }
+  return resp.ciphertext();
 }
 
-StatusOr<std::string> GcpKmsAead::Decrypt(
+util::StatusOr<std::string> GcpKmsAead::Decrypt(
     absl::string_view ciphertext, absl::string_view associated_data) const {
   DecryptRequest req;
   req.set_name(key_name_);
@@ -92,16 +83,18 @@
   req.set_additional_authenticated_data(std::string(associated_data));
 
   DecryptResponse resp;
-  ClientContext context;
+  grpc::ClientContext context;
   context.AddMetadata("x-goog-request-params",
                       absl::StrCat("name=", key_name_));
 
-  auto status =  kms_stub_->Decrypt(&context, req, &resp);
+  grpc::Status status = kms_stub_->Decrypt(&context, req, &resp);
 
-  if (status.ok()) return resp.plaintext();
-  return Status(
-      absl::StatusCode::kInvalidArgument,
-      absl::StrCat("GCP KMS encryption failed: ", status.error_message()));
+  if (!status.ok()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("GCP KMS encryption failed: ", status.error_message()));
+  }
+  return resp.plaintext();
 }
 
 }  // namespace gcpkms
diff --git a/cc/integration/gcpkms/gcp_kms_aead.h b/cc/integration/gcpkms/gcp_kms_aead.h
index 8e403ff..ff7833c 100644
--- a/cc/integration/gcpkms/gcp_kms_aead.h
+++ b/cc/integration/gcpkms/gcp_kms_aead.h
@@ -17,6 +17,7 @@
 #ifndef TINK_INTEGRATION_GCPKMS_GCP_KMS_AEAD_H_
 #define TINK_INTEGRATION_GCPKMS_GCP_KMS_AEAD_H_
 
+#include <memory>
 #include <string>
 
 #include "google/cloud/kms/v1/service.grpc.pb.h"
@@ -29,18 +30,25 @@
 namespace integration {
 namespace gcpkms {
 
-// GcpKmsAead is an implementation of AEAD that forwards
-// encryption/decryption requests to a key managed by
-// <a href="https://cloud.google.com/kms/">Google Cloud KMS</a>.
+// GcpKmsAead is an implementation of AEAD that forwards encryption/decryption
+// requests to a key managed by the Google Cloud KMS
+// (https://cloud.google.com/kms/).
 class GcpKmsAead : public Aead {
  public:
-  // Creates a new GcpKmsAead that is bound to the key specified in 'key_name',
+  // Move only.
+  GcpKmsAead(GcpKmsAead&& other) = default;
+  GcpKmsAead& operator=(GcpKmsAead&& other) = default;
+  GcpKmsAead(const GcpKmsAead&) = delete;
+  GcpKmsAead& operator=(const GcpKmsAead&) = delete;
+
+  // Creates a new GcpKmsAead that is bound to the key specified in `key_name`,
   // and that uses the channel when communicating with the KMS.
-  // Valid values for 'key_name' have the following format:
+  //
+  // Valid values for `key_name` have the following format:
   //    projects/*/locations/*/keyRings/*/cryptoKeys/*.
   // See https://cloud.google.com/kms/docs/object-hierarchy for more info.
-  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  New(absl::string_view key_name,
+  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>> New(
+      absl::string_view key_name,
       std::shared_ptr<google::cloud::kms::v1::KeyManagementService::Stub>
           kms_stub);
 
@@ -52,18 +60,17 @@
       absl::string_view ciphertext,
       absl::string_view associated_data) const override;
 
-  virtual ~GcpKmsAead() {}
-
  private:
-  GcpKmsAead(
+  explicit GcpKmsAead(
       absl::string_view key_name,
       std::shared_ptr<google::cloud::kms::v1::KeyManagementService::Stub>
-          kms_stub);
-  std::string key_name_;  // The location of a crypto key in GCP KMS.
-  std::shared_ptr<google::cloud::kms::v1::KeyManagementService::Stub>
-      kms_stub_;
-};
+          kms_stub)
+      : key_name_(key_name), kms_stub_(kms_stub) {}
 
+  // The location of a crypto key in GCP KMS.
+  std::string key_name_;
+  std::shared_ptr<google::cloud::kms::v1::KeyManagementService::Stub> kms_stub_;
+};
 
 }  // namespace gcpkms
 }  // namespace integration
diff --git a/cc/integration/gcpkms/gcp_kms_aead_integration_test.cc b/cc/integration/gcpkms/gcp_kms_aead_integration_test.cc
new file mode 100644
index 0000000..4b7b1e2
--- /dev/null
+++ b/cc/integration/gcpkms/gcp_kms_aead_integration_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "gtest/gtest.h"
+#include "absl/log/check.h"
+#include "tink/integration/gcpkms/gcp_kms_aead.h"
+#include "tink/integration/gcpkms/gcp_kms_client.h"
+#include "tink/util/test_matchers.h"
+#include "tools/cpp/runfiles/runfiles.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace gcpkms {
+namespace {
+
+using ::bazel::tools::cpp::runfiles::Runfiles;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::testing::Environment;
+
+constexpr absl::string_view kGcpKmsKeyUri =
+    "gcp-kms://projects/tink-test-infrastructure/locations/global/keyRings/"
+    "unit-and-integration-testing/cryptoKeys/aead-key";
+
+std::string RunfilesPath(absl::string_view path) {
+  std::string error;
+  std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest(&error));
+  CHECK(runfiles != nullptr) << "Unable to determine runfile path: ";
+  const char* workspace_dir = getenv("TEST_WORKSPACE");
+  CHECK(workspace_dir != nullptr && workspace_dir[0] != '\0')
+      << "Unable to determine workspace name.";
+  return runfiles->Rlocation(absl::StrCat(workspace_dir, "/", path));
+}
+
+class GcpKmsAeadIntegrationTestEnvironment : public Environment {
+ public:
+  ~GcpKmsAeadIntegrationTestEnvironment() override = default;
+
+  void SetUp() override {
+    // Set root certificates for gRPC in Bazel Test which are needed on macOS.
+    const char* test_srcdir = getenv("TEST_SRCDIR");
+    if (test_srcdir != nullptr) {
+      setenv(
+          "GRPC_DEFAULT_SSL_ROOTS_FILE_PATH",
+          absl::StrCat(test_srcdir, "/google_root_pem/file/downloaded").c_str(),
+          /*overwrite=*/false);
+    }
+  }
+};
+
+Environment* const foo_env = testing::AddGlobalTestEnvironment(
+    new GcpKmsAeadIntegrationTestEnvironment());
+
+TEST(GcpKmsAeadIntegrationTest, EncryptDecrypt) {
+  std::string credentials = RunfilesPath("testdata/gcp/credential.json");
+  util::StatusOr<std::unique_ptr<GcpKmsClient>> client =
+      GcpKmsClient::New(/*key_uri=*/"", credentials);
+  ASSERT_THAT(client, IsOk());
+
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      (*client)->GetAead(kGcpKmsKeyUri);
+  ASSERT_THAT(aead, IsOk());
+
+  constexpr absl::string_view kPlaintext = "plaintext";
+  constexpr absl::string_view kAssociatedData = "aad";
+
+  util::StatusOr<std::string> ciphertext =
+      (*aead)->Encrypt(kPlaintext, kAssociatedData);
+  ASSERT_THAT(ciphertext, IsOk());
+  EXPECT_THAT((*aead)->Decrypt(*ciphertext, kAssociatedData),
+              IsOkAndHolds(kPlaintext));
+}
+
+}  // namespace
+}  // namespace gcpkms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/integration/gcpkms/gcp_kms_aead_test.cc b/cc/integration/gcpkms/gcp_kms_aead_test.cc
deleted file mode 100644
index a94433e..0000000
--- a/cc/integration/gcpkms/gcp_kms_aead_test.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// 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
-//
-//      http://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 "gtest/gtest.h"
-#include "tink/integration/gcpkms/gcp_kms_aead.h"
-
-namespace crypto {
-namespace tink {
-namespace subtle {
-namespace {
-
-using crypto::tink::integration::gcpkms::GcpKmsAead;
-
-class GcpKmsAeadTest : public ::testing::Test {
-  // TODO(kste): Add tests when mock for
-  // google::cloud::kms::v1::KeyManagementService::StubInterface is available.
-};
-
-
-}  // namespace
-}  // namespace subtle
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/integration/gcpkms/gcp_kms_client.cc b/cc/integration/gcpkms/gcp_kms_client.cc
index c90572c..f38d133 100644
--- a/cc/integration/gcpkms/gcp_kms_client.cc
+++ b/cc/integration/gcpkms/gcp_kms_client.cc
@@ -17,6 +17,7 @@
 
 #include <fstream>
 #include <iostream>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
@@ -26,9 +27,8 @@
 #include "grpcpp/security/credentials.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
-#include "absl/strings/ascii.h"
+#include "absl/strings/match.h"
 #include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
 #include "tink/integration/gcpkms/gcp_kms_aead.h"
 #include "tink/kms_clients.h"
@@ -43,23 +43,18 @@
 
 namespace {
 
-using crypto::tink::Version;
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
-using google::cloud::kms::v1::KeyManagementService;
-using grpc::ChannelArguments;
-using grpc::ChannelCredentials;
+using ::google::cloud::kms::v1::KeyManagementService;
 
-static constexpr char kKeyUriPrefix[] = "gcp-kms://";
-static constexpr char kGcpKmsServer[] = "cloudkms.googleapis.com";
-static constexpr char kTinkUserAgentPrefix[] = "Tink/";
+static constexpr absl::string_view kKeyUriPrefix = "gcp-kms://";
+static constexpr absl::string_view kGcpKmsServer = "cloudkms.googleapis.com";
+static constexpr absl::string_view kTinkUserAgentPrefix = "Tink/";
 
-StatusOr<std::string> ReadFile(absl::string_view filename) {
+util::StatusOr<std::string> ReadFile(absl::string_view filename) {
   std::ifstream input_stream;
   input_stream.open(std::string(filename), std::ifstream::in);
   if (!input_stream.is_open()) {
-    return Status(absl::StatusCode::kInvalidArgument,
-                  absl::StrCat("Error reading file ", filename));
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("Error reading file ", filename));
   }
   std::stringstream input;
   input << input_stream.rdbuf();
@@ -67,104 +62,110 @@
   return input.str();
 }
 
-StatusOr<std::shared_ptr<ChannelCredentials>> GetCredentials(
+util::StatusOr<std::shared_ptr<grpc::ChannelCredentials>> GetCredentials(
     absl::string_view credentials_path) {
   if (credentials_path.empty()) {
-    auto creds = grpc::GoogleDefaultCredentials();
+    std::shared_ptr<grpc::ChannelCredentials> creds =
+        grpc::GoogleDefaultCredentials();
     if (creds == nullptr) {
-      return Status(absl::StatusCode::kInternal,
-                    "Could not read default credentials");
+      return util::Status(absl::StatusCode::kInternal,
+                          "Could not read default credentials");
     }
     return creds;
   }
 
   // Try reading credentials from a file.
-  auto json_creds_result = ReadFile(credentials_path);
-  if (!json_creds_result.ok()) return json_creds_result.status();
-  auto creds =
-      grpc::ServiceAccountJWTAccessCredentials(json_creds_result.value());
-  if (creds != nullptr) {
-    // Creating "empty" 'channel_creds', to convert 'creds'
-    // to ChannelCredentials via CompositeChannelCredentials().
-    auto channel_creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
-    return grpc::CompositeChannelCredentials(channel_creds, creds);
+  util::StatusOr<std::string> json_creds_result = ReadFile(credentials_path);
+  if (!json_creds_result.ok()) {
+    return json_creds_result.status();
   }
-  return Status(
-      absl::StatusCode::kInvalidArgument,
-      absl::StrCat("Could not load credentials from file ", credentials_path));
+  std::shared_ptr<grpc::CallCredentials> creds =
+      grpc::ServiceAccountJWTAccessCredentials(json_creds_result.value());
+  if (creds == nullptr) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("Could not load credentials from file ",
+                                     credentials_path));
+  }
+  // Creating "empty" 'channel_creds', to convert 'creds' to ChannelCredentials
+  // via CompositeChannelCredentials().
+  std::shared_ptr<grpc::ChannelCredentials> channel_creds =
+      grpc::SslCredentials(grpc::SslCredentialsOptions());
+  return grpc::CompositeChannelCredentials(channel_creds, creds);
 }
 
-// Returns GCP KMS key name contained in 'key_uri'.
-// If 'key_uri' does not refer to an GCP key, returns an empty string.
-std::string GetKeyName(absl::string_view key_uri) {
-  if (!absl::StartsWithIgnoreCase(key_uri, kKeyUriPrefix)) return "";
-  return std::string(key_uri.substr(std::string(kKeyUriPrefix).length()));
+// Returns GCP KMS key name contained in `key_uri`. If `key_uri` does not refer
+// to a GCP key, returns an error status.
+util::StatusOr<std::string> GetKeyName(absl::string_view key_uri) {
+  if (!absl::StartsWithIgnoreCase(key_uri, kKeyUriPrefix)) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("The key URI ", key_uri,
+                                     " does not start with ", kKeyUriPrefix));
+  }
+  return std::string(key_uri.substr(kKeyUriPrefix.length()));
 }
 
 }  // namespace
 
-// static
-StatusOr<std::unique_ptr<GcpKmsClient>> GcpKmsClient::New(
+util::StatusOr<std::unique_ptr<GcpKmsClient>> GcpKmsClient::New(
     absl::string_view key_uri, absl::string_view credentials_path) {
-  std::unique_ptr<GcpKmsClient> client(new GcpKmsClient());
-
-  // If a specific key is given, create a GCP KMSClient.
+  // Empty key name by default.
+  std::string key_name = "";
   if (!key_uri.empty()) {
-    client->key_name_ = GetKeyName(key_uri);
-    if (client->key_name_.empty()) {
-      return Status(absl::StatusCode::kInvalidArgument,
-                    absl::StrCat("Key '", key_uri, "' not supported"));
+    util::StatusOr<std::string> key_name_from_uri = GetKeyName(key_uri);
+    if (!key_name_from_uri.ok()) {
+      return key_name_from_uri.status();
     }
+    key_name = key_name_from_uri.value();
   }
+
   // Read credentials.
-  auto creds_result = GetCredentials(credentials_path);
+  util::StatusOr<std::shared_ptr<grpc::ChannelCredentials>> creds_result =
+      GetCredentials(credentials_path);
   if (!creds_result.ok()) {
     return creds_result.status();
   }
 
   // Create a KMS stub.
-  ChannelArguments args;
+  grpc::ChannelArguments args;
   args.SetUserAgentPrefix(
-      absl::StrCat(kTinkUserAgentPrefix, Version::kTinkVersion, " CPP-Python"));
-  client->kms_stub_ = KeyManagementService::NewStub(
-      grpc::CreateCustomChannel(kGcpKmsServer, creds_result.value(), args));
-  return std::move(client);
+      absl::StrCat(kTinkUserAgentPrefix, Version::kTinkVersion, " CPP"));
+  std::shared_ptr<KeyManagementService::Stub> kms_stub =
+      KeyManagementService::NewStub(grpc::CreateCustomChannel(
+          std::string(kGcpKmsServer), creds_result.value(), args));
+  return absl::WrapUnique(new GcpKmsClient(key_name, std::move(kms_stub)));
 }
 
 bool GcpKmsClient::DoesSupport(absl::string_view key_uri) const {
-  if (!key_name_.empty()) {
-    return key_name_ == GetKeyName(key_uri);
+  util::StatusOr<std::string> key_name = GetKeyName(key_uri);
+  if (!key_name.ok()) {
+    return false;
   }
-  return !GetKeyName(key_uri).empty();
+  return key_name_.empty() ? true : key_name_ == *key_name;
 }
 
-StatusOr<std::unique_ptr<Aead>> GcpKmsClient::GetAead(
+util::StatusOr<std::unique_ptr<Aead>> GcpKmsClient::GetAead(
     absl::string_view key_uri) const {
-  if (!DoesSupport(key_uri)) {
-    if (!key_name_.empty()) {
-      return Status(absl::StatusCode::kInvalidArgument,
-                    absl::StrCat("This client is bound to ", key_name_,
-                                 " and cannot use key ", key_uri));
-    } else {
-      return Status(absl::StatusCode::kInvalidArgument,
-                    absl::StrCat("This client does not support key ", key_uri));
-    }
+  util::StatusOr<std::string> key_name_from_key_uri = GetKeyName(key_uri);
+  // key_uri is invalid.
+  if (!key_name_from_key_uri.ok()) {
+    return key_name_from_key_uri.status();
   }
-  if (!key_name_.empty()) {  // This client is bound to a specific key.
-    return GcpKmsAead::New(key_name_, kms_stub_);
-  } else {  // Create an GCP KMSClient for the given key.
-    auto key_name = GetKeyName(key_uri);
-    return GcpKmsAead::New(key_name, kms_stub_);
+  // key_uri is valid, but if key_name_ is not empty key_name_from_key_uri must
+  // be equal to key_name_.
+  if (!key_name_.empty() && key_name_ != *key_name_from_key_uri) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("This client is bound to ", key_name_,
+                                     " and cannot use key ", key_uri));
   }
+  return GcpKmsAead::New(*key_name_from_key_uri, kms_stub_);
 }
 
-Status GcpKmsClient::RegisterNewClient(absl::string_view key_uri,
-                                       absl::string_view credentials_path) {
+util::Status GcpKmsClient::RegisterNewClient(
+    absl::string_view key_uri, absl::string_view credentials_path) {
   auto client_result = GcpKmsClient::New(key_uri, credentials_path);
   if (!client_result.ok()) {
     return client_result.status();
   }
-
   return KmsClients::Add(std::move(client_result.value()));
 }
 
diff --git a/cc/integration/gcpkms/gcp_kms_client.h b/cc/integration/gcpkms/gcp_kms_client.h
index 2040eaf..0523dd7 100644
--- a/cc/integration/gcpkms/gcp_kms_client.h
+++ b/cc/integration/gcpkms/gcp_kms_client.h
@@ -19,6 +19,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "google/cloud/kms/v1/service.grpc.pb.h"
 #include "grpcpp/channel.h"
@@ -33,40 +34,48 @@
 namespace integration {
 namespace gcpkms {
 
-
-// GcpKmsClient is an implementation of KmsClient for
-// <a href="https://cloud.google.com/kms/">Google Cloud KMS</a>.
-class GcpKmsClient : public crypto::tink::KmsClient  {
+// GcpKmsClient is an implementation of KmsClient for Google Cloud KMS
+// (https://cloud.google.com/kms/).
+class GcpKmsClient : public crypto::tink::KmsClient {
  public:
-  // Creates a new GcpKmsClient that is bound to the key specified in 'key_uri',
-  // and that uses the specifed credentials when communicating with the KMS.
+  // Move only.
+  GcpKmsClient(GcpKmsClient&& other) = default;
+  GcpKmsClient& operator=(GcpKmsClient&& other) = default;
+  GcpKmsClient(const GcpKmsClient&) = delete;
+  GcpKmsClient& operator=(const GcpKmsClient&) = delete;
+
+  // Creates a new GcpKmsClient that is bound to the key specified in `key_uri`,
+  // and that uses the specified credentials when communicating with the KMS.
   //
-  // Either of arguments can be empty.
-  // If 'key_uri' is empty, then the client is not bound to any particular key.
-  // If 'credential_path' is empty, then default credentials will be used.
-  static crypto::tink::util::StatusOr<std::unique_ptr<GcpKmsClient>>
-  New(absl::string_view key_uri, absl::string_view credentials_path);
+  // Either argument can be empty.
+  // If `key_uri` is empty, then the client is not bound to any particular key.
+  // If `credential_path` is empty, then default credentials will be used.
+  static crypto::tink::util::StatusOr<std::unique_ptr<GcpKmsClient>> New(
+      absl::string_view key_uri, absl::string_view credentials_path);
 
   // Creates a new client and registers it in KMSClients.
   static crypto::tink::util::Status RegisterNewClient(
       absl::string_view key_uri, absl::string_view credentials_path);
 
-  // Returns true iff this client does support KMS key specified by 'key_uri'.
+  // Returns true iff this client does support KMS key specified by `key_uri`.
   bool DoesSupport(absl::string_view key_uri) const override;
 
-  // Returns an Aead-primitive backed by KMS key specified by 'key_uri',
-  // provided that this KmsClient does support 'key_uri'.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  GetAead(absl::string_view key_uri) const override;
+  // Returns an Aead-primitive backed by KMS key specified by `key_uri`,
+  // provided that this KmsClient does support `key_uri`.
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetAead(
+      absl::string_view key_uri) const override;
 
  private:
-  GcpKmsClient() {}
+  explicit GcpKmsClient(
+      std::string key_name,
+      std::shared_ptr<google::cloud::kms::v1::KeyManagementService::Stub>
+          kms_stub)
+      : key_name_(key_name), kms_stub_(std::move(kms_stub)) {}
 
   std::string key_name_;
   std::shared_ptr<google::cloud::kms::v1::KeyManagementService::Stub> kms_stub_;
 };
 
-
 }  // namespace gcpkms
 }  // namespace integration
 }  // namespace tink
diff --git a/cc/integration/gcpkms/gcp_kms_client_test.cc b/cc/integration/gcpkms/gcp_kms_client_test.cc
index c8400f4..a53eed7 100644
--- a/cc/integration/gcpkms/gcp_kms_client_test.cc
+++ b/cc/integration/gcpkms/gcp_kms_client_test.cc
@@ -29,16 +29,14 @@
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
-using ::crypto::tink::test::IsOk;
-using ::crypto::tink::test::StatusIs;
-
 namespace crypto {
 namespace tink {
 namespace integration {
 namespace gcpkms {
 namespace {
 
-using crypto::tink::integration::gcpkms::GcpKmsClient;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
 
 TEST(GcpKmsClientTest, ClientNotBoundToAKey) {
   std::string gcp_key1 = "gcp-kms://projects/someProject/.../cryptoKeys/key1";
@@ -47,12 +45,12 @@
   std::string creds_file =
       std::string(getenv("TEST_SRCDIR")) + "/tink_cc_gcpkms/testdata/gcp/credential.json";
 
-  auto client_result = GcpKmsClient::New("", creds_file);
-  EXPECT_TRUE(client_result.ok()) << client_result.status();
-  auto client = std::move(client_result.value());
-  EXPECT_TRUE(client->DoesSupport(gcp_key1));
-  EXPECT_TRUE(client->DoesSupport(gcp_key2));
-  EXPECT_FALSE(client->DoesSupport(non_gcp_key));
+  util::StatusOr<std::unique_ptr<GcpKmsClient>> client =
+      GcpKmsClient::New("", creds_file);
+  ASSERT_THAT(client, IsOk());
+  EXPECT_TRUE((*client)->DoesSupport(gcp_key1));
+  EXPECT_TRUE((*client)->DoesSupport(gcp_key2));
+  EXPECT_FALSE((*client)->DoesSupport(non_gcp_key));
 }
 
 TEST(GcpKmsClientTest, ClientBoundToASpecificKey) {
@@ -62,12 +60,12 @@
   std::string creds_file =
       std::string(getenv("TEST_SRCDIR")) + "/tink_cc_gcpkms/testdata/gcp/credential.json";
 
-  auto client_result = GcpKmsClient::New(gcp_key1, creds_file);
-  EXPECT_TRUE(client_result.ok()) << client_result.status();
-  auto client = std::move(client_result.value());
-  EXPECT_TRUE(client->DoesSupport(gcp_key1));
-  EXPECT_FALSE(client->DoesSupport(gcp_key2));
-  EXPECT_FALSE(client->DoesSupport(non_gcp_key));
+  util::StatusOr<std::unique_ptr<GcpKmsClient>> client =
+      GcpKmsClient::New(gcp_key1, creds_file);
+  ASSERT_THAT(client, IsOk());
+  EXPECT_TRUE((*client)->DoesSupport(gcp_key1));
+  EXPECT_FALSE((*client)->DoesSupport(gcp_key2));
+  EXPECT_FALSE((*client)->DoesSupport(non_gcp_key));
 }
 
 TEST(GcpKmsClientTest, ClientCreationAndRegistry) {
@@ -75,10 +73,11 @@
   std::string creds_file =
       absl::StrCat(getenv("TEST_SRCDIR"), "/tink_cc_gcpkms/testdata/gcp/credential.json");
 
-  auto client_result = GcpKmsClient::RegisterNewClient(gcp_key1, creds_file);
-  EXPECT_THAT(client_result, IsOk());
+  util::Status client_result =
+      GcpKmsClient::RegisterNewClient(gcp_key1, creds_file);
+  ASSERT_THAT(client_result, IsOk());
 
-  auto registry_result = KmsClients::Get(gcp_key1);
+  util::StatusOr<const KmsClient*> registry_result = KmsClients::Get(gcp_key1);
   EXPECT_THAT(registry_result, IsOk());
 }
 
@@ -87,7 +86,8 @@
   std::string creds_file =
       std::string(getenv("TEST_SRCDIR")) + "/tink_cc_gcpkms/testdata/gcp/credential.json";
 
-  auto client_result = GcpKmsClient::RegisterNewClient(non_gcp_key, creds_file);
+  util::Status client_result =
+      GcpKmsClient::RegisterNewClient(non_gcp_key, creds_file);
   EXPECT_THAT(client_result, StatusIs(absl::StatusCode::kInvalidArgument));
 }
 
diff --git a/cc/integration/gcpkms/testdata/README.md b/cc/integration/gcpkms/testdata/README.md
deleted file mode 100644
index 9d362c7..0000000
--- a/cc/integration/gcpkms/testdata/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-This folder contains AWS and GCP credentials that are used for testing Tink.
-
-# AWS
-
-For security reasons, all AWS credentials in this folder are invalid. If you
-want to run tests that depend on them, please create your own
-[AWS access keys](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html).
-The credentials are required in several formats expected by different APIs. For
-example, Java expects the credentials as a
-[properties file](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html).
-In order to cover all tests across all languages you will have to replace
-`aws/credentials.cred`, `aws/credentials.csv` and `aws/credentials.ini`. These
-can be generated in a similar way to this
-[script](https://github.com/google/tink/blob/master/kokoro/copy_credentials.sh).
-
-# GCP
-
-For security reasons, all GCP credentials in this folder are invalid. If you
-want to run tests that depend on them, please create your own
-[Cloud KMS key](https://cloud.google.com/kms/docs/creating-keys), and copy the
-credentials to `gcp/credential.json` and the key URI to `gcp/key_name.txt`.
diff --git a/cc/integration/gcpkms/testdata/aws/README.md b/cc/integration/gcpkms/testdata/aws/README.md
new file mode 100644
index 0000000..ca15b34
--- /dev/null
+++ b/cc/integration/gcpkms/testdata/aws/README.md
@@ -0,0 +1,16 @@
+This folder contains AWS credentials that are used for testing Tink.
+
+For security reasons, all credentials in this folder are invalid. If you want to
+run tests that depend on them, please create your own [AWS access
+keys][aws-access-keys].
+
+The credentials are required in several formats expected by different APIs. For
+example, Java expects the credentials as a [properties file][properties-file].
+In order to cover all tests across all languages you have to replace
+`aws/credentials.cred`, `aws/credentials.csv` and `aws/credentials.ini`. These
+can be generated in a similar way to this [credential copying
+script][copy-credentials-script].
+
+[aws-access-keys]: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
+[properties-file]: https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html
+[copy-credentials-script]: https://github.com/google/tink/blob/master/kokoro/copy_credentials.sh
diff --git a/cc/integration/gcpkms/testdata/gcp/README.md b/cc/integration/gcpkms/testdata/gcp/README.md
new file mode 100644
index 0000000..81b5de0
--- /dev/null
+++ b/cc/integration/gcpkms/testdata/gcp/README.md
@@ -0,0 +1,7 @@
+This folder contains GCP credentials that are used for testing Tink.
+
+For security reasons, all credentials in this folder are invalid.
+
+If you want to run tests that depend on them, please create your own
+[Cloud KMS key](https://cloud.google.com/kms/docs/creating-keys), and copy the
+credentials to `gcp/credential.json` and the key URI to `gcp/key_name.txt`.
diff --git a/cc/integration/gcpkms/tink_cc_gcpkms_deps.bzl b/cc/integration/gcpkms/tink_cc_gcpkms_deps.bzl
index c6ab778..8cfb30d 100644
--- a/cc/integration/gcpkms/tink_cc_gcpkms_deps.bzl
+++ b/cc/integration/gcpkms/tink_cc_gcpkms_deps.bzl
@@ -27,25 +27,25 @@
 
     # gRPC needs rules_apple, which in turn needs rules_swift and apple_support.
     if not native.existing_rule("build_bazel_rules_apple"):
-        # Release from 2022-05-02.
+        # Release from 2022-09-16.
         http_archive(
             name = "build_bazel_rules_apple",
-            sha256 = "12865e5944f09d16364aa78050366aca9dc35a32a018fa35f5950238b08bf744",
-            url = "https://github.com/bazelbuild/rules_apple/releases/download/0.34.2/rules_apple.0.34.2.tar.gz",
+            sha256 = "90e3b5e8ff942be134e64a83499974203ea64797fd620eddeb71b3a8e1bff681",
+            url = "https://github.com/bazelbuild/rules_apple/releases/download/1.1.2/rules_apple.1.1.2.tar.gz",
         )
     if not native.existing_rule("build_bazel_rules_swift"):
-        # Release from 2022-03-23.
+        # Release from 2022-09-16.
         http_archive(
             name = "build_bazel_rules_swift",
-            sha256 = "a2fd565e527f83fb3f9eb07eb9737240e668c9242d3bc318712efa54a7deda97",
-            url = "https://github.com/bazelbuild/rules_swift/releases/download/0.27.0/rules_swift.0.27.0.tar.gz",
+            sha256 = "51efdaf85e04e51174de76ef563f255451d5a5cd24c61ad902feeadafc7046d9",
+            url = "https://github.com/bazelbuild/rules_swift/releases/download/1.2.0/rules_swift.1.2.0.tar.gz",
         )
     if not native.existing_rule("build_bazel_apple_support"):
-        # Release from 2022-02-03.
+        # Release from 2022-10-31.
         http_archive(
             name = "build_bazel_apple_support",
-            sha256 = "5bbce1b2b9a3d4b03c0697687023ef5471578e76f994363c641c5f50ff0c7268",
-            url = "https://github.com/bazelbuild/apple_support/releases/download/0.13.0/apple_support.0.13.0.tar.gz",
+            sha256 = "2e3dc4d0000e8c2f5782ea7bb53162f37c485b5d8dc62bb3d7d7fc7c276f0d00",
+            url = "https://github.com/bazelbuild/apple_support/releases/download/1.3.2/apple_support.1.3.2.tar.gz",
         )
 
     if not native.existing_rule("com_google_googleapis"):
@@ -57,7 +57,7 @@
             url = "https://github.com/googleapis/googleapis/archive/2f9af297c84c55c8b871ba4495e01ade42476c92.tar.gz",
         )
 
-    if "upb" not in native.existing_rules():
+    if not native.existing_rule("upb"):
         # Matches version embedded in com_github_grpc_grpc from 2022-05-11.
         http_archive(
             name = "upb",
@@ -66,7 +66,7 @@
             url = "https://github.com/protocolbuffers/upb/archive/bef53686ec702607971bd3ea4d4fefd80c6cc6e8.tar.gz",
         )
 
-    if "envoy_api" not in native.existing_rules():
+    if not native.existing_rule("envoy_api"):
         # Matches version embedded in com_github_grpc_grpc from 2022-05-11.
         http_archive(
             name = "envoy_api",
@@ -75,7 +75,7 @@
             url = "https://github.com/envoyproxy/data-plane-api/archive/9c42588c956220b48eb3099d186487c2f04d32ec.tar.gz",
         )
 
-    if "com_envoyproxy_protoc_gen_validate" not in native.existing_rules():
+    if not native.existing_rule("com_envoyproxy_protoc_gen_validate"):
         # Matches version embedded in com_github_grpc_grpc from 2022-05-11.
         http_archive(
             name = "com_envoyproxy_protoc_gen_validate",
@@ -88,7 +88,7 @@
             patch_args = ["-p1"],
         )
 
-    if "bazel_gazelle" not in native.existing_rules():
+    if not native.existing_rule("bazel_gazelle"):
         # Matches version embedded in com_github_grpc_grpc from 2022-05-11.
         http_archive(
             name = "bazel_gazelle",
@@ -117,23 +117,3 @@
             strip_prefix = "grpc-java-1.46.0",
             url = "https://github.com/grpc/grpc-java/archive/v1.46.0.tar.gz",
         )
-
-    if not native.existing_rule("curl"):
-        # Release from 2016-05-30.
-        http_archive(
-            name = "curl",
-            url = "https://mirror.bazel.build/curl.haxx.se/download/curl-7.49.1.tar.gz",
-            sha256 = "ff3e80c1ca6a068428726cd7dd19037a47cc538ce58ef61c59587191039b2ca6",
-            strip_prefix = "curl-7.49.1",
-            build_file = "@tink_cc_awskms//:third_party/curl.BUILD.bazel",
-        )
-
-    if not native.existing_rule("zlib"):
-        # Releaes from 2022-03-27.
-        http_archive(
-            name = "zlib",
-            url = "https://mirror.bazel.build/zlib.net/zlib-1.2.12.tar.gz",
-            sha256 = "91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9",
-            strip_prefix = "zlib-1.2.12",
-            build_file = "@tink_cc_awskms//:third_party/zlib.BUILD.bazel",
-        )
diff --git a/cc/internal/BUILD.bazel b/cc/internal/BUILD.bazel
index 84932a9..d573b1d 100644
--- a/cc/internal/BUILD.bazel
+++ b/cc/internal/BUILD.bazel
@@ -25,18 +25,30 @@
     srcs = ["util.cc"],
     hdrs = ["util.h"],
     include_prefix = "tink/internal",
-    deps = ["@com_google_absl//absl/strings"],
+    deps = [
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/strings",
+    ],
 )
 
 cc_library(
     name = "test_file_util",
     testonly = 1,
-    srcs = ["test_file_util_bazel.cc"],
+    srcs = [
+        "test_file_util.cc",
+        "test_file_util_bazel.cc",
+    ],
     hdrs = ["test_file_util.h"],
     include_prefix = "tink/internal",
     deps = [
+        "//subtle:random",
+        "//util:status",
+        "//util:test_util",
         "@bazel_tools//tools/cpp/runfiles",
+        "@com_google_absl//absl/log:check",
         "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest",
     ],
 )
 
@@ -50,6 +62,7 @@
         "//:primitive_set",
         "//:primitive_wrapper",
         "//proto:tink_cc_proto",
+        "//util:status",
         "//util:statusor",
         "//util:validation",
         "@com_google_absl//absl/container:flat_hash_map",
@@ -78,30 +91,26 @@
     include_prefix = "tink/internal",
     deps = [
         ":fips_utils",
+        ":key_type_info_store",
         ":keyset_wrapper",
-        ":keyset_wrapper_impl",
-        "//:catalogue",
-        "//:core/key_manager_impl",
+        ":keyset_wrapper_store",
         "//:core/key_type_manager",
-        "//:core/private_key_manager_impl",
         "//:core/private_key_type_manager",
+        "//:input_stream",
         "//:key_manager",
         "//:primitive_set",
         "//:primitive_wrapper",
         "//monitoring",
         "//proto:tink_cc_proto",
         "//util:errors",
-        "//util:protobuf_helper",
         "//util:status",
         "//util:statusor",
-        "//util:validation",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/synchronization",
-        "@com_google_absl//absl/types:optional",
     ],
 )
 
@@ -148,6 +157,8 @@
         "//util:status",
         "//util:statusor",
         "@boringssl//:crypto",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
         "@com_google_absl//absl/types:span",
     ],
 )
@@ -160,6 +171,7 @@
     deps = [
         ":bn_util",
         ":err_util",
+        ":fips_utils",
         ":ssl_unique_ptr",
         ":ssl_util",
         "//config:tink_fips",
@@ -170,6 +182,7 @@
         "//util:statusor",
         "@boringssl//:crypto",
         "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
         "@com_google_absl//absl/strings",
     ],
 )
@@ -234,6 +247,7 @@
         "//:primitive_set",
         "//:primitive_wrapper",
         "//proto:tink_cc_proto",
+        "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "//util:test_util",
@@ -252,6 +266,7 @@
         ":key_info",
         "//proto:tink_cc_proto",
         "@com_google_googletest//:gtest_main",
+        "@com_google_protobuf//:protobuf",
     ],
 )
 
@@ -261,38 +276,46 @@
     srcs = ["registry_impl_test.cc"],
     tags = ["fips"],
     deps = [
+        ":fips_utils",
+        ":registry_impl",
         "//:aead",
-        "//:catalogue",
         "//:core/key_manager_impl",
         "//:core/key_type_manager",
-        "//:crypto_format",
-        "//:keyset_manager",
+        "//:core/private_key_manager_impl",
+        "//:core/private_key_type_manager",
+        "//:core/template_util",
+        "//:hybrid_decrypt",
+        "//:input_stream",
+        "//:key_manager",
+        "//:mac",
+        "//:primitive_set",
+        "//:primitive_wrapper",
         "//:registry",
         "//aead:aead_wrapper",
         "//aead:aes_gcm_key_manager",
-        "//config:tink_fips",
         "//hybrid:ecies_aead_hkdf_private_key_manager",
         "//hybrid:ecies_aead_hkdf_public_key_manager",
-        "//monitoring",
         "//monitoring:monitoring_client_mocks",
         "//proto:aes_ctr_hmac_aead_cc_proto",
         "//proto:aes_gcm_cc_proto",
         "//proto:common_cc_proto",
         "//proto:ecdsa_cc_proto",
+        "//proto:ecies_aead_hkdf_cc_proto",
         "//proto:tink_cc_proto",
         "//subtle:aes_gcm_boringssl",
         "//subtle:random",
+        "//util:input_stream_util",
         "//util:istream_input_stream",
         "//util:protobuf_helper",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
-        "//util:test_keyset_handle",
         "//util:test_matchers",
         "//util:test_util",
         "@boringssl//:crypto",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -333,27 +356,30 @@
         ":bn_util",
         ":ssl_unique_ptr",
         "//util:secret_data",
+        "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "@boringssl//:crypto",
         "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:span",
         "@com_google_googletest//:gtest_main",
     ],
 )
 
 cc_test(
     name = "rsa_util_test",
-    size = "large",
     srcs = ["rsa_util_test.cc"],
     deps = [
         ":bn_util",
         ":rsa_util",
         ":ssl_unique_ptr",
         "//subtle:random",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "@boringssl//:crypto",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -361,12 +387,11 @@
 
 cc_test(
     name = "ec_util_test",
-    size = "large",
     srcs = ["ec_util_test.cc"],
     data = [
-        "@wycheproof//testvectors:ecdh",
-        "@wycheproof//testvectors:ecdsa_webcrypto",
-        "@wycheproof//testvectors:eddsa",
+        "//testvectors:ecdh",
+        "//testvectors:ecdsa_webcrypto",
+        "//testvectors:eddsa",
     ],
     deps = [
         ":bn_util",
@@ -378,12 +403,15 @@
         "//subtle:subtle_util",
         "//subtle:wycheproof_util",
         "//util:secret_data",
+        "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "@boringssl//:crypto",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/types:span",
         "@com_google_googletest//:gtest_main",
+        "@rapidjson",
     ],
 )
 
@@ -394,11 +422,11 @@
     deps = [
         ":md_util",
         "//subtle:common_enums",
+        "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "@boringssl//:crypto",
         "@com_google_absl//absl/strings",
-        "@com_google_absl//absl/types:span",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -428,6 +456,7 @@
         ":aes_util",
         "//subtle:subtle_util",
         "//util:secret_data",
+        "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "@boringssl//:crypto",
@@ -443,12 +472,16 @@
     hdrs = ["monitoring_util.h"],
     include_prefix = "tink/internal",
     deps = [
+        ":key_status_util",
+        "//:key_status",
         "//:primitive_set",
         "//monitoring",
         "//proto:tink_cc_proto",
+        "//util:status",
         "//util:statusor",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
     ],
 )
 
@@ -457,12 +490,718 @@
     srcs = ["monitoring_util_test.cc"],
     deps = [
         ":monitoring_util",
+        "//:key_status",
         "//:primitive_set",
         "//monitoring",
         "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
         "//util:test_matchers",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_library(
+    name = "serialization",
+    hdrs = ["serialization.h"],
+    include_prefix = "tink/internal",
+    deps = ["@com_google_absl//absl/strings"],
+)
+
+cc_library(
+    name = "proto_parameters_serialization",
+    srcs = ["proto_parameters_serialization.cc"],
+    hdrs = ["proto_parameters_serialization.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":serialization",
+        ":util",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "proto_parameters_serialization_test",
+    srcs = ["proto_parameters_serialization_test.cc"],
+    deps = [
+        ":proto_parameters_serialization",
+        "//proto:test_proto_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "proto_key_serialization",
+    srcs = ["proto_key_serialization.cc"],
+    hdrs = ["proto_key_serialization.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":serialization",
+        ":util",
+        "//:restricted_data",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "proto_key_serialization_test",
+    srcs = ["proto_key_serialization_test.cc"],
+    deps = [
+        ":proto_key_serialization",
+        "//:insecure_secret_key_access",
+        "//:restricted_data",
+        "//proto:tink_cc_proto",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "legacy_proto_parameters",
+    srcs = ["legacy_proto_parameters.cc"],
+    hdrs = ["legacy_proto_parameters.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":proto_parameters_serialization",
+        "//:parameters",
+        "//proto:tink_cc_proto",
+    ],
+)
+
+cc_test(
+    name = "legacy_proto_parameters_test",
+    srcs = ["legacy_proto_parameters_test.cc"],
+    deps = [
+        ":legacy_proto_parameters",
+        ":proto_parameters_serialization",
+        "//:parameters",
+        "//proto:test_proto_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "legacy_proto_key",
+    srcs = ["legacy_proto_key.cc"],
+    hdrs = ["legacy_proto_key.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":proto_key_serialization",
+        "//:key",
+        "//:parameters",
+        "//:secret_key_access_token",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "legacy_proto_key_test",
+    srcs = ["legacy_proto_key_test.cc"],
+    deps = [
+        ":legacy_proto_key",
+        ":proto_key_serialization",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:parameters",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//proto:tink_cc_proto",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "parser_index",
+    hdrs = ["parser_index.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":serialization",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "parser_index_test",
+    srcs = ["parser_index_test.cc"],
+    deps = [
+        ":parser_index",
+        ":serialization",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "serializer_index",
+    hdrs = ["serializer_index.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":serialization",
+        "//:key",
+        "//:parameters",
+    ],
+)
+
+cc_test(
+    name = "serializer_index_test",
+    srcs = ["serializer_index_test.cc"],
+    deps = [
+        ":serialization_test_util",
+        ":serializer_index",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "parameters_parser",
+    hdrs = ["parameters_parser.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":parser_index",
+        ":serialization",
+        "//:parameters",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "parameters_parser_test",
+    srcs = ["parameters_parser_test.cc"],
+    deps = [
+        ":parameters_parser",
+        ":parser_index",
+        ":serialization",
+        ":serialization_test_util",
+        "//:parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "parameters_serializer",
+    hdrs = ["parameters_serializer.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":serialization",
+        ":serializer_index",
+        "//:parameters",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "parameters_serializer_test",
+    srcs = ["parameters_serializer_test.cc"],
+    deps = [
+        ":parameters_serializer",
+        ":serialization",
+        ":serialization_test_util",
+        ":serializer_index",
+        "//:parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "key_parser",
+    hdrs = ["key_parser.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":parser_index",
+        ":serialization",
+        "//:key",
+        "//:secret_key_access_token",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/functional:function_ref",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "key_parser_test",
+    srcs = ["key_parser_test.cc"],
+    deps = [
+        ":key_parser",
+        ":parser_index",
+        ":serialization",
+        ":serialization_test_util",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "key_serializer",
+    hdrs = ["key_serializer.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":serialization",
+        ":serializer_index",
+        "//:key",
+        "//:secret_key_access_token",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/functional:function_ref",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "key_serializer_test",
+    srcs = ["key_serializer_test.cc"],
+    deps = [
+        ":key_serializer",
+        ":serialization",
+        ":serialization_test_util",
+        ":serializer_index",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:secret_key_access_token",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "key_status_util",
+    srcs = ["key_status_util.cc"],
+    hdrs = ["key_status_util.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        "//:key_status",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+    ],
+)
+
+cc_test(
+    name = "key_status_util_test",
+    srcs = ["key_status_util_test.cc"],
+    deps = [
+        ":key_status_util",
+        "//:key_status",
+        "//proto:tink_cc_proto",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "keyset_handle_builder_entry",
+    srcs = ["keyset_handle_builder_entry.cc"],
+    hdrs = ["keyset_handle_builder_entry.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":key_status_util",
+        ":legacy_proto_key",
+        ":legacy_proto_parameters",
+        ":mutable_serialization_registry",
+        ":proto_key_serialization",
+        ":proto_parameters_serialization",
+        ":serialization",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:key_status",
+        "//:parameters",
+        "//:registry",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "keyset_handle_builder_entry_test",
+    srcs = ["keyset_handle_builder_entry_test.cc"],
+    deps = [
+        ":keyset_handle_builder_entry",
+        ":legacy_proto_key",
+        ":legacy_proto_parameters",
+        ":proto_key_serialization",
+        ":proto_parameters_serialization",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:key_status",
+        "//:keyset_handle",
+        "//:keyset_handle_builder",
+        "//:parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//config:tink_config",
+        "//mac:aes_cmac_key",
+        "//mac:aes_cmac_parameters",
+        "//mac:mac_key_templates",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "serialization_registry",
+    srcs = ["serialization_registry.cc"],
+    hdrs = ["serialization_registry.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":key_parser",
+        ":key_serializer",
+        ":parameters_parser",
+        ":parameters_serializer",
+        ":parser_index",
+        ":serialization",
+        ":serializer_index",
+        "//:key",
+        "//:parameters",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "serialization_registry_test",
+    srcs = ["serialization_registry_test.cc"],
+    deps = [
+        ":key_parser",
+        ":key_serializer",
+        ":parameters_parser",
+        ":parameters_serializer",
+        ":serialization",
+        ":serialization_registry",
+        ":serialization_test_util",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:parameters",
+        "//:secret_key_access_token",
+        "//util:status",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "mutable_serialization_registry",
+    srcs = ["mutable_serialization_registry.cc"],
+    hdrs = ["mutable_serialization_registry.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":key_parser",
+        ":key_serializer",
+        ":legacy_proto_key",
+        ":parameters_parser",
+        ":parameters_serializer",
+        ":proto_key_serialization",
+        ":serialization",
+        ":serialization_registry",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:parameters",
+        "//:secret_key_access_token",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/synchronization",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "mutable_serialization_registry_test",
+    srcs = ["mutable_serialization_registry_test.cc"],
+    deps = [
+        ":key_parser",
+        ":key_serializer",
+        ":mutable_serialization_registry",
+        ":parameters_parser",
+        ":parameters_serializer",
+        ":proto_key_serialization",
+        ":serialization",
+        ":serialization_test_util",
+        "//:insecure_secret_key_access",
+        "//:key",
+        "//:parameters",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "serialization_test_util",
+    testonly = 1,
+    hdrs = ["serialization_test_util.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":serialization",
+        "//:key",
+        "//:parameters",
+        "//:secret_key_access_token",
+        "//util:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_test(
+    name = "serialization_test_util_test",
+    srcs = ["serialization_test_util_test.cc"],
+    deps = [
+        ":serialization_test_util",
+        "//:insecure_secret_key_access",
+        "//:parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "test_random_access_stream",
+    testonly = 1,
+    srcs = ["test_random_access_stream.cc"],
+    hdrs = ["test_random_access_stream.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        "//:random_access_stream",
+        "//util:buffer",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "test_random_access_stream_test",
+    srcs = ["test_random_access_stream_test.cc"],
+    deps = [
+        ":test_random_access_stream",
+        "//subtle:random",
+        "//util:buffer",
+        "//util:status",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "configuration_impl",
+    hdrs = ["configuration_impl.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":key_type_info_store",
+        ":keyset_wrapper_store",
+        "//:configuration",
+    ],
+)
+
+cc_test(
+    name = "configuration_impl_test",
+    srcs = ["configuration_impl_test.cc"],
+    deps = [
+        ":configuration_impl",
+        ":keyset_wrapper_store",
+        "//:cleartext_keyset_handle",
+        "//:configuration",
+        "//proto:aes_gcm_cc_proto",
+        "//proto:rsa_ssa_pss_cc_proto",
+        "//subtle:random",
+        "//util:test_matchers",
+        "//util:test_util",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "key_type_info_store",
+    srcs = ["key_type_info_store.cc"],
+    hdrs = ["key_type_info_store.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":fips_utils",
+        "//:core/key_manager_impl",
+        "//:core/key_type_manager",
+        "//:core/private_key_manager_impl",
+        "//:core/private_key_type_manager",
+        "//:key_manager",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "key_type_info_store_test",
+    srcs = ["key_type_info_store_test.cc"],
+    deps = [
+        ":fips_utils",
+        ":key_type_info_store",
+        "//:aead",
+        "//:core/key_manager_impl",
+        "//:key_manager",
+        "//aead:aes_gcm_key_manager",
+        "//aead:cord_aead",
+        "//aead:kms_envelope_aead_key_manager",
+        "//proto:aes_gcm_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:ecdsa_cc_proto",
+        "//signature:ecdsa_sign_key_manager",
+        "//signature:ecdsa_verify_key_manager",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "keyset_wrapper_store",
+    hdrs = ["keyset_wrapper_store.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":keyset_wrapper",
+        ":keyset_wrapper_impl",
+        "//:primitive_wrapper",
+        "//util:status",
+        "//util:statusor",
+    ],
+)
+
+cc_test(
+    name = "keyset_wrapper_store_test",
+    srcs = ["keyset_wrapper_store_test.cc"],
+    deps = [
+        ":keyset_wrapper_store",
+        ":registry_impl",
+        "//:primitive_set",
+        "//:primitive_wrapper",
+        "//mac:mac_wrapper",
+        "//proto:aes_gcm_cc_proto",
+        "//subtle:random",
+        "//util:status",
+        "//util:statusor",
+        "//util:test_matchers",
+        "//util:test_util",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "key_gen_configuration_impl",
+    hdrs = ["key_gen_configuration_impl.h"],
+    include_prefix = "tink/internal",
+    deps = [
+        ":key_type_info_store",
+        "//:key_gen_configuration",
+    ],
+)
+
+cc_test(
+    name = "key_gen_configuration_impl_test",
+    srcs = ["key_gen_configuration_impl_test.cc"],
+    deps = [
+        ":key_gen_configuration_impl",
+        "//:key_gen_configuration",
+        "//aead:aead_key_templates",
+        "//proto:aes_gcm_cc_proto",
+        "//proto:rsa_ssa_pss_cc_proto",
+        "//util:test_matchers",
+        "//util:test_util",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/internal/BUILD.gn b/cc/internal/BUILD.gn
index 7e51e25..affe66f 100644
--- a/cc/internal/BUILD.gn
+++ b/cc/internal/BUILD.gn
@@ -26,7 +26,11 @@
     "util.cc",
     "util.h",
   ]
-  public_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ]
+  public_deps = [
+    "//third_party/abseil-cpp/absl/base:core_headers",
+    "//third_party/abseil-cpp/absl/log:log",
+    "//third_party/abseil-cpp/absl/strings:strings",
+  ]
   public_configs = [ "//third_party/tink:tink_config" ]
 }
 
@@ -42,6 +46,7 @@
     "//third_party/tink/cc:primitive_set",
     "//third_party/tink/cc:primitive_wrapper",
     "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
     "//third_party/tink/cc/util:statusor",
     "//third_party/tink/cc/util:validation",
   ]
@@ -79,30 +84,26 @@
   ]
   public_deps = [
     ":fips_utils",
+    ":key_type_info_store",
     ":keyset_wrapper",
-    ":keyset_wrapper_impl",
+    ":keyset_wrapper_store",
     "//third_party/abseil-cpp/absl/base:core_headers",
     "//third_party/abseil-cpp/absl/container:flat_hash_map",
     "//third_party/abseil-cpp/absl/memory:memory",
     "//third_party/abseil-cpp/absl/status:status",
     "//third_party/abseil-cpp/absl/strings:strings",
     "//third_party/abseil-cpp/absl/synchronization:synchronization",
-    "//third_party/abseil-cpp/absl/types:optional",
-    "//third_party/tink/cc:catalogue",
-    "//third_party/tink/cc:core/key_manager_impl",
     "//third_party/tink/cc:core/key_type_manager",
-    "//third_party/tink/cc:core/private_key_manager_impl",
     "//third_party/tink/cc:core/private_key_type_manager",
+    "//third_party/tink/cc:input_stream",
     "//third_party/tink/cc:key_manager",
     "//third_party/tink/cc:primitive_set",
     "//third_party/tink/cc:primitive_wrapper",
     "//third_party/tink/cc/monitoring:monitoring",
     "//third_party/tink/cc/proto:tink_proto",
     "//third_party/tink/cc/util:errors",
-    "//third_party/tink/cc/util:protobuf_helper",
     "//third_party/tink/cc/util:status",
     "//third_party/tink/cc/util:statusor",
-    "//third_party/tink/cc/util:validation",
   ]
   public_configs = [ "//third_party/tink:tink_config" ]
 }
@@ -146,6 +147,8 @@
   ]
   public_deps = [
     ":ssl_unique_ptr",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
     "//third_party/abseil-cpp/absl/types:span",
     "//third_party/boringssl:crypto",
     "//third_party/tink/cc/subtle:subtle_util",
@@ -233,12 +236,332 @@
   configs -= [ "//build/config:no_rtti" ]
   sources = [ "monitoring_util.h" ]
   public_deps = [
+    ":key_status_util",
     "//third_party/abseil-cpp/absl/container:flat_hash_map",
     "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/tink/cc:key_status",
     "//third_party/tink/cc:primitive_set",
     "//third_party/tink/cc/monitoring:monitoring",
     "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
     "//third_party/tink/cc/util:statusor",
   ]
   public_configs = [ "//third_party/tink:tink_config" ]
 }
+
+# CC Library : serialization
+source_set("serialization") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "serialization.h" ]
+  public_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : proto_parameters_serialization
+source_set("proto_parameters_serialization") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "proto_parameters_serialization.cc",
+    "proto_parameters_serialization.h",
+  ]
+  public_deps = [
+    ":serialization",
+    ":util",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : proto_key_serialization
+source_set("proto_key_serialization") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "proto_key_serialization.cc",
+    "proto_key_serialization.h",
+  ]
+  public_deps = [
+    ":serialization",
+    ":util",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:restricted_data",
+    "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : legacy_proto_key
+source_set("legacy_proto_key") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "legacy_proto_key.cc",
+    "legacy_proto_key.h",
+  ]
+  public_deps = [
+    ":proto_key_serialization",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:key",
+    "//third_party/tink/cc:parameters",
+    "//third_party/tink/cc:secret_key_access_token",
+    "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : parser_index
+source_set("parser_index") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "parser_index.h" ]
+  public_deps = [
+    ":serialization",
+    "//third_party/abseil-cpp/absl/strings:strings",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : serializer_index
+source_set("serializer_index") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "serializer_index.h" ]
+  public_deps = [
+    ":serialization",
+    "//third_party/tink/cc:key",
+    "//third_party/tink/cc:parameters",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : parameters_parser
+source_set("parameters_parser") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "parameters_parser.h" ]
+  public_deps = [
+    ":parser_index",
+    ":serialization",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/tink/cc:parameters",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : parameters_serializer
+source_set("parameters_serializer") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "parameters_serializer.h" ]
+  public_deps = [
+    ":serialization",
+    ":serializer_index",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/tink/cc:parameters",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_parser
+source_set("key_parser") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "key_parser.h" ]
+  public_deps = [
+    ":parser_index",
+    ":serialization",
+    "//third_party/abseil-cpp/absl/functional:function_ref",
+    "//third_party/abseil-cpp/absl/log:log",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:key",
+    "//third_party/tink/cc:secret_key_access_token",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_serializer
+source_set("key_serializer") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "key_serializer.h" ]
+  public_deps = [
+    ":serialization",
+    ":serializer_index",
+    "//third_party/abseil-cpp/absl/functional:function_ref",
+    "//third_party/abseil-cpp/absl/log:log",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:key",
+    "//third_party/tink/cc:secret_key_access_token",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_status_util
+source_set("key_status_util") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "key_status_util.cc",
+    "key_status_util.h",
+  ]
+  public_deps = [
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/tink/cc:key_status",
+    "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : serialization_registry
+source_set("serialization_registry") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "serialization_registry.cc",
+    "serialization_registry.h",
+  ]
+  public_deps = [
+    ":key_parser",
+    ":key_serializer",
+    ":parameters_parser",
+    ":parameters_serializer",
+    ":parser_index",
+    ":serialization",
+    ":serializer_index",
+    "//third_party/abseil-cpp/absl/container:flat_hash_map",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:str_format",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:key",
+    "//third_party/tink/cc:parameters",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : mutable_serialization_registry
+source_set("mutable_serialization_registry") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "mutable_serialization_registry.cc",
+    "mutable_serialization_registry.h",
+  ]
+  public_deps = [
+    ":key_parser",
+    ":key_serializer",
+    ":legacy_proto_key",
+    ":parameters_parser",
+    ":parameters_serializer",
+    ":proto_key_serialization",
+    ":serialization",
+    ":serialization_registry",
+    "//third_party/abseil-cpp/absl/base:core_headers",
+    "//third_party/abseil-cpp/absl/memory:memory",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/synchronization:synchronization",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:insecure_secret_key_access",
+    "//third_party/tink/cc:key",
+    "//third_party/tink/cc:parameters",
+    "//third_party/tink/cc:secret_key_access_token",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : configuration_impl
+source_set("configuration_impl") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "configuration_impl.h" ]
+  public_deps = [
+    ":key_type_info_store",
+    ":keyset_wrapper_store",
+    "//third_party/tink/cc:configuration",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_type_info_store
+source_set("key_type_info_store") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "key_type_info_store.cc",
+    "key_type_info_store.h",
+  ]
+  public_deps = [
+    ":fips_utils",
+    "//third_party/abseil-cpp/absl/container:flat_hash_map",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/tink/cc:core/key_manager_impl",
+    "//third_party/tink/cc:core/key_type_manager",
+    "//third_party/tink/cc:core/private_key_manager_impl",
+    "//third_party/tink/cc:core/private_key_type_manager",
+    "//third_party/tink/cc:key_manager",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : keyset_wrapper_store
+source_set("keyset_wrapper_store") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "keyset_wrapper_store.h" ]
+  public_deps = [
+    ":keyset_wrapper",
+    ":keyset_wrapper_impl",
+    "//third_party/tink/cc:primitive_wrapper",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : key_gen_configuration_impl
+source_set("key_gen_configuration_impl") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "key_gen_configuration_impl.h" ]
+  public_deps = [
+    ":key_type_info_store",
+    "//third_party/tink/cc:key_gen_configuration",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
diff --git a/cc/internal/CMakeLists.txt b/cc/internal/CMakeLists.txt
index 20e4305..82d2499 100644
--- a/cc/internal/CMakeLists.txt
+++ b/cc/internal/CMakeLists.txt
@@ -24,16 +24,25 @@
     util.cc
     util.h
   DEPS
+    absl::core_headers
+    absl::log
     absl::strings
 )
 
 tink_cc_library(
   NAME test_file_util
   SRCS
+    test_file_util.cc
     test_file_util_cmake.cc
     test_file_util.h
   DEPS
+    absl::check
     absl::strings
+    gmock
+    tink::subtle::random
+    tink::util::status
+    tink::util::test_util
+  TESTONLY
 )
 
 tink_cc_library(
@@ -46,6 +55,7 @@
     absl::flat_hash_map
     tink::core::primitive_set
     tink::core::primitive_wrapper
+    tink::util::status
     tink::util::statusor
     tink::util::validation
     tink::proto::tink_cc_proto
@@ -84,29 +94,25 @@
     registry_impl.h
   DEPS
     tink::internal::fips_utils
+    tink::internal::key_type_info_store
     tink::internal::keyset_wrapper
-    tink::internal::keyset_wrapper_impl
+    tink::internal::keyset_wrapper_store
     absl::core_headers
     absl::flat_hash_map
     absl::memory
     absl::status
     absl::strings
     absl::synchronization
-    absl::optional
-    tink::core::catalogue
-    tink::core::key_manager_impl
     tink::core::key_type_manager
-    tink::core::private_key_manager_impl
     tink::core::private_key_type_manager
+    tink::core::input_stream
     tink::core::key_manager
     tink::core::primitive_set
     tink::core::primitive_wrapper
     tink::monitoring::monitoring
     tink::util::errors
-    tink::util::protobuf_helper
     tink::util::status
     tink::util::statusor
-    tink::util::validation
     tink::proto::tink_cc_proto
 )
 
@@ -152,6 +158,8 @@
     bn_util.h
   DEPS
     tink::internal::ssl_unique_ptr
+    absl::status
+    absl::strings
     absl::span
     crypto
     tink::subtle::subtle_util
@@ -168,9 +176,11 @@
   DEPS
     tink::internal::bn_util
     tink::internal::err_util
+    tink::internal::fips_utils
     tink::internal::ssl_unique_ptr
     tink::internal::ssl_util
     absl::status
+    absl::statusor
     absl::strings
     crypto
     tink::config::tink_fips
@@ -204,6 +214,7 @@
     absl::strings
     tink::core::primitive_set
     tink::core::primitive_wrapper
+    tink::util::status
     tink::util::statusor
     tink::util::test_matchers
     tink::util::test_util
@@ -217,6 +228,7 @@
   DEPS
     tink::internal::key_info
     gmock
+    protobuf::libprotobuf-lite
     tink::proto::tink_cc_proto
 )
 
@@ -225,39 +237,47 @@
   SRCS
     registry_impl_test.cc
   DEPS
+    tink::internal::fips_utils
+    tink::internal::registry_impl
     gmock
     absl::memory
     absl::status
+    absl::statusor
     absl::strings
     crypto
     tink::core::aead
-    tink::core::catalogue
     tink::core::key_manager_impl
     tink::core::key_type_manager
-    tink::core::crypto_format
-    tink::core::keyset_manager
+    tink::core::private_key_manager_impl
+    tink::core::private_key_type_manager
+    tink::core::template_util
+    tink::core::hybrid_decrypt
+    tink::core::input_stream
+    tink::core::key_manager
+    tink::core::mac
+    tink::core::primitive_set
+    tink::core::primitive_wrapper
     tink::core::registry
     tink::aead::aead_wrapper
     tink::aead::aes_gcm_key_manager
-    tink::config::tink_fips
     tink::hybrid::ecies_aead_hkdf_private_key_manager
     tink::hybrid::ecies_aead_hkdf_public_key_manager
-    tink::monitoring::monitoring
     tink::monitoring::monitoring_client_mocks
     tink::subtle::aes_gcm_boringssl
     tink::subtle::random
+    tink::util::input_stream_util
     tink::util::istream_input_stream
     tink::util::protobuf_helper
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
-    tink::util::test_keyset_handle
     tink::util::test_matchers
     tink::util::test_util
     tink::proto::aes_ctr_hmac_aead_cc_proto
     tink::proto::aes_gcm_cc_proto
     tink::proto::common_cc_proto
     tink::proto::ecdsa_cc_proto
+    tink::proto::ecies_aead_hkdf_cc_proto
     tink::proto::tink_cc_proto
 )
 
@@ -281,8 +301,10 @@
     tink::internal::ssl_unique_ptr
     gmock
     absl::strings
+    absl::span
     crypto
     tink::util::secret_data
+    tink::util::status
     tink::util::statusor
     tink::util::test_matchers
 )
@@ -296,9 +318,11 @@
     tink::internal::rsa_util
     tink::internal::ssl_unique_ptr
     gmock
+    absl::status
     absl::strings
     crypto
     tink::subtle::random
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -330,13 +354,16 @@
     tink::internal::ssl_unique_ptr
     tink::internal::ssl_util
     gmock
+    absl::status
     absl::strings
     absl::span
     crypto
+    rapidjson
     tink::subtle::common_enums
     tink::subtle::subtle_util
     tink::subtle::wycheproof_util
     tink::util::secret_data
+    tink::util::status
     tink::util::statusor
     tink::util::test_matchers
 )
@@ -366,9 +393,9 @@
     tink::internal::md_util
     gmock
     absl::strings
-    absl::span
     crypto
     tink::subtle::common_enums
+    tink::util::status
     tink::util::statusor
     tink::util::test_matchers
 )
@@ -402,6 +429,7 @@
     crypto
     tink::subtle::subtle_util
     tink::util::secret_data
+    tink::util::status
     tink::util::statusor
     tink::util::test_matchers
 )
@@ -411,10 +439,14 @@
   SRCS
     monitoring_util.h
   DEPS
+    tink::internal::key_status_util
     absl::flat_hash_map
     absl::status
+    absl::strings
+    tink::core::key_status
     tink::core::primitive_set
     tink::monitoring::monitoring
+    tink::util::status
     tink::util::statusor
     tink::proto::tink_cc_proto
 )
@@ -426,10 +458,697 @@
   DEPS
     tink::internal::monitoring_util
     gmock
+    absl::flat_hash_map
+    absl::memory
     absl::status
     absl::strings
+    tink::core::key_status
     tink::core::primitive_set
     tink::monitoring::monitoring
+    tink::util::status
+    tink::util::statusor
     tink::util::test_matchers
     tink::proto::tink_cc_proto
 )
+
+tink_cc_library(
+  NAME serialization
+  SRCS
+    serialization.h
+  DEPS
+    absl::strings
+)
+
+tink_cc_library(
+  NAME proto_parameters_serialization
+  SRCS
+    proto_parameters_serialization.cc
+    proto_parameters_serialization.h
+  DEPS
+    tink::internal::serialization
+    tink::internal::util
+    absl::status
+    absl::strings
+    tink::util::status
+    tink::util::statusor
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME proto_parameters_serialization_test
+  SRCS
+    proto_parameters_serialization_test.cc
+  DEPS
+    tink::internal::proto_parameters_serialization
+    gmock
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::test_proto_cc_proto
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME proto_key_serialization
+  SRCS
+    proto_key_serialization.cc
+    proto_key_serialization.h
+  DEPS
+    tink::internal::serialization
+    tink::internal::util
+    absl::status
+    absl::strings
+    absl::optional
+    tink::core::restricted_data
+    tink::util::status
+    tink::util::statusor
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME proto_key_serialization_test
+  SRCS
+    proto_key_serialization_test.cc
+  DEPS
+    tink::internal::proto_key_serialization
+    gmock
+    absl::status
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::restricted_data
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME legacy_proto_parameters
+  SRCS
+    legacy_proto_parameters.cc
+    legacy_proto_parameters.h
+  DEPS
+    tink::internal::proto_parameters_serialization
+    tink::core::parameters
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME legacy_proto_parameters_test
+  SRCS
+    legacy_proto_parameters_test.cc
+  DEPS
+    tink::internal::legacy_proto_parameters
+    tink::internal::proto_parameters_serialization
+    gmock
+    tink::core::parameters
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::test_proto_cc_proto
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME legacy_proto_key
+  SRCS
+    legacy_proto_key.cc
+    legacy_proto_key.h
+  DEPS
+    tink::internal::proto_key_serialization
+    absl::status
+    absl::strings
+    absl::optional
+    tink::core::key
+    tink::core::parameters
+    tink::core::secret_key_access_token
+    tink::util::status
+    tink::util::statusor
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME legacy_proto_key_test
+  SRCS
+    legacy_proto_key_test.cc
+  DEPS
+    tink::internal::legacy_proto_key
+    tink::internal::proto_key_serialization
+    gmock
+    absl::status
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::parameters
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME parser_index
+  SRCS
+    parser_index.h
+  DEPS
+    tink::internal::serialization
+    absl::strings
+)
+
+tink_cc_test(
+  NAME parser_index_test
+  SRCS
+    parser_index_test.cc
+  DEPS
+    tink::internal::parser_index
+    tink::internal::serialization
+    gmock
+    absl::strings
+)
+
+tink_cc_library(
+  NAME serializer_index
+  SRCS
+    serializer_index.h
+  DEPS
+    tink::internal::serialization
+    tink::core::key
+    tink::core::parameters
+)
+
+tink_cc_test(
+  NAME serializer_index_test
+  SRCS
+    serializer_index_test.cc
+  DEPS
+    tink::internal::serialization_test_util
+    tink::internal::serializer_index
+    gmock
+)
+
+tink_cc_library(
+  NAME parameters_parser
+  SRCS
+    parameters_parser.h
+  DEPS
+    tink::internal::parser_index
+    tink::internal::serialization
+    absl::status
+    absl::strings
+    tink::core::parameters
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME parameters_parser_test
+  SRCS
+    parameters_parser_test.cc
+  DEPS
+    tink::internal::parameters_parser
+    tink::internal::parser_index
+    tink::internal::serialization
+    tink::internal::serialization_test_util
+    gmock
+    absl::memory
+    absl::status
+    tink::core::parameters
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME parameters_serializer
+  SRCS
+    parameters_serializer.h
+  DEPS
+    tink::internal::serialization
+    tink::internal::serializer_index
+    absl::status
+    absl::strings
+    tink::core::parameters
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME parameters_serializer_test
+  SRCS
+    parameters_serializer_test.cc
+  DEPS
+    tink::internal::parameters_serializer
+    tink::internal::serialization
+    tink::internal::serialization_test_util
+    tink::internal::serializer_index
+    gmock
+    absl::memory
+    absl::status
+    tink::core::parameters
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME key_parser
+  SRCS
+    key_parser.h
+  DEPS
+    tink::internal::parser_index
+    tink::internal::serialization
+    absl::function_ref
+    absl::log
+    absl::status
+    absl::strings
+    absl::optional
+    tink::core::key
+    tink::core::secret_key_access_token
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME key_parser_test
+  SRCS
+    key_parser_test.cc
+  DEPS
+    tink::internal::key_parser
+    tink::internal::parser_index
+    tink::internal::serialization
+    tink::internal::serialization_test_util
+    gmock
+    absl::memory
+    absl::status
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::parameters
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME key_serializer
+  SRCS
+    key_serializer.h
+  DEPS
+    tink::internal::serialization
+    tink::internal::serializer_index
+    absl::function_ref
+    absl::log
+    absl::status
+    absl::optional
+    tink::core::key
+    tink::core::secret_key_access_token
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME key_serializer_test
+  SRCS
+    key_serializer_test.cc
+  DEPS
+    tink::internal::key_serializer
+    tink::internal::serialization
+    tink::internal::serialization_test_util
+    tink::internal::serializer_index
+    gmock
+    absl::memory
+    absl::status
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::secret_key_access_token
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME key_status_util
+  SRCS
+    key_status_util.cc
+    key_status_util.h
+  DEPS
+    absl::status
+    tink::core::key_status
+    tink::util::status
+    tink::util::statusor
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME key_status_util_test
+  SRCS
+    key_status_util_test.cc
+  DEPS
+    tink::internal::key_status_util
+    gmock
+    absl::status
+    tink::core::key_status
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME keyset_handle_builder_entry
+  SRCS
+    keyset_handle_builder_entry.cc
+    keyset_handle_builder_entry.h
+  DEPS
+    tink::internal::key_status_util
+    tink::internal::legacy_proto_key
+    tink::internal::legacy_proto_parameters
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::internal::serialization
+    absl::status
+    absl::strings
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::key_status
+    tink::core::parameters
+    tink::core::registry
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::util::status
+    tink::util::statusor
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME keyset_handle_builder_entry_test
+  SRCS
+    keyset_handle_builder_entry_test.cc
+  DEPS
+    tink::internal::keyset_handle_builder_entry
+    tink::internal::legacy_proto_key
+    tink::internal::legacy_proto_parameters
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    gmock
+    absl::memory
+    absl::status
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::key_status
+    tink::core::keyset_handle
+    tink::core::keyset_handle_builder
+    tink::core::parameters
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::config::tink_config
+    tink::mac::aes_cmac_key
+    tink::mac::aes_cmac_parameters
+    tink::mac::mac_key_templates
+    tink::util::status
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME serialization_registry
+  SRCS
+    serialization_registry.cc
+    serialization_registry.h
+  DEPS
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::parser_index
+    tink::internal::serialization
+    tink::internal::serializer_index
+    absl::flat_hash_map
+    absl::status
+    absl::str_format
+    absl::optional
+    tink::core::key
+    tink::core::parameters
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME serialization_registry_test
+  SRCS
+    serialization_registry_test.cc
+  DEPS
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::serialization
+    tink::internal::serialization_registry
+    tink::internal::serialization_test_util
+    gmock
+    absl::status
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::parameters
+    tink::core::secret_key_access_token
+    tink::util::status
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME mutable_serialization_registry
+  SRCS
+    mutable_serialization_registry.cc
+    mutable_serialization_registry.h
+  DEPS
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::legacy_proto_key
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::proto_key_serialization
+    tink::internal::serialization
+    tink::internal::serialization_registry
+    absl::core_headers
+    absl::memory
+    absl::status
+    absl::synchronization
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::parameters
+    tink::core::secret_key_access_token
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME mutable_serialization_registry_test
+  SRCS
+    mutable_serialization_registry_test.cc
+  DEPS
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::mutable_serialization_registry
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::proto_key_serialization
+    tink::internal::serialization
+    tink::internal::serialization_test_util
+    gmock
+    absl::status
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::key
+    tink::core::parameters
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::util::status
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME serialization_test_util
+  SRCS
+    serialization_test_util.h
+  DEPS
+    tink::internal::serialization
+    absl::strings
+    absl::optional
+    tink::core::key
+    tink::core::parameters
+    tink::core::secret_key_access_token
+    tink::util::statusor
+  TESTONLY
+)
+
+tink_cc_test(
+  NAME serialization_test_util_test
+  SRCS
+    serialization_test_util_test.cc
+  DEPS
+    tink::internal::serialization_test_util
+    gmock
+    absl::optional
+    tink::core::insecure_secret_key_access
+    tink::core::parameters
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME test_random_access_stream
+  SRCS
+    test_random_access_stream.cc
+    test_random_access_stream.h
+  DEPS
+    absl::status
+    absl::strings
+    tink::core::random_access_stream
+    tink::util::buffer
+    tink::util::status
+    tink::util::statusor
+  TESTONLY
+)
+
+tink_cc_test(
+  NAME test_random_access_stream_test
+  SRCS
+    test_random_access_stream_test.cc
+  DEPS
+    tink::internal::test_random_access_stream
+    gmock
+    absl::status
+    tink::subtle::random
+    tink::util::buffer
+    tink::util::status
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME configuration_impl
+  SRCS
+    configuration_impl.h
+  DEPS
+    tink::internal::key_type_info_store
+    tink::internal::keyset_wrapper_store
+    tink::core::configuration
+)
+
+tink_cc_test(
+  NAME configuration_impl_test
+  SRCS
+    configuration_impl_test.cc
+  DEPS
+    tink::internal::configuration_impl
+    tink::internal::keyset_wrapper_store
+    gmock
+    absl::status
+    tink::core::cleartext_keyset_handle
+    tink::core::configuration
+    tink::subtle::random
+    tink::util::test_matchers
+    tink::util::test_util
+    tink::proto::aes_gcm_cc_proto
+    tink::proto::rsa_ssa_pss_cc_proto
+)
+
+tink_cc_library(
+  NAME key_type_info_store
+  SRCS
+    key_type_info_store.cc
+    key_type_info_store.h
+  DEPS
+    tink::internal::fips_utils
+    absl::flat_hash_map
+    absl::status
+    absl::strings
+    tink::core::key_manager_impl
+    tink::core::key_type_manager
+    tink::core::private_key_manager_impl
+    tink::core::private_key_type_manager
+    tink::core::key_manager
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME key_type_info_store_test
+  SRCS
+    key_type_info_store_test.cc
+  DEPS
+    tink::internal::fips_utils
+    tink::internal::key_type_info_store
+    gmock
+    absl::status
+    absl::optional
+    tink::core::aead
+    tink::core::key_manager_impl
+    tink::core::key_manager
+    tink::aead::aes_gcm_key_manager
+    tink::aead::cord_aead
+    tink::aead::kms_envelope_aead_key_manager
+    tink::signature::ecdsa_sign_key_manager
+    tink::signature::ecdsa_verify_key_manager
+    tink::util::test_matchers
+    tink::proto::aes_gcm_cc_proto
+    tink::proto::common_cc_proto
+    tink::proto::ecdsa_cc_proto
+)
+
+tink_cc_library(
+  NAME keyset_wrapper_store
+  SRCS
+    keyset_wrapper_store.h
+  DEPS
+    tink::internal::keyset_wrapper
+    tink::internal::keyset_wrapper_impl
+    tink::core::primitive_wrapper
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME keyset_wrapper_store_test
+  SRCS
+    keyset_wrapper_store_test.cc
+  DEPS
+    tink::internal::keyset_wrapper_store
+    tink::internal::registry_impl
+    gmock
+    absl::status
+    tink::core::primitive_set
+    tink::core::primitive_wrapper
+    tink::mac::mac_wrapper
+    tink::subtle::random
+    tink::util::status
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::util::test_util
+    tink::proto::aes_gcm_cc_proto
+)
+
+tink_cc_library(
+  NAME key_gen_configuration_impl
+  SRCS
+    key_gen_configuration_impl.h
+  DEPS
+    tink::internal::key_type_info_store
+    tink::core::key_gen_configuration
+)
+
+tink_cc_test(
+  NAME key_gen_configuration_impl_test
+  SRCS
+    key_gen_configuration_impl_test.cc
+  DEPS
+    tink::internal::key_gen_configuration_impl
+    gmock
+    tink::core::key_gen_configuration
+    tink::aead::aead_key_templates
+    tink::util::test_matchers
+    tink::util::test_util
+    tink::proto::aes_gcm_cc_proto
+    tink::proto::rsa_ssa_pss_cc_proto
+)
diff --git a/cc/internal/aes_util.cc b/cc/internal/aes_util.cc
index d7d8c29..55af17b 100644
--- a/cc/internal/aes_util.cc
+++ b/cc/internal/aes_util.cc
@@ -16,6 +16,7 @@
 #include "tink/internal/aes_util.h"
 
 #include <cstdint>
+#include <string>
 #include <vector>
 
 #include "absl/status/status.h"
@@ -24,6 +25,7 @@
 #include "absl/types/span.h"
 #include "openssl/aes.h"
 #include "openssl/evp.h"
+#include "tink/util/statusor.h"
 #ifndef OPENSSL_IS_BORINGSSL
 // This is needed to use block128_f, which is necessary when OpenSSL is used.
 #include "openssl/modes.h"
diff --git a/cc/internal/aes_util_test.cc b/cc/internal/aes_util_test.cc
index d28a813..806a49b 100644
--- a/cc/internal/aes_util_test.cc
+++ b/cc/internal/aes_util_test.cc
@@ -18,7 +18,6 @@
 #include <algorithm>
 #include <cstdint>
 #include <string>
-#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -30,6 +29,7 @@
 #include "openssl/evp.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/util/secret_data.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 
diff --git a/cc/internal/bn_util.cc b/cc/internal/bn_util.cc
index 22a2513..0d73ba8 100644
--- a/cc/internal/bn_util.cc
+++ b/cc/internal/bn_util.cc
@@ -15,15 +15,21 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/bn_util.h"
 
+#include <stddef.h>
+
+#include <memory>
 #include <string>
 #include <utility>
 
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "openssl/bn.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
+#include "tink/util/statusor.h"
 
 namespace crypto {
 namespace tink {
@@ -34,6 +40,10 @@
   if (bignum == nullptr) {
     return util::Status(absl::StatusCode::kInvalidArgument, "BIGNUM is NULL");
   }
+  if (BN_is_negative(bignum)) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Value must not be negative");
+  }
 
   // BN_bn2binpad returns the length of the buffer on success and -1 on failure.
   int len = BN_bn2binpad(
diff --git a/cc/internal/bn_util.h b/cc/internal/bn_util.h
index 8eea532..fce7eda 100644
--- a/cc/internal/bn_util.h
+++ b/cc/internal/bn_util.h
@@ -16,11 +16,16 @@
 #ifndef TINK_INTERNAL_BN_UTIL_H_
 #define TINK_INTERNAL_BN_UTIL_H_
 
+#include <stddef.h>
+
 #include <string>
 
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "openssl/bn.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/util/secret_data.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
 namespace crypto {
diff --git a/cc/internal/bn_util_test.cc b/cc/internal/bn_util_test.cc
index 161cbe6..7b6fd5c 100644
--- a/cc/internal/bn_util_test.cc
+++ b/cc/internal/bn_util_test.cc
@@ -15,6 +15,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/bn_util.h"
 
+#include <stddef.h>
+
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -22,9 +25,12 @@
 #include "gtest/gtest.h"
 #include "absl/strings/escaping.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "openssl/bn.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/util/secret_data.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 
@@ -59,6 +65,21 @@
   }
 }
 
+TEST(StringToBignum, IgnoresLeadingZeros) {
+  std::string encoded = absl::HexStringToBytes("0102");
+  std::string encoded_with_leading_zeros = absl::HexStringToBytes("0000000102");
+
+  util::StatusOr<internal::SslUniquePtr<BIGNUM>> num =
+      StringToBignum(encoded);
+  ASSERT_THAT(num, IsOk());
+
+  util::StatusOr<internal::SslUniquePtr<BIGNUM>> num2 =
+      StringToBignum(encoded_with_leading_zeros);
+  ASSERT_THAT(num2, IsOk());
+
+  EXPECT_EQ(BN_cmp(num2->get(), num->get()), 0);
+}
+
 TEST(BnUtil, BignumToString) {
   std::vector<std::string> bn_strs = {"0000000000000000", "0000000000000001",
                                       "1000000000000000", "ffffffffffffffff",
@@ -75,6 +96,94 @@
   }
 }
 
+TEST(BignumToStringWithBNNumBytes, NoLeadingZeros) {
+  {
+    util::StatusOr<internal::SslUniquePtr<BIGNUM>> bn0 =
+      StringToBignum(absl::HexStringToBytes("000000"));
+    ASSERT_THAT(bn0, IsOk());
+
+    util::StatusOr<std::string> encoded0 =
+        internal::BignumToString(bn0->get(), BN_num_bytes(bn0->get()));
+    ASSERT_THAT(encoded0, IsOk());
+    EXPECT_EQ(*encoded0, absl::HexStringToBytes(""));
+  }
+
+  {
+    util::StatusOr<internal::SslUniquePtr<BIGNUM>> bn127 =
+      StringToBignum(absl::HexStringToBytes("00007F"));
+    ASSERT_THAT(bn127, IsOk());
+
+    util::StatusOr<std::string> encoded127 =
+        internal::BignumToString(bn127->get(), BN_num_bytes(bn127->get()));
+    ASSERT_THAT(encoded127, IsOk());
+    EXPECT_EQ(*encoded127, absl::HexStringToBytes("7F"));
+  }
+
+  {
+    util::StatusOr<internal::SslUniquePtr<BIGNUM>> bn128 =
+        StringToBignum(absl::HexStringToBytes("000080"));
+    ASSERT_THAT(bn128, IsOk());
+
+    util::StatusOr<std::string> encoded128 =
+        internal::BignumToString(bn128->get(), BN_num_bytes(bn128->get()));
+    ASSERT_THAT(encoded128, IsOk());
+    EXPECT_EQ(*encoded128, absl::HexStringToBytes("80"));
+  }
+
+  {
+    util::StatusOr<internal::SslUniquePtr<BIGNUM>> bn255 =
+      StringToBignum(absl::HexStringToBytes("0000FF"));
+    ASSERT_THAT(bn255, IsOk());
+
+      util::StatusOr<std::string> encoded255 =
+        internal::BignumToString(bn255->get(), BN_num_bytes(bn255->get()));
+    ASSERT_THAT(encoded255, IsOk());
+    EXPECT_EQ(*encoded255, absl::HexStringToBytes("FF"));
+  }
+
+  {
+    util::StatusOr<internal::SslUniquePtr<BIGNUM>> bn256 =
+      StringToBignum(absl::HexStringToBytes("000100"));
+    ASSERT_THAT(bn256, IsOk());
+
+    util::StatusOr<std::string> encoded256 =
+        internal::BignumToString(bn256->get(), BN_num_bytes(bn256->get()));
+    ASSERT_THAT(encoded256, IsOk());
+    EXPECT_EQ(*encoded256, absl::HexStringToBytes("0100"));
+  }
+}
+
+
+TEST(BignumToString, PadsWithLeadingZeros) {
+  util::StatusOr<internal::SslUniquePtr<BIGNUM>> num =
+      StringToBignum(absl::HexStringToBytes("0102"));
+  ASSERT_THAT(num, IsOk());
+
+  util::StatusOr<std::string> encoded =
+      BignumToString(num->get(), /*len=*/ 2);
+  ASSERT_THAT(encoded, IsOk());
+  EXPECT_EQ(*encoded, absl::HexStringToBytes("0102"));
+
+  util::StatusOr<std::string> encodedWithPadding =
+      BignumToString(num->get(), /*len=*/ 5);
+  ASSERT_THAT(encodedWithPadding, IsOk());
+  EXPECT_EQ(*encodedWithPadding, absl::HexStringToBytes("0000000102"));
+
+  // try to encode with a value for len that is too short.
+  ASSERT_THAT(BignumToString(num->get(), /*len=*/1), Not(IsOk()));
+}
+
+TEST(BignumToString, RejectsNegativeNumbers) {
+  // create a negative BIGNUM
+  util::StatusOr<internal::SslUniquePtr<BIGNUM>> number = HexToBignum("01");
+  ASSERT_THAT(number, IsOk());
+  BN_set_negative(number->get(), 1);
+  // Check that number is negative
+  ASSERT_EQ(CompareBignumWithWord(number->get(), /*word=*/0), -1);
+
+  ASSERT_THAT(BignumToString(number->get(), /*len=*/2), Not(IsOk()));
+}
+
 TEST(BnUtil, BignumToSecretData) {
   std::vector<std::string> bn_strs = {"0000000000000000", "0000000000000001",
                                       "1000000000000000", "ffffffffffffffff",
diff --git a/cc/internal/configuration_impl.h b/cc/internal/configuration_impl.h
new file mode 100644
index 0000000..cf59e10
--- /dev/null
+++ b/cc/internal/configuration_impl.h
@@ -0,0 +1,141 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_CONFIGURATION_IMPL_H_
+#define TINK_INTERNAL_CONFIGURATION_IMPL_H_
+
+#include "tink/configuration.h"
+#include "tink/internal/key_type_info_store.h"
+#include "tink/internal/keyset_wrapper_store.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+constexpr absl::string_view kConfigurationImplErr =
+    "Use crypto::tink::Registry instead when in global registry mode.";
+
+class ConfigurationImpl {
+ public:
+  template <class PW>
+  static crypto::tink::util::Status AddPrimitiveWrapper(
+      std::unique_ptr<PW> wrapper, crypto::tink::Configuration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kConfigurationImplErr);
+    }
+
+    // Function `primitive_getter` must be defined here, since
+    // PW::InputPrimitive is not accessible later.
+    // TODO(b/284084337): Move primitive getter out of key manager.
+    std::function<crypto::tink::util::StatusOr<
+        std::unique_ptr<typename PW::InputPrimitive>>(
+        const google::crypto::tink::KeyData& key_data)>
+        primitive_getter =
+            [&config](const google::crypto::tink::KeyData& key_data)
+        -> crypto::tink::util::StatusOr<
+            std::unique_ptr<typename PW::InputPrimitive>> {
+      crypto::tink::util::StatusOr<
+          const crypto::tink::internal::KeyTypeInfoStore::Info*>
+          info = config.key_type_info_store_.Get(key_data.type_url());
+      if (!info.ok()) {
+        return info.status();
+      }
+
+      crypto::tink::util::StatusOr<
+          const crypto::tink::KeyManager<typename PW::InputPrimitive>*>
+          key_manager = (*info)->get_key_manager<typename PW::InputPrimitive>(
+              key_data.type_url());
+      if (!key_manager.ok()) {
+        return key_manager.status();
+      }
+
+      return (*key_manager)->GetPrimitive(key_data);
+    };
+
+    return config.keyset_wrapper_store_
+        .Add<typename PW::InputPrimitive, typename PW::Primitive>(
+            std::move(wrapper), primitive_getter);
+  }
+
+  template <class KM>
+  static crypto::tink::util::Status AddKeyTypeManager(
+      std::unique_ptr<KM> key_manager, crypto::tink::Configuration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kConfigurationImplErr);
+    }
+    return config.key_type_info_store_.AddKeyTypeManager(
+        std::move(key_manager), /*new_key_allowed=*/true);
+  }
+
+  template <class PrivateKM, class PublicKM>
+  static crypto::tink::util::Status AddAsymmetricKeyManagers(
+      std::unique_ptr<PrivateKM> private_key_manager,
+      std::unique_ptr<PublicKM> public_key_manager,
+      crypto::tink::Configuration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kConfigurationImplErr);
+    }
+    return config.key_type_info_store_.AddAsymmetricKeyTypeManagers(
+        std::move(private_key_manager), std::move(public_key_manager),
+        /*new_key_allowed=*/true);
+  }
+
+  static crypto::tink::util::StatusOr<
+      const crypto::tink::internal::KeyTypeInfoStore*>
+  GetKeyTypeInfoStore(const crypto::tink::Configuration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kConfigurationImplErr);
+    }
+    return &config.key_type_info_store_;
+  }
+
+  static crypto::tink::util::StatusOr<
+      const crypto::tink::internal::KeysetWrapperStore*>
+  GetKeysetWrapperStore(const crypto::tink::Configuration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kConfigurationImplErr);
+    }
+    return &config.keyset_wrapper_store_;
+  }
+
+  // `config` can be set to global registry mode only if empty.
+  static crypto::tink::util::Status SetGlobalRegistryMode(
+      crypto::tink::Configuration& config) {
+    if (!config.key_type_info_store_.IsEmpty() ||
+        !config.keyset_wrapper_store_.IsEmpty()) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        "Using the global registry is only "
+                                        "allowed when Configuration is empty.");
+    }
+    config.global_registry_mode_ = true;
+    return crypto::tink::util::OkStatus();
+  }
+
+  static bool GetGlobalRegistryMode(const crypto::tink::Configuration& config) {
+    return config.global_registry_mode_;
+  }
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_CONFIGURATION_IMPL_H_
diff --git a/cc/internal/configuration_impl_test.cc b/cc/internal/configuration_impl_test.cc
new file mode 100644
index 0000000..0b33108
--- /dev/null
+++ b/cc/internal/configuration_impl_test.cc
@@ -0,0 +1,466 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/configuration_impl.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/configuration.h"
+#include "tink/internal/keyset_wrapper_store.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/rsa_ssa_pss.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::OutputPrefixType;
+using ::google::crypto::tink::RsaSsaPssKeyFormat;
+using ::google::crypto::tink::RsaSsaPssParams;
+using ::google::crypto::tink::RsaSsaPssPrivateKey;
+using ::google::crypto::tink::RsaSsaPssPublicKey;
+
+class FakePrimitive {
+ public:
+  explicit FakePrimitive(std::string s) : s_(s) {}
+  std::string get() { return s_; }
+
+ private:
+  std::string s_;
+};
+
+class FakePrimitive2 {
+ public:
+  explicit FakePrimitive2(std::string s) : s_(s) {}
+  std::string get() { return s_ + "2"; }
+
+ private:
+  std::string s_;
+};
+
+// Transforms AesGcmKey into FakePrimitive.
+class FakeKeyTypeManager
+    : public KeyTypeManager<AesGcmKey, AesGcmKeyFormat, List<FakePrimitive>> {
+ public:
+  class FakePrimitiveFactory : public PrimitiveFactory<FakePrimitive> {
+   public:
+    util::StatusOr<std::unique_ptr<FakePrimitive>> Create(
+        const AesGcmKey& key) const override {
+      return absl::make_unique<FakePrimitive>(key.key_value());
+    }
+  };
+
+  FakeKeyTypeManager()
+      : KeyTypeManager(absl::make_unique<FakePrimitiveFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const AesGcmKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateKeyFormat(
+      const AesGcmKeyFormat& key_format) const override {
+    return util::OkStatus();
+  }
+
+  util::StatusOr<AesGcmKey> CreateKey(
+      const AesGcmKeyFormat& key_format) const override {
+    return AesGcmKey();
+  }
+
+  util::StatusOr<AesGcmKey> DeriveKey(
+      const AesGcmKeyFormat& key_format,
+      InputStream* input_stream) const override {
+    return AesGcmKey();
+  }
+
+ private:
+  const std::string key_type_ =
+      "type.googleapis.com/google.crypto.tink.AesGcmKey";
+};
+
+// Transforms FakePrimitive into FakePrimitive.
+class FakePrimitiveWrapper
+    : public PrimitiveWrapper<FakePrimitive, FakePrimitive> {
+ public:
+  util::StatusOr<std::unique_ptr<FakePrimitive>> Wrap(
+      std::unique_ptr<PrimitiveSet<FakePrimitive>> primitive_set)
+      const override {
+    return absl::make_unique<FakePrimitive>(
+        primitive_set->get_primary()->get_primitive().get());
+  }
+};
+
+// Transforms FakePrimitive2 into FakePrimitive.
+class FakePrimitiveWrapper2
+    : public PrimitiveWrapper<FakePrimitive2, FakePrimitive> {
+ public:
+  util::StatusOr<std::unique_ptr<FakePrimitive>> Wrap(
+      std::unique_ptr<PrimitiveSet<FakePrimitive2>> primitive_set)
+      const override {
+    return absl::make_unique<FakePrimitive>(
+        primitive_set->get_primary()->get_primitive().get());
+  }
+};
+
+std::string AddAesGcmKeyToKeyset(Keyset& keyset, uint32_t key_id,
+                                 OutputPrefixType output_prefix_type,
+                                 KeyStatusType key_status_type) {
+  AesGcmKey key;
+  key.set_version(0);
+  key.set_key_value(subtle::Random::GetRandomBytes(16));
+  KeyData key_data;
+  key_data.set_value(key.SerializeAsString());
+  key_data.set_type_url("type.googleapis.com/google.crypto.tink.AesGcmKey");
+  test::AddKeyData(key_data, key_id, output_prefix_type, key_status_type,
+                   &keyset);
+  return key.key_value();
+}
+
+TEST(ConfigurationImplTest, AddPrimitiveWrapper) {
+  Configuration config;
+  EXPECT_THAT((ConfigurationImpl::AddPrimitiveWrapper(
+                  absl::make_unique<FakePrimitiveWrapper>(), config)),
+              IsOk());
+}
+
+TEST(ConfigurationImplTest, AddKeyTypeManager) {
+  Configuration config;
+  EXPECT_THAT(ConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              IsOk());
+}
+
+TEST(ConfigurationImplTest, GetKeyTypeInfoStore) {
+  Configuration config;
+  ASSERT_THAT(ConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              IsOk());
+
+  std::string type_url = FakeKeyTypeManager().get_key_type();
+  util::StatusOr<const KeyTypeInfoStore*> store =
+      ConfigurationImpl::GetKeyTypeInfoStore(config);
+  ASSERT_THAT(store, IsOk());
+  util::StatusOr<const KeyTypeInfoStore::Info*> info = (*store)->Get(type_url);
+  ASSERT_THAT(info, IsOk());
+
+  util::StatusOr<const KeyManager<FakePrimitive>*> key_manager =
+      (*info)->get_key_manager<FakePrimitive>(type_url);
+  ASSERT_THAT(key_manager, IsOk());
+  EXPECT_EQ((*key_manager)->get_key_type(), type_url);
+}
+
+TEST(ConfigurationImplTest, GetKeyTypeInfoStoreMissingInfoFails) {
+  Configuration config;
+  util::StatusOr<const KeyTypeInfoStore*> store =
+      ConfigurationImpl::GetKeyTypeInfoStore(config);
+  ASSERT_THAT(store, IsOk());
+  EXPECT_THAT((*store)->Get("i.do.not.exist").status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(ConfigurationImplTest, GetKeysetWrapperStoreAndWrap) {
+  Configuration config;
+  ASSERT_THAT((ConfigurationImpl::AddPrimitiveWrapper(
+                  absl::make_unique<FakePrimitiveWrapper>(), config)),
+              IsOk());
+  ASSERT_THAT(ConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              IsOk());
+
+  util::StatusOr<const KeysetWrapperStore*> store =
+      ConfigurationImpl::GetKeysetWrapperStore(config);
+  ASSERT_THAT(store, IsOk());
+  util::StatusOr<const KeysetWrapper<FakePrimitive>*> wrapper =
+      (*store)->Get<FakePrimitive>();
+  ASSERT_THAT(wrapper, IsOk());
+
+  Keyset keyset;
+  std::string raw_key = AddAesGcmKeyToKeyset(
+      keyset, /*key_id=*/13, OutputPrefixType::TINK, KeyStatusType::ENABLED);
+  keyset.set_primary_key_id(13);
+
+  util::StatusOr<std::unique_ptr<FakePrimitive>> aead =
+      (*wrapper)->Wrap(keyset, /*annotations=*/{});
+  ASSERT_THAT(aead, IsOk());
+  EXPECT_EQ((*aead)->get(), raw_key);
+}
+
+TEST(ConfigurationImplTest, KeysetWrapperWrapMissingKeyTypeInfoFails) {
+  Configuration config;
+  ASSERT_THAT(ConfigurationImpl::AddPrimitiveWrapper(
+                  absl::make_unique<FakePrimitiveWrapper>(), config),
+              IsOk());
+
+  util::StatusOr<const KeysetWrapperStore*> store =
+      ConfigurationImpl::GetKeysetWrapperStore(config);
+  ASSERT_THAT(store, IsOk());
+  util::StatusOr<const KeysetWrapper<FakePrimitive>*> wrapper =
+      (*store)->Get<FakePrimitive>();
+  ASSERT_THAT(wrapper, IsOk());
+
+  Keyset keyset;
+  std::string raw_key = AddAesGcmKeyToKeyset(
+      keyset, /*key_id=*/13, OutputPrefixType::TINK, KeyStatusType::ENABLED);
+  keyset.set_primary_key_id(13);
+
+  EXPECT_THAT((*wrapper)->Wrap(keyset, /*annotations=*/{}).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(ConfigurationImplTest, KeysetWrapperWrapMissingKeyManagerFails) {
+  Configuration config;
+  // Transforms FakePrimitive2 to FakePrimitive.
+  ASSERT_THAT((ConfigurationImpl::AddPrimitiveWrapper(
+                  absl::make_unique<FakePrimitiveWrapper2>(), config)),
+              IsOk());
+  // Transforms KeyData to FakePrimitive.
+  ASSERT_THAT(ConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              IsOk());
+
+  // AesGcmKey KeyData -> FakePrimitive2 -> FakePrimitive is the success path,
+  // but the AesGcmKey KeyData -> FakePrimitive2 transformation is not
+  // registered.
+  util::StatusOr<const KeysetWrapperStore*> store =
+      ConfigurationImpl::GetKeysetWrapperStore(config);
+  ASSERT_THAT(store, IsOk());
+  util::StatusOr<const KeysetWrapper<FakePrimitive>*> wrapper =
+      (*store)->Get<FakePrimitive>();
+  ASSERT_THAT(wrapper, IsOk());
+
+  Keyset keyset;
+  std::string raw_key = AddAesGcmKeyToKeyset(
+      keyset, /*key_id=*/13, OutputPrefixType::TINK, KeyStatusType::ENABLED);
+  keyset.set_primary_key_id(13);
+
+  // FakeKeyTypeManager cannot transform AesGcmKey KeyData -> FakePrimitive2.
+  EXPECT_THAT((*wrapper)->Wrap(keyset, /*annotations=*/{}).status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+class FakeSignKeyManager
+    : public PrivateKeyTypeManager<RsaSsaPssPrivateKey, RsaSsaPssKeyFormat,
+                                   RsaSsaPssPublicKey, List<PublicKeySign>> {
+ public:
+  class PublicKeySignFactory : public PrimitiveFactory<PublicKeySign> {
+   public:
+    util::StatusOr<std::unique_ptr<PublicKeySign>> Create(
+        const RsaSsaPssPrivateKey& key) const override {
+      return {absl::make_unique<test::DummyPublicKeySign>("a public key sign")};
+    }
+  };
+
+  explicit FakeSignKeyManager()
+      : PrivateKeyTypeManager(absl::make_unique<PublicKeySignFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PRIVATE;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const RsaSsaPssPrivateKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateKeyFormat(
+      const RsaSsaPssKeyFormat& key_format) const override {
+    return util::OkStatus();
+  }
+
+  util::StatusOr<RsaSsaPssPrivateKey> CreateKey(
+      const RsaSsaPssKeyFormat& key_format) const override {
+    return RsaSsaPssPrivateKey();
+  }
+
+  util::StatusOr<RsaSsaPssPrivateKey> DeriveKey(
+      const RsaSsaPssKeyFormat& key_format,
+      InputStream* input_stream) const override {
+    return RsaSsaPssPrivateKey();
+  }
+
+  util::StatusOr<RsaSsaPssPublicKey> GetPublicKey(
+      const RsaSsaPssPrivateKey& private_key) const override {
+    return private_key.public_key();
+  }
+
+ private:
+  const std::string key_type_ = "some.sign.key.type";
+};
+
+class FakeVerifyKeyManager
+    : public KeyTypeManager<RsaSsaPssPublicKey, void, List<PublicKeyVerify>> {
+ public:
+  class PublicKeyVerifyFactory : public PrimitiveFactory<PublicKeyVerify> {
+   public:
+    util::StatusOr<std::unique_ptr<PublicKeyVerify>> Create(
+        const RsaSsaPssPublicKey& key) const override {
+      return {
+          absl::make_unique<test::DummyPublicKeyVerify>("a public key verify")};
+    }
+  };
+
+  explicit FakeVerifyKeyManager()
+      : KeyTypeManager(absl::make_unique<PublicKeyVerifyFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PUBLIC;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const RsaSsaPssPublicKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateParams(const RsaSsaPssParams& params) const {
+    return util::OkStatus();
+  }
+
+ private:
+  const std::string key_type_ = "some.verify.key.type";
+};
+
+TEST(ConfigurationImplTest, AddAsymmetricKeyManagers) {
+  Configuration config;
+  EXPECT_THAT(ConfigurationImpl::AddAsymmetricKeyManagers(
+                  absl::make_unique<FakeSignKeyManager>(),
+                  absl::make_unique<FakeVerifyKeyManager>(), config),
+              IsOk());
+}
+
+TEST(ConfigurationImplTest, GetKeyTypeInfoStoreAsymmetric) {
+  Configuration config;
+  ASSERT_THAT(ConfigurationImpl::AddAsymmetricKeyManagers(
+                  absl::make_unique<FakeSignKeyManager>(),
+                  absl::make_unique<FakeVerifyKeyManager>(), config),
+              IsOk());
+
+  {
+    std::string type_url = FakeSignKeyManager().get_key_type();
+    util::StatusOr<const KeyTypeInfoStore*> store =
+        ConfigurationImpl::GetKeyTypeInfoStore(config);
+    ASSERT_THAT(store, IsOk());
+    util::StatusOr<const KeyTypeInfoStore::Info*> info =
+        (*store)->Get(type_url);
+    ASSERT_THAT(info, IsOk());
+
+    util::StatusOr<const KeyManager<PublicKeySign>*> key_manager =
+        (*info)->get_key_manager<PublicKeySign>(type_url);
+    ASSERT_THAT(key_manager, IsOk());
+    EXPECT_EQ((*key_manager)->get_key_type(), type_url);
+  }
+  {
+    std::string type_url = FakeVerifyKeyManager().get_key_type();
+    util::StatusOr<const KeyTypeInfoStore*> store =
+        ConfigurationImpl::GetKeyTypeInfoStore(config);
+    ASSERT_THAT(store, IsOk());
+    util::StatusOr<const KeyTypeInfoStore::Info*> info =
+        (*store)->Get(type_url);
+    ASSERT_THAT(info, IsOk());
+
+    util::StatusOr<const KeyManager<PublicKeyVerify>*> key_manager =
+        (*info)->get_key_manager<PublicKeyVerify>(type_url);
+    ASSERT_THAT(key_manager, IsOk());
+    EXPECT_EQ((*key_manager)->get_key_type(), type_url);
+  }
+}
+
+TEST(ConfigurationImplTest, GlobalRegistryMode) {
+  Registry::Reset();
+  Configuration config;
+  ASSERT_THAT(ConfigurationImpl::SetGlobalRegistryMode(config), IsOk());
+  EXPECT_TRUE(ConfigurationImpl::GetGlobalRegistryMode(config));
+
+  // Check that ConfigurationImpl functions return kFailedPrecondition.
+  EXPECT_THAT(ConfigurationImpl::AddPrimitiveWrapper(
+                  absl::make_unique<FakePrimitiveWrapper>(), config),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_THAT(ConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_THAT(ConfigurationImpl::AddAsymmetricKeyManagers(
+                  absl::make_unique<FakeSignKeyManager>(),
+                  absl::make_unique<FakeVerifyKeyManager>(), config),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_THAT(ConfigurationImpl::GetKeyTypeInfoStore(config).status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_THAT(ConfigurationImpl::GetKeysetWrapperStore(config).status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+
+  Keyset keyset;
+  std::string raw_key = AddAesGcmKeyToKeyset(
+      keyset, /*key_id=*/13, OutputPrefixType::TINK, KeyStatusType::ENABLED);
+  keyset.set_primary_key_id(13);
+  std::unique_ptr<KeysetHandle> handle =
+      CleartextKeysetHandle::GetKeysetHandle(keyset);
+  // TODO(b/265705174): Replace with GetPrimitive(config) once implemented.
+  EXPECT_THAT(handle->GetPrimitive<FakePrimitive>().status(),
+              StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
+                  absl::make_unique<FakePrimitiveWrapper>()),
+              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<FakeKeyTypeManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+  // TODO(b/265705174): Replace with GetPrimitive(config) once implemented.
+  EXPECT_THAT(handle->GetPrimitive<FakePrimitive>(), IsOk());
+}
+
+TEST(ConfigurationImplTest, GlobalRegistryModeWithNonEmptyConfigFails) {
+  Configuration config;
+  ASSERT_THAT(ConfigurationImpl::AddPrimitiveWrapper(
+                  absl::make_unique<FakePrimitiveWrapper>(), config),
+              IsOk());
+  EXPECT_THAT(ConfigurationImpl::SetGlobalRegistryMode(config),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_FALSE(ConfigurationImpl::GetGlobalRegistryMode(config));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/ec_util.cc b/cc/internal/ec_util.cc
index 1dac7b0..f832227 100644
--- a/cc/internal/ec_util.cc
+++ b/cc/internal/ec_util.cc
@@ -21,14 +21,16 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
-#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "openssl/bn.h"
 #include "openssl/ec.h"
+#include "openssl/crypto.h"
 #include "openssl/ecdsa.h"
 #include "openssl/evp.h"
 #include "tink/internal/bn_util.h"
diff --git a/cc/internal/ec_util.h b/cc/internal/ec_util.h
index 155cd7b..3a47b36 100644
--- a/cc/internal/ec_util.h
+++ b/cc/internal/ec_util.h
@@ -16,8 +16,12 @@
 #ifndef TINK_INTERNAL_EC_UTIL_H_
 #define TINK_INTERNAL_EC_UTIL_H_
 
+#include <stdint.h>
+
+#include <memory>
 #include <string>
 
+#include "absl/strings/string_view.h"
 #include "openssl/ec.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/subtle/common_enums.h"
diff --git a/cc/internal/ec_util_test.cc b/cc/internal/ec_util_test.cc
index 4f4b1ba..5354e9a 100644
--- a/cc/internal/ec_util_test.cc
+++ b/cc/internal/ec_util_test.cc
@@ -15,20 +15,25 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/ec_util.h"
 
+#include <stdint.h>
+
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/status/status.h"
 #include "absl/strings/escaping.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
+#include "openssl/bn.h"
 #include "openssl/ec.h"
 #include "openssl/ecdsa.h"
 #include "openssl/evp.h"
+#include "include/rapidjson/document.h"
 #include "tink/internal/bn_util.h"
 #include "tink/internal/fips_utils.h"
 #include "tink/internal/ssl_unique_ptr.h"
@@ -37,6 +42,7 @@
 #include "tink/subtle/subtle_util.h"
 #include "tink/subtle/wycheproof_util.h"
 #include "tink/util/secret_data.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 
@@ -425,7 +431,7 @@
   EllipticCurveType curve;
 };
 
-const std::vector<EncodingTestVector> GetEncodingTestVectors() {
+std::vector<EncodingTestVector> GetEncodingTestVectors() {
   return {
       {EcPointFormat::UNCOMPRESSED,
        "00093057fb862f2ad2e82e581baeb3324e7b32946f2ba845a9beeed87d6995f54918ec6"
@@ -826,7 +832,6 @@
   others = ReadEcdhWycheproofTestVectors(
       /*file_name=*/"ecdh_secp521r1_test.json");
   test_vectors.insert(test_vectors.end(), others.begin(), others.end());
-// placeholder_disabled_subtle_test, please ignore
   others = ReadEcdhWycheproofTestVectors(
       /*file_name=*/"ecdh_test.json");
   test_vectors.insert(test_vectors.end(), others.begin(), others.end());
diff --git a/cc/internal/err_util.cc b/cc/internal/err_util.cc
index 3f5c0ad..06dc10d 100644
--- a/cc/internal/err_util.cc
+++ b/cc/internal/err_util.cc
@@ -15,6 +15,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/err_util.h"
 
+#include <stddef.h>
+
 #include <string>
 
 #include "openssl/err.h"
diff --git a/cc/internal/err_util_test.cc b/cc/internal/err_util_test.cc
index 6da867f..7343fd4 100644
--- a/cc/internal/err_util_test.cc
+++ b/cc/internal/err_util_test.cc
@@ -16,6 +16,7 @@
 #include "tink/internal/err_util.h"
 
 #include <string>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/internal/fips_utils.cc b/cc/internal/fips_utils.cc
index 2384a26..b08e7c7 100644
--- a/cc/internal/fips_utils.cc
+++ b/cc/internal/fips_utils.cc
@@ -18,8 +18,10 @@
 
 #include <atomic>
 
+#include "absl/base/attributes.h"
 #include "absl/status/status.h"
 #include "openssl/crypto.h"
+#include "tink/util/status.h"
 
 namespace crypto {
 namespace tink {
@@ -37,8 +39,17 @@
 
 void UnSetFipsRestricted() { is_fips_restricted = false; }
 
-crypto::tink::util::Status ChecksFipsCompatibility(
-    FipsCompatibility fips_status) {
+bool IsFipsModeEnabled() { return kUseOnlyFips || is_fips_restricted; }
+
+bool IsFipsEnabledInSsl() {
+#ifdef OPENSSL_IS_BORINGSSL
+  return FIPS_mode();
+#else
+  return false;
+#endif
+}
+
+util::Status ChecksFipsCompatibility(FipsCompatibility fips_status) {
   switch (fips_status) {
     case FipsCompatibility::kNotFips:
       if (IsFipsModeEnabled()) {
@@ -48,7 +59,7 @@
         return util::OkStatus();
       }
     case FipsCompatibility::kRequiresBoringCrypto:
-      if ((IsFipsModeEnabled()) && !FIPS_mode()) {
+      if ((IsFipsModeEnabled()) && !IsFipsEnabledInSsl()) {
         return util::Status(
             absl::StatusCode::kInternal,
             "BoringSSL not built with the BoringCrypto module. If you want to "
@@ -63,8 +74,6 @@
   }
 }
 
-bool IsFipsModeEnabled() { return kUseOnlyFips || is_fips_restricted; }
-
 }  // namespace internal
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/internal/fips_utils.h b/cc/internal/fips_utils.h
index a1867cd..165aa33 100644
--- a/cc/internal/fips_utils.h
+++ b/cc/internal/fips_utils.h
@@ -34,6 +34,9 @@
 // the FIPS restrictions have been enabled at runtime.
 bool IsFipsModeEnabled();
 
+// Returns true if the Ssl layer (BoringSSL or OpenSSL) has FIPS mode enabled.
+bool IsFipsEnabledInSsl();
+
 // Enable FIPS restrictions. If Tink has been built in FIPS mode this is
 // redundant.
 void SetFipsRestricted();
diff --git a/cc/internal/fips_utils_test.cc b/cc/internal/fips_utils_test.cc
index e16f635..c852c30 100644
--- a/cc/internal/fips_utils_test.cc
+++ b/cc/internal/fips_utils_test.cc
@@ -24,7 +24,7 @@
 
 namespace crypto {
 namespace tink {
-
+namespace internal {
 namespace {
 
 using ::crypto::tink::test::IsOk;
@@ -32,58 +32,52 @@
 
 class FipsIncompatible {
  public:
-  static constexpr crypto::tink::internal::FipsCompatibility kFipsStatus =
-      crypto::tink::internal::FipsCompatibility::kNotFips;
+  static constexpr FipsCompatibility kFipsStatus = FipsCompatibility::kNotFips;
 };
 
 class FipsCompatibleWithBoringCrypto {
  public:
-  static constexpr crypto::tink::internal::FipsCompatibility kFipsStatus =
-      crypto::tink::internal::FipsCompatibility::kRequiresBoringCrypto;
+  static constexpr FipsCompatibility kFipsStatus =
+      FipsCompatibility::kRequiresBoringCrypto;
 };
 
 TEST(FipsUtilsTest, CompatibilityInNonFipsMode) {
-  if (internal::kUseOnlyFips) {
+  if (kUseOnlyFips) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
-  EXPECT_THAT(internal::CheckFipsCompatibility<FipsIncompatible>(), IsOk());
-  EXPECT_THAT(
-      internal::CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(),
-      IsOk());
+  EXPECT_THAT(CheckFipsCompatibility<FipsIncompatible>(), IsOk());
+  EXPECT_THAT(CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(), IsOk());
 }
 
 TEST(FipsUtilsTest, CompatibilityInFipsMode) {
-  if (!internal::kUseOnlyFips || !FIPS_mode()) {
+  if (!kUseOnlyFips || !IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should only run in FIPS mode with Boringcrypto available.";
   }
 
-  EXPECT_THAT(internal::CheckFipsCompatibility<FipsIncompatible>(),
+  EXPECT_THAT(CheckFipsCompatibility<FipsIncompatible>(),
               StatusIs(absl::StatusCode::kInternal));
-  EXPECT_THAT(
-      internal::CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(),
-      IsOk());
+  EXPECT_THAT(CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(), IsOk());
 }
 
 TEST(TinkFipsTest, CompatibilityInFipsModeWithoutBoringCrypto) {
-  if (!internal::kUseOnlyFips || FIPS_mode()) {
+  if (!kUseOnlyFips || IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test only run if BoringCrypto module is not available.";
   }
 
   // In FIPS only mode compatibility checks should disallow algorithms
   // with the FipsCompatibility::kNone flag.
-  EXPECT_THAT(internal::CheckFipsCompatibility<FipsIncompatible>(),
+  EXPECT_THAT(CheckFipsCompatibility<FipsIncompatible>(),
               StatusIs(absl::StatusCode::kInternal));
 
   // FIPS validated implementations are not allowed if BoringCrypto is not
   // available.
-  EXPECT_THAT(
-      internal::CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(),
-      StatusIs(absl::StatusCode::kInternal));
+  EXPECT_THAT(CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(),
+              StatusIs(absl::StatusCode::kInternal));
 }
 
 }  // namespace
-
+}  // namespace internal
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/internal/key_gen_configuration_impl.h b/cc/internal/key_gen_configuration_impl.h
new file mode 100644
index 0000000..2d3d602
--- /dev/null
+++ b/cc/internal/key_gen_configuration_impl.h
@@ -0,0 +1,91 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_KEY_GEN_CONFIGURATION_IMPL_H_
+#define TINK_INTERNAL_KEY_GEN_CONFIGURATION_IMPL_H_
+
+#include "tink/internal/key_type_info_store.h"
+#include "tink/key_gen_configuration.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+constexpr absl::string_view kKeyGenConfigurationImplErr =
+    "Use crypto::tink::Registry instead when in global registry mode.";
+
+class KeyGenConfigurationImpl {
+ public:
+  template <class KM>
+  static crypto::tink::util::Status AddKeyTypeManager(
+      std::unique_ptr<KM> key_manager,
+      crypto::tink::KeyGenConfiguration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kKeyGenConfigurationImplErr);
+    }
+    return config.key_type_info_store_.AddKeyTypeManager(
+        std::move(key_manager), /*new_key_allowed=*/true);
+  }
+
+  template <class PrivateKM, class PublicKM>
+  static crypto::tink::util::Status AddAsymmetricKeyManagers(
+      std::unique_ptr<PrivateKM> private_key_manager,
+      std::unique_ptr<PublicKM> public_key_manager,
+      crypto::tink::KeyGenConfiguration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kKeyGenConfigurationImplErr);
+    }
+    return config.key_type_info_store_.AddAsymmetricKeyTypeManagers(
+        std::move(private_key_manager), std::move(public_key_manager),
+        /*new_key_allowed=*/true);
+  }
+
+  static crypto::tink::util::StatusOr<
+      const crypto::tink::internal::KeyTypeInfoStore*>
+  GetKeyTypeInfoStore(const crypto::tink::KeyGenConfiguration& config) {
+    if (config.global_registry_mode_) {
+      return crypto::tink::util::Status(absl::StatusCode::kFailedPrecondition,
+                                        kKeyGenConfigurationImplErr);
+    }
+    return &config.key_type_info_store_;
+  }
+
+  // `config` can be set to global registry mode only if empty.
+  static crypto::tink::util::Status SetGlobalRegistryMode(
+      crypto::tink::KeyGenConfiguration& config) {
+    if (!config.key_type_info_store_.IsEmpty()) {
+      return crypto::tink::util::Status(
+          absl::StatusCode::kFailedPrecondition,
+          "Using the global registry is only allowed when KeyGenConfiguration "
+          "is empty.");
+    }
+    config.global_registry_mode_ = true;
+    return crypto::tink::util::OkStatus();
+  }
+
+  static bool GetGlobalRegistryMode(
+      const crypto::tink::KeyGenConfiguration& config) {
+    return config.global_registry_mode_;
+  }
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_KEY_GEN_CONFIGURATION_IMPL_H_
diff --git a/cc/internal/key_gen_configuration_impl_test.cc b/cc/internal/key_gen_configuration_impl_test.cc
new file mode 100644
index 0000000..d9a9126
--- /dev/null
+++ b/cc/internal/key_gen_configuration_impl_test.cc
@@ -0,0 +1,312 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/key_gen_configuration_impl.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/key_gen_configuration.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/rsa_ssa_pss.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::RsaSsaPssKeyFormat;
+using ::google::crypto::tink::RsaSsaPssParams;
+using ::google::crypto::tink::RsaSsaPssPrivateKey;
+using ::google::crypto::tink::RsaSsaPssPublicKey;
+
+class FakePrimitive {
+ public:
+  explicit FakePrimitive(std::string s) : s_(s) {}
+  std::string get() { return s_; }
+
+ private:
+  std::string s_;
+};
+
+class FakeKeyTypeManager
+    : public KeyTypeManager<AesGcmKey, AesGcmKeyFormat, List<FakePrimitive>> {
+ public:
+  class FakePrimitiveFactory : public PrimitiveFactory<FakePrimitive> {
+   public:
+    util::StatusOr<std::unique_ptr<FakePrimitive>> Create(
+        const AesGcmKey& key) const override {
+      return absl::make_unique<FakePrimitive>(key.key_value());
+    }
+  };
+
+  FakeKeyTypeManager()
+      : KeyTypeManager(absl::make_unique<FakePrimitiveFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const AesGcmKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateKeyFormat(
+      const AesGcmKeyFormat& key_format) const override {
+    return util::OkStatus();
+  }
+
+  util::StatusOr<AesGcmKey> CreateKey(
+      const AesGcmKeyFormat& key_format) const override {
+    return AesGcmKey();
+  }
+
+  util::StatusOr<AesGcmKey> DeriveKey(
+      const AesGcmKeyFormat& key_format,
+      InputStream* input_stream) const override {
+    return AesGcmKey();
+  }
+
+ private:
+  const std::string key_type_ =
+      "type.googleapis.com/google.crypto.tink.AesGcmKey";
+};
+
+TEST(KeyGenConfigurationImplTest, AddKeyTypeManager) {
+  KeyGenConfiguration config;
+  EXPECT_THAT(KeyGenConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              IsOk());
+}
+
+TEST(KeyGenConfigurationImplTest, GetKeyTypeInfoStore) {
+  KeyGenConfiguration config;
+  ASSERT_THAT(KeyGenConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              IsOk());
+
+  std::string type_url = FakeKeyTypeManager().get_key_type();
+  util::StatusOr<const KeyTypeInfoStore*> store =
+      KeyGenConfigurationImpl::GetKeyTypeInfoStore(config);
+  ASSERT_THAT(store, IsOk());
+  util::StatusOr<const KeyTypeInfoStore::Info*> info = (*store)->Get(type_url);
+  ASSERT_THAT(info, IsOk());
+
+  util::StatusOr<const KeyManager<FakePrimitive>*> key_manager =
+      (*info)->get_key_manager<FakePrimitive>(type_url);
+  ASSERT_THAT(key_manager, IsOk());
+  EXPECT_EQ((*key_manager)->get_key_type(), type_url);
+}
+
+TEST(KeyGenConfigurationImplTest, GetKeyTypeInfoStoreMissingInfoFails) {
+  KeyGenConfiguration config;
+  util::StatusOr<const KeyTypeInfoStore*> store =
+      KeyGenConfigurationImpl::GetKeyTypeInfoStore(config);
+  ASSERT_THAT(store, IsOk());
+  EXPECT_THAT((*store)->Get("i.do.not.exist").status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+class FakeSignKeyManager
+    : public PrivateKeyTypeManager<RsaSsaPssPrivateKey, RsaSsaPssKeyFormat,
+                                   RsaSsaPssPublicKey, List<PublicKeySign>> {
+ public:
+  class PublicKeySignFactory : public PrimitiveFactory<PublicKeySign> {
+   public:
+    util::StatusOr<std::unique_ptr<PublicKeySign>> Create(
+        const RsaSsaPssPrivateKey& key) const override {
+      return {absl::make_unique<test::DummyPublicKeySign>("a public key sign")};
+    }
+  };
+
+  explicit FakeSignKeyManager()
+      : PrivateKeyTypeManager(absl::make_unique<PublicKeySignFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PRIVATE;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const RsaSsaPssPrivateKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateKeyFormat(
+      const RsaSsaPssKeyFormat& key_format) const override {
+    return util::OkStatus();
+  }
+
+  util::StatusOr<RsaSsaPssPrivateKey> CreateKey(
+      const RsaSsaPssKeyFormat& key_format) const override {
+    return RsaSsaPssPrivateKey();
+  }
+
+  util::StatusOr<RsaSsaPssPrivateKey> DeriveKey(
+      const RsaSsaPssKeyFormat& key_format,
+      InputStream* input_stream) const override {
+    return RsaSsaPssPrivateKey();
+  }
+
+  util::StatusOr<RsaSsaPssPublicKey> GetPublicKey(
+      const RsaSsaPssPrivateKey& private_key) const override {
+    return private_key.public_key();
+  }
+
+ private:
+  const std::string key_type_ = "some.sign.key.type";
+};
+
+class FakeVerifyKeyManager
+    : public KeyTypeManager<RsaSsaPssPublicKey, void, List<PublicKeyVerify>> {
+ public:
+  class PublicKeyVerifyFactory : public PrimitiveFactory<PublicKeyVerify> {
+   public:
+    util::StatusOr<std::unique_ptr<PublicKeyVerify>> Create(
+        const RsaSsaPssPublicKey& key) const override {
+      return {
+          absl::make_unique<test::DummyPublicKeyVerify>("a public key verify")};
+    }
+  };
+
+  explicit FakeVerifyKeyManager()
+      : KeyTypeManager(absl::make_unique<PublicKeyVerifyFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PUBLIC;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const RsaSsaPssPublicKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateParams(const RsaSsaPssParams& params) const {
+    return util::OkStatus();
+  }
+
+ private:
+  const std::string key_type_ = "some.verify.key.type";
+};
+
+TEST(KeyGenConfigurationImplTest, AddAsymmetricKeyManagers) {
+  KeyGenConfiguration config;
+  EXPECT_THAT(KeyGenConfigurationImpl::AddAsymmetricKeyManagers(
+                  absl::make_unique<FakeSignKeyManager>(),
+                  absl::make_unique<FakeVerifyKeyManager>(), config),
+              IsOk());
+}
+
+TEST(KeyGenConfigurationImplTest, GetKeyTypeInfoStoreAsymmetric) {
+  KeyGenConfiguration config;
+  ASSERT_THAT(KeyGenConfigurationImpl::AddAsymmetricKeyManagers(
+                  absl::make_unique<FakeSignKeyManager>(),
+                  absl::make_unique<FakeVerifyKeyManager>(), config),
+              IsOk());
+
+  {
+    std::string type_url = FakeSignKeyManager().get_key_type();
+    util::StatusOr<const KeyTypeInfoStore*> store =
+        KeyGenConfigurationImpl::GetKeyTypeInfoStore(config);
+    ASSERT_THAT(store, IsOk());
+    util::StatusOr<const KeyTypeInfoStore::Info*> info =
+        (*store)->Get(type_url);
+    ASSERT_THAT(info, IsOk());
+
+    util::StatusOr<const KeyManager<PublicKeySign>*> key_manager =
+        (*info)->get_key_manager<PublicKeySign>(type_url);
+    ASSERT_THAT(key_manager, IsOk());
+    EXPECT_EQ((*key_manager)->get_key_type(), type_url);
+  }
+  {
+    std::string type_url = FakeVerifyKeyManager().get_key_type();
+    util::StatusOr<const KeyTypeInfoStore*> store =
+        KeyGenConfigurationImpl::GetKeyTypeInfoStore(config);
+    ASSERT_THAT(store, IsOk());
+    util::StatusOr<const KeyTypeInfoStore::Info*> info =
+        (*store)->Get(type_url);
+    ASSERT_THAT(info, IsOk());
+
+    util::StatusOr<const KeyManager<PublicKeyVerify>*> key_manager =
+        (*info)->get_key_manager<PublicKeyVerify>(type_url);
+    ASSERT_THAT(key_manager, IsOk());
+    EXPECT_EQ((*key_manager)->get_key_type(), type_url);
+  }
+}
+
+TEST(KeyGenConfigurationImplTest, GlobalRegistryMode) {
+  Registry::Reset();
+  KeyGenConfiguration config;
+  ASSERT_THAT(KeyGenConfigurationImpl::SetGlobalRegistryMode(config), IsOk());
+  EXPECT_TRUE(KeyGenConfigurationImpl::GetGlobalRegistryMode(config));
+
+  // Check that KeyGenConfigurationImpl functions return kFailedPrecondition.
+  EXPECT_THAT(KeyGenConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_THAT(KeyGenConfigurationImpl::AddAsymmetricKeyManagers(
+                  absl::make_unique<FakeSignKeyManager>(),
+                  absl::make_unique<FakeVerifyKeyManager>(), config),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_THAT(KeyGenConfigurationImpl::GetKeyTypeInfoStore(config).status(),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+
+  // TODO(b/265705174): Replace with KeysetHandle::GenerateNew(config).
+  EXPECT_THAT(Registry::NewKeyData(AeadKeyTemplates::Aes256Gcm()).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<FakeKeyTypeManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+  // TODO(b/265705174): Replace with KeysetHandle::GenerateNew(config) once
+  // implemented.
+  EXPECT_THAT(Registry::NewKeyData(AeadKeyTemplates::Aes256Gcm()), IsOk());
+}
+
+TEST(KeyGenConfigurationImplTest, GlobalRegistryModeWithNonEmptyConfigFails) {
+  KeyGenConfiguration config;
+  ASSERT_THAT(KeyGenConfigurationImpl::AddKeyTypeManager(
+                  absl::make_unique<FakeKeyTypeManager>(), config),
+              IsOk());
+  EXPECT_THAT(KeyGenConfigurationImpl::SetGlobalRegistryMode(config),
+              StatusIs(absl::StatusCode::kFailedPrecondition));
+  EXPECT_FALSE(KeyGenConfigurationImpl::GetGlobalRegistryMode(config));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/key_info.cc b/cc/internal/key_info.cc
index c864741..04d0c13 100644
--- a/cc/internal/key_info.cc
+++ b/cc/internal/key_info.cc
@@ -16,6 +16,8 @@
 
 #include "tink/internal/key_info.h"
 
+#include "proto/tink.pb.h"
+
 namespace crypto {
 namespace tink {
 
diff --git a/cc/internal/key_parser.h b/cc/internal/key_parser.h
new file mode 100644
index 0000000..c734814
--- /dev/null
+++ b/cc/internal/key_parser.h
@@ -0,0 +1,123 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_KEY_PARSER_H_
+#define TINK_INTERNAL_KEY_PARSER_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <typeindex>
+#include <utility>
+
+#include "absl/functional/function_ref.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/internal/parser_index.h"
+#include "tink/internal/serialization.h"
+#include "tink/key.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Non-template base class that can be used with internal registry map.
+class KeyParser {
+ public:
+  // Parses a `serialization` into a key.
+  //
+  // This function is usually called on a `Serialization` subclass matching the
+  // value returned by `ObjectIdentifier()`. However, implementations should
+  // check that this is the case.
+  virtual util::StatusOr<std::unique_ptr<Key>> ParseKey(
+      const Serialization& serialization,
+      absl::optional<SecretKeyAccessToken> token) const = 0;
+
+  // Returns the object identifier for `SerializationT`, which is only valid
+  // for the lifetime of this object.
+  //
+  // The object identifier is a unique identifier per registry for this object
+  // (in the standard proto serialization, it is the type URL). In other words,
+  // when registering a `KeyParser`, the registry will invoke this to get
+  // the handled object identifier. In order to parse an object of
+  // `SerializationT`, the registry will then obtain the object identifier of
+  // this serialization object, and call the parser corresponding to this
+  // object.
+  virtual absl::string_view ObjectIdentifier() const = 0;
+
+  // Returns an index that can be used to look up the `KeyParser`
+  // object registered for the `KeyT` type in a registry.
+  virtual ParserIndex Index() const = 0;
+
+  virtual ~KeyParser() = default;
+};
+
+// Parses `SerializationT` objects into `KeyT` objects.
+template <typename SerializationT, typename KeyT>
+class KeyParserImpl : public KeyParser {
+ public:
+  // Creates a key parser with `object_identifier` and parsing `function`. The
+  // referenced `function` should outlive the created key parser object.
+  explicit KeyParserImpl(
+      absl::string_view object_identifier,
+      absl::FunctionRef<util::StatusOr<KeyT>(
+          SerializationT, absl::optional<SecretKeyAccessToken>)>
+          function)
+      : object_identifier_(object_identifier), function_(function) {}
+
+  util::StatusOr<std::unique_ptr<Key>> ParseKey(
+      const Serialization& serialization,
+      absl::optional<SecretKeyAccessToken> token) const override {
+    if (serialization.ObjectIdentifier() != object_identifier_) {
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Invalid object identifier for this key parser.");
+    }
+    const SerializationT* st =
+        dynamic_cast<const SerializationT*>(&serialization);
+    if (st == nullptr) {
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Invalid serialization type for this key parser.");
+    }
+    util::StatusOr<KeyT> key = function_(*st, token);
+    if (!key.ok()) return key.status();
+    return {absl::make_unique<KeyT>(std::move(*key))};
+  }
+
+  absl::string_view ObjectIdentifier() const override {
+    return object_identifier_;
+  }
+
+  ParserIndex Index() const override {
+    return ParserIndex::Create<SerializationT>(object_identifier_);
+  }
+
+ private:
+  std::string object_identifier_;
+  std::function<util::StatusOr<KeyT>(SerializationT,
+                                     absl::optional<SecretKeyAccessToken>)>
+      function_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_KEY_PARSER_H_
diff --git a/cc/internal/key_parser_test.cc b/cc/internal/key_parser_test.cc
new file mode 100644
index 0000000..c9dac8e
--- /dev/null
+++ b/cc/internal/key_parser_test.cc
@@ -0,0 +1,110 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/key_parser.h"
+
+#include <memory>
+#include <optional>
+#include <string_view>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/parser_index.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_test_util.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Eq;
+
+TEST(KeyParserTest, Create) {
+  std::unique_ptr<KeyParser> parser =
+      absl::make_unique<KeyParserImpl<NoIdSerialization, NoIdKey>>(
+          kNoIdTypeUrl, ParseNoIdKey);
+
+  EXPECT_THAT(parser->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+  EXPECT_THAT(
+      parser->Index(),
+      Eq(ParserIndex::Create<NoIdSerialization>(kNoIdTypeUrl)));
+}
+
+TEST(KeyParserTest, ParseKey) {
+  std::unique_ptr<KeyParser> parser =
+      absl::make_unique<KeyParserImpl<NoIdSerialization, NoIdKey>>(
+          kNoIdTypeUrl, ParseNoIdKey);
+
+  NoIdSerialization serialization;
+  util::StatusOr<std::unique_ptr<Key>> key =
+      parser->ParseKey(serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT((*key)->GetIdRequirement(), Eq(absl::nullopt));
+  EXPECT_THAT((*key)->GetParameters(), Eq(NoIdParams()));
+  EXPECT_THAT(**key, Eq(NoIdKey()));
+}
+
+TEST(KeyParserTest, ParsePublicKeyNoAccessToken) {
+  std::unique_ptr<KeyParser> parser =
+      absl::make_unique<KeyParserImpl<NoIdSerialization, NoIdKey>>(
+          kNoIdTypeUrl, ParseNoIdKey);
+
+  NoIdSerialization serialization;
+  util::StatusOr<std::unique_ptr<Key>> public_key =
+      parser->ParseKey(serialization, absl::nullopt);
+  ASSERT_THAT(public_key, IsOk());
+  EXPECT_THAT((*public_key)->GetIdRequirement(), Eq(absl::nullopt));
+  EXPECT_THAT((*public_key)->GetParameters(), Eq(NoIdParams()));
+  EXPECT_THAT(**public_key, Eq(NoIdKey()));
+}
+
+TEST(KeyParserTest, ParseKeyWithInvalidSerializationType) {
+  std::unique_ptr<KeyParser> parser =
+      absl::make_unique<KeyParserImpl<NoIdSerialization, NoIdKey>>(
+          "example_type_url", ParseNoIdKey);
+
+  IdKeySerialization serialization(/*id=*/123);
+  util::StatusOr<std::unique_ptr<Key>> key =
+      parser->ParseKey(serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(KeyParserTest, ParseKeyWithInvalidObjectIdentifier) {
+  std::unique_ptr<KeyParser> parser =
+      absl::make_unique<KeyParserImpl<NoIdSerialization, NoIdKey>>(
+          "mismatched_type_url", ParseNoIdKey);
+
+  NoIdSerialization serialization;
+  util::StatusOr<std::unique_ptr<Key>> key =
+      parser->ParseKey(serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/key_serializer.h b/cc/internal/key_serializer.h
new file mode 100644
index 0000000..1d879a6
--- /dev/null
+++ b/cc/internal/key_serializer.h
@@ -0,0 +1,92 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_KEY_SERIALIZER_H_
+#define TINK_INTERNAL_KEY_SERIALIZER_H_
+
+#include <functional>
+#include <memory>
+#include <typeindex>
+#include <utility>
+
+#include "absl/functional/function_ref.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serializer_index.h"
+#include "tink/key.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Non-template base class that can be used with internal registry map.
+class KeySerializer {
+ public:
+  // Returns the serialization of `key`.
+  virtual util::StatusOr<std::unique_ptr<Serialization>> SerializeKey(
+      const Key& key, absl::optional<SecretKeyAccessToken> token) const = 0;
+
+  // Returns an index that can be used to look up the `KeySerializer`
+  // object registered for the `KeyT` type in a registry.
+  virtual SerializerIndex Index() const = 0;
+
+  virtual ~KeySerializer() = default;
+};
+
+// Serializes `KeyT` objects into `SerializationT` objects.
+template <typename KeyT, typename SerializationT>
+class KeySerializerImpl : public KeySerializer {
+ public:
+  // Creates a key serializer with serialization `function`. The referenced
+  // `function` should outlive the created key serializer object.
+  explicit KeySerializerImpl(absl::FunctionRef<util::StatusOr<SerializationT>(
+                                 KeyT, absl::optional<SecretKeyAccessToken>)>
+                                 function)
+      : function_(function) {}
+
+  util::StatusOr<std::unique_ptr<Serialization>> SerializeKey(
+      const Key& key,
+      absl::optional<SecretKeyAccessToken> token) const override {
+    const KeyT* kt = dynamic_cast<const KeyT*>(&key);
+    if (kt == nullptr) {
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Invalid key type for this key serializer.");
+    }
+    util::StatusOr<SerializationT> serialization = function_(*kt, token);
+    if (!serialization.ok()) return serialization.status();
+    return {absl::make_unique<SerializationT>(std::move(*serialization))};
+  }
+
+  SerializerIndex Index() const override {
+    return SerializerIndex::Create<KeyT, SerializationT>();
+  }
+
+ private:
+  std::function<util::StatusOr<SerializationT>(
+      KeyT, absl::optional<SecretKeyAccessToken>)>
+      function_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_KEY_SERIALIZER_H_
diff --git a/cc/internal/key_serializer_test.cc b/cc/internal/key_serializer_test.cc
new file mode 100644
index 0000000..e67257e
--- /dev/null
+++ b/cc/internal/key_serializer_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/key_serializer.h"
+
+#include <memory>
+#include <string_view>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_test_util.h"
+#include "tink/internal/serializer_index.h"
+#include "tink/key.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Eq;
+
+TEST(KeySerializerTest, Create) {
+  std::unique_ptr<KeySerializer> serializer =
+      absl::make_unique<KeySerializerImpl<NoIdKey, NoIdSerialization>>(
+          SerializeNoIdKey);
+
+  EXPECT_THAT(serializer->Index(),
+              Eq(SerializerIndex::Create<NoIdKey, NoIdSerialization>()));
+}
+
+TEST(KeySerializerTest, SerializeKey) {
+  std::unique_ptr<KeySerializer> serializer =
+      absl::make_unique<KeySerializerImpl<NoIdKey, NoIdSerialization>>(
+          SerializeNoIdKey);
+
+  NoIdKey key;
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      serializer->SerializeKey(key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+TEST(KeySerializerTest, SerializePublicKeyNoAccessToken) {
+  std::unique_ptr<KeySerializer> serializer =
+      absl::make_unique<KeySerializerImpl<NoIdKey, NoIdSerialization>>(
+          SerializeNoIdKey);
+
+  NoIdKey public_key;
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      serializer->SerializeKey(public_key, absl::nullopt);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+TEST(KeySerializerTest, SerializeKeyWithInvalidKeyType) {
+  std::unique_ptr<KeySerializer> serializer =
+      absl::make_unique<KeySerializerImpl<NoIdKey, NoIdSerialization>>(
+          SerializeNoIdKey);
+
+  IdKey key(/*id=*/123);
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      serializer->SerializeKey(key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/key_status_util.cc b/cc/internal/key_status_util.cc
new file mode 100644
index 0000000..a28f418
--- /dev/null
+++ b/cc/internal/key_status_util.cc
@@ -0,0 +1,76 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/key_status_util.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "tink/key_status.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::google::crypto::tink::KeyStatusType;
+
+util::StatusOr<KeyStatus> FromKeyStatusType(KeyStatusType status_type) {
+  switch (status_type) {
+    case KeyStatusType::ENABLED:
+      return KeyStatus::kEnabled;
+    case KeyStatusType::DISABLED:
+      return KeyStatus::kDisabled;
+    case KeyStatusType::DESTROYED:
+      return KeyStatus::kDestroyed;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Invalid key status type.");
+  }
+}
+
+util::StatusOr<KeyStatusType> ToKeyStatusType(KeyStatus status) {
+  switch (status) {
+    case KeyStatus::kEnabled:
+      return KeyStatusType::ENABLED;
+    case KeyStatus::kDisabled:
+      return KeyStatusType::DISABLED;
+    case KeyStatus::kDestroyed:
+      return KeyStatusType::DESTROYED;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Invalid key status.");
+  }
+}
+
+std::string ToKeyStatusName(KeyStatus status) {
+  switch (status) {
+    case KeyStatus::kEnabled:
+    return KeyStatusType_Name(KeyStatusType::ENABLED);
+    case KeyStatus::kDisabled:
+    return KeyStatusType_Name(KeyStatusType::DISABLED);
+    case KeyStatus::kDestroyed:
+      return KeyStatusType_Name(KeyStatusType::DESTROYED);
+    default:
+      return KeyStatusType_Name(KeyStatusType::UNKNOWN_STATUS);
+  }
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/key_status_util.h b/cc/internal/key_status_util.h
new file mode 100644
index 0000000..f9736cf
--- /dev/null
+++ b/cc/internal/key_status_util.h
@@ -0,0 +1,48 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_KEY_STATUS_UTIL_H_
+#define TINK_INTERNAL_KEY_STATUS_UTIL_H_
+
+#include <string>
+
+#include "tink/key_status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Returns `KeyStatus` C++ enum for a given `KeyStatusType` proto enum. If
+// `status_type` is unrecognized (i.e., not handled), then an error is returned.
+util::StatusOr<KeyStatus> FromKeyStatusType(
+    google::crypto::tink::KeyStatusType status_type);
+
+// Returns `KeyStatusType` proto enum for a given `KeyStatus` C++ enum. If
+// `status` is unrecognized (i.e., not handled), then an error is returned.
+util::StatusOr<google::crypto::tink::KeyStatusType> ToKeyStatusType(
+    KeyStatus status);
+
+// Returns a canonical name for a `KeyStatus` based on the corresponding
+// `KeyStatusType` proto enum.
+std::string ToKeyStatusName(KeyStatus status);
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_KEY_STATUS_UTIL_H_
diff --git a/cc/internal/key_status_util_test.cc b/cc/internal/key_status_util_test.cc
new file mode 100644
index 0000000..6ac0933
--- /dev/null
+++ b/cc/internal/key_status_util_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/key_status_util.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/key_status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyStatusType;
+
+TEST(KeyStatusUtilTest, FromKeyStatusType) {
+  util::StatusOr<KeyStatus> enabled = FromKeyStatusType(KeyStatusType::ENABLED);
+  EXPECT_THAT(enabled, IsOkAndHolds(KeyStatus::kEnabled));
+
+  util::StatusOr<KeyStatus> disabled =
+      FromKeyStatusType(KeyStatusType::DISABLED);
+  EXPECT_THAT(disabled, IsOkAndHolds(KeyStatus::kDisabled));
+
+  util::StatusOr<KeyStatus> destroyed =
+      FromKeyStatusType(KeyStatusType::DESTROYED);
+  EXPECT_THAT(destroyed, IsOkAndHolds(KeyStatus::kDestroyed));
+
+  util::StatusOr<KeyStatus> unknown =
+      FromKeyStatusType(KeyStatusType::UNKNOWN_STATUS);
+  EXPECT_THAT(unknown.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(KeyStatusUtilTest, ToKeyStatusType) {
+  util::StatusOr<KeyStatusType> enabled = ToKeyStatusType(KeyStatus::kEnabled);
+  EXPECT_THAT(enabled, IsOkAndHolds(KeyStatusType::ENABLED));
+
+  util::StatusOr<KeyStatusType> disabled =
+      ToKeyStatusType(KeyStatus::kDisabled);
+  EXPECT_THAT(disabled, IsOkAndHolds(KeyStatusType::DISABLED));
+
+  util::StatusOr<KeyStatusType> destroyed =
+      ToKeyStatusType(KeyStatus::kDestroyed);
+  EXPECT_THAT(destroyed, IsOkAndHolds(KeyStatusType::DESTROYED));
+
+  util::StatusOr<KeyStatusType> unknown = ToKeyStatusType(
+      KeyStatus::kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements);
+  EXPECT_THAT(unknown.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(KeyStatusUtilTest, ToKeyStatusName) {
+  EXPECT_EQ(ToKeyStatusName(KeyStatus::kEnabled), "ENABLED");
+  EXPECT_EQ(ToKeyStatusName(KeyStatus::kDisabled), "DISABLED");
+  EXPECT_EQ(ToKeyStatusName(KeyStatus::kDestroyed), "DESTROYED");
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/key_type_info_store.cc b/cc/internal/key_type_info_store.cc
new file mode 100644
index 0000000..5d041ff
--- /dev/null
+++ b/cc/internal/key_type_info_store.cc
@@ -0,0 +1,64 @@
+// Copyright 2018 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/key_type_info_store.h"
+
+#include <memory>
+#include <typeindex>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+util::StatusOr<KeyTypeInfoStore::Info*> KeyTypeInfoStore::Get(
+    absl::string_view type_url) const {
+  auto it = type_url_to_info_.find(type_url);
+  if (it == type_url_to_info_.end()) {
+    return ToStatusF(absl::StatusCode::kNotFound,
+                     "No manager for type '%s' has been registered.", type_url);
+  }
+  return it->second.get();
+}
+
+util::Status KeyTypeInfoStore::IsInsertable(
+    absl::string_view type_url, const std::type_index& key_manager_type_index,
+    bool new_key_allowed) const {
+  auto it = type_url_to_info_.find(type_url);
+  if (it == type_url_to_info_.end()) {
+    return crypto::tink::util::OkStatus();
+  }
+  if (it->second->key_manager_type_index() != key_manager_type_index) {
+    return ToStatusF(absl::StatusCode::kAlreadyExists,
+                     "A manager for type '%s' has been already registered.",
+                     type_url);
+  }
+  if (!it->second->new_key_allowed() && new_key_allowed) {
+    return ToStatusF(absl::StatusCode::kAlreadyExists,
+                     "A manager for type '%s' has been already registered "
+                     "with forbidden new key operation.",
+                     type_url);
+  }
+  return util::OkStatus();
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/key_type_info_store.h b/cc/internal/key_type_info_store.h
new file mode 100644
index 0000000..603fb00
--- /dev/null
+++ b/cc/internal/key_type_info_store.h
@@ -0,0 +1,425 @@
+// Copyright 2018 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_KEY_TYPE_INFO_STORE_H_
+#define TINK_INTERNAL_KEY_TYPE_INFO_STORE_H_
+
+#include <atomic>
+#include <functional>
+#include <initializer_list>
+#include <memory>
+#include <string>
+#include <typeindex>
+#include <utility>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/strings/str_join.h"
+#include "tink/core/key_manager_impl.h"
+#include "tink/core/key_type_manager.h"
+#include "tink/core/private_key_manager_impl.h"
+#include "tink/core/private_key_type_manager.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/key_manager.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Stores information about key types constructed from their KeyTypeManager or
+// KeyManager. This is used by the Configuration and Registry classes.
+//
+// Once inserted, Info objects must remain valid for the lifetime of the
+// KeyTypeInfoStore object, and the Info object's pointer stability is required.
+// Elements in Info, which include the KeyTypeManager or KeyManager, must not
+// be replaced.
+//
+// Example:
+//  KeyTypeInfoStore store;
+//  crypto::tink::util::Status status =
+//      store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(), true);
+//  crypto::tink::util::StatusOr<KeyTypeInfoStore::Info*> info =
+//      store.Get(AesGcmKeyManager().get_key_type());
+class KeyTypeInfoStore {
+ public:
+  KeyTypeInfoStore() = default;
+
+  // Movable, but not copyable.
+  KeyTypeInfoStore(KeyTypeInfoStore&& other) = default;
+  KeyTypeInfoStore& operator=(KeyTypeInfoStore&& other) = default;
+
+  // Information about a key type constructed from its KeyTypeManager or
+  // KeyManager.
+  class Info {
+   public:
+    // Takes ownership of `manager`.
+    template <typename KeyProto, typename KeyFormatProto,
+              typename... Primitives>
+    Info(KeyTypeManager<KeyProto, KeyFormatProto, List<Primitives...>>* manager,
+         bool new_key_allowed)
+        : key_manager_type_index_(std::type_index(typeid(*manager))),
+          public_key_type_manager_type_index_(absl::nullopt),
+          new_key_allowed_(new_key_allowed),
+          key_type_manager_(absl::WrapUnique(manager)),
+          internal_key_factory_(
+              absl::make_unique<internal::KeyFactoryImpl<KeyTypeManager<
+                  KeyProto, KeyFormatProto, List<Primitives...>>>>(manager)),
+          key_factory_(internal_key_factory_.get()),
+          key_deriver_(CreateDeriverFunctionFor(manager)) {
+      // TODO(C++17): Replace with a fold expression.
+      (void)std::initializer_list<int>{
+          0, (primitive_to_manager_.emplace(
+                  std::type_index(typeid(Primitives)),
+                  internal::MakeKeyManager<Primitives>(manager)),
+              0)...};
+    }
+
+    // Takes ownership of `private_manager`, but not of `public_manager`, which
+    // must only be alive for the duration of the constructor.
+    template <typename PrivateKeyProto, typename KeyFormatProto,
+              typename PublicKeyProto, typename PublicPrimitivesList,
+              typename... PrivatePrimitives>
+    Info(PrivateKeyTypeManager<PrivateKeyProto, KeyFormatProto, PublicKeyProto,
+                               List<PrivatePrimitives...>>* private_manager,
+         KeyTypeManager<PublicKeyProto, void, PublicPrimitivesList>*
+             public_manager,
+         bool new_key_allowed)
+        : key_manager_type_index_(std::type_index(typeid(*private_manager))),
+          public_key_type_manager_type_index_(
+              std::type_index(typeid(*public_manager))),
+          new_key_allowed_(new_key_allowed),
+          key_type_manager_(absl::WrapUnique(private_manager)),
+          internal_key_factory_(
+              absl::make_unique<internal::PrivateKeyFactoryImpl<
+                  PrivateKeyProto, KeyFormatProto, PublicKeyProto,
+                  List<PrivatePrimitives...>, PublicPrimitivesList>>(
+                  private_manager, public_manager)),
+          key_factory_(internal_key_factory_.get()),
+          key_deriver_(CreateDeriverFunctionFor(private_manager)) {
+      // TODO(C++17): Replace with a fold expression.
+      (void)std::initializer_list<int>{
+          0, (primitive_to_manager_.emplace(
+                  std::type_index(typeid(PrivatePrimitives)),
+                  internal::MakePrivateKeyManager<PrivatePrimitives>(
+                      private_manager, public_manager)),
+              0)...};
+    }
+
+    // Takes ownership of `manager`. KeyManager is the legacy/internal version
+    // of KeyTypeManager.
+    template <typename P>
+    Info(KeyManager<P>* manager, bool new_key_allowed)
+        : key_manager_type_index_(std::type_index(typeid(*manager))),
+          public_key_type_manager_type_index_(absl::nullopt),
+          new_key_allowed_(new_key_allowed),
+          key_type_manager_(nullptr),
+          internal_key_factory_(nullptr),
+          key_factory_(&manager->get_key_factory()) {
+      primitive_to_manager_.emplace(std::type_index(typeid(P)),
+                                    absl::WrapUnique(manager));
+    }
+
+    template <typename P>
+    crypto::tink::util::StatusOr<const KeyManager<P>*> get_key_manager(
+        absl::string_view requested_type_url) const {
+      auto it = primitive_to_manager_.find(std::type_index(typeid(P)));
+      if (it == primitive_to_manager_.end()) {
+        return crypto::tink::util::Status(
+            absl::StatusCode::kInvalidArgument,
+            absl::StrCat(
+                "Primitive type ", typeid(P).name(),
+                " not among supported primitives ",
+                absl::StrJoin(
+                    primitive_to_manager_.begin(), primitive_to_manager_.end(),
+                    ", ",
+                    [](std::string* out,
+                       const std::pair<const std::type_index,
+                                       std::unique_ptr<KeyManagerBase>>& kv) {
+                      absl::StrAppend(out, kv.first.name());
+                    }),
+                " for type URL ", requested_type_url));
+      }
+      return static_cast<const KeyManager<P>*>(it->second.get());
+    }
+
+    const std::type_index& key_manager_type_index() const {
+      return key_manager_type_index_;
+    }
+
+    const absl::optional<std::type_index>& public_key_type_manager_type_index()
+        const {
+      return public_key_type_manager_type_index_;
+    }
+
+    bool new_key_allowed() const { return new_key_allowed_.load(); }
+
+    void set_new_key_allowed(bool b) { new_key_allowed_.store(b); }
+
+    const KeyFactory& key_factory() const { return *key_factory_; }
+
+    const std::function<crypto::tink::util::StatusOr<
+        google::crypto::tink::KeyData>(absl::string_view, InputStream*)>&
+    key_deriver() const {
+      return key_deriver_;
+    }
+
+   private:
+    // Dynamic type_index of the KeyManager or KeyTypeManager for this key type.
+    std::type_index key_manager_type_index_;
+    // Dynamic type_index of the public KeyTypeManager for this key type when
+    // inserted into the registry via RegisterAsymmetricKeyManagers. Otherwise,
+    // nullopt.
+    absl::optional<std::type_index> public_key_type_manager_type_index_;
+    // Whether the key manager allows the creation of new keys.
+    std::atomic<bool> new_key_allowed_;
+
+    // Map from primitive type_index to KeyManager.
+    absl::flat_hash_map<std::type_index, std::unique_ptr<KeyManagerBase>>
+        primitive_to_manager_;
+    // Key type manager. Equals nullptr if Info was constructed from a
+    // KeyManager.
+    const std::shared_ptr<void> key_type_manager_;
+
+    // Key factory. Equals nullptr if Info was constructed from a KeyManager.
+    std::unique_ptr<const KeyFactory> internal_key_factory_;
+    // Unowned version of `internal_key_factory_` if Info was constructed from a
+    // KeyTypeManager. Key factory belonging to the KeyManager if Info was
+    // constructed from a KeyManager.
+    const KeyFactory* key_factory_;
+
+    // Derives a key if Info was constructed from a KeyTypeManager with a
+    // non-void KeyFormat type. Else, this function is empty and casting to a
+    // bool returns false.
+    std::function<crypto::tink::util::StatusOr<google::crypto::tink::KeyData>(
+        absl::string_view, InputStream*)>
+        key_deriver_;
+  };
+
+  // Adds a crypto::tink::KeyTypeManager to KeyTypeInfoStore. `new_key_allowed`
+  // indicates whether `manager` may create new keys.
+  template <class KeyTypeManager>
+  crypto::tink::util::Status AddKeyTypeManager(
+      std::unique_ptr<KeyTypeManager> manager, bool new_key_allowed);
+
+  // Adds a pair of crypto::tink::PrivateKeyTypeManager and
+  // crypto::tink::KeyTypeManager to KeyTypeInfoStore. `new_key_allowed`
+  // indicates whether `private_manager` may create new keys.
+  template <class PrivateKeyTypeManager, class PublicKeyTypeManager>
+  crypto::tink::util::Status AddAsymmetricKeyTypeManagers(
+      std::unique_ptr<PrivateKeyTypeManager> private_manager,
+      std::unique_ptr<PublicKeyTypeManager> public_manager,
+      bool new_key_allowed);
+
+  // Adds a crypto::tink::KeyManager to KeyTypeInfoStore. `new_key_allowed`
+  // indicates whether `manager` may create new keys. KeyManager is the
+  // legacy/internal version of KeyTypeManager.
+  template <class P>
+  crypto::tink::util::Status AddKeyManager(
+      std::unique_ptr<KeyManager<P>> manager, bool new_key_allowed);
+
+  // Gets Info associated with `type_url`, returning either a valid, non-null
+  // Info or an error.
+  crypto::tink::util::StatusOr<Info*> Get(absl::string_view type_url) const;
+
+  bool IsEmpty() const { return type_url_to_info_.empty(); }
+
+ private:
+  // Whether a key manager with `type_url` and `key_manager_type_index` can be
+  // inserted.
+  crypto::tink::util::Status IsInsertable(
+      absl::string_view type_url, const std::type_index& key_manager_type_index,
+      bool new_key_allowed) const;
+
+  void Add(std::string type_url, std::unique_ptr<Info> info,
+           bool new_key_allowed) {
+    auto it = type_url_to_info_.find(type_url);
+    if (it != type_url_to_info_.end()) {
+      it->second->set_new_key_allowed(new_key_allowed);
+    } else {
+      type_url_to_info_.insert({type_url, std::move(info)});
+    }
+  }
+
+  // Map from the type_url to Info.
+  // Elements in Info must not be replaced, and pointer stability is required
+  // for `Get()`.
+  absl::flat_hash_map<std::string, std::unique_ptr<Info>> type_url_to_info_;
+};
+
+template <class P>
+crypto::tink::util::Status KeyTypeInfoStore::AddKeyManager(
+    std::unique_ptr<KeyManager<P>> manager, bool new_key_allowed) {
+  std::string type_url = manager->get_key_type();
+  if (!manager->DoesSupport(type_url)) {
+    return ToStatusF(absl::StatusCode::kInvalidArgument,
+                     "The manager does not support type '%s'.", type_url);
+  }
+
+  crypto::tink::util::Status status = IsInsertable(
+      type_url, std::type_index(typeid(*manager)), new_key_allowed);
+  if (!status.ok()) {
+    return status;
+  }
+
+  auto info = absl::make_unique<Info>(manager.release(), new_key_allowed);
+  Add(type_url, std::move(info), new_key_allowed);
+  return crypto::tink::util::OkStatus();
+}
+
+template <class KeyTypeManager>
+crypto::tink::util::Status KeyTypeInfoStore::AddKeyTypeManager(
+    std::unique_ptr<KeyTypeManager> manager, bool new_key_allowed) {
+  // Check FIPS status.
+  internal::FipsCompatibility fips_compatible = manager->FipsStatus();
+  auto fips_status = internal::ChecksFipsCompatibility(fips_compatible);
+  if (!fips_status.ok()) {
+    return crypto::tink::util::Status(
+        absl::StatusCode::kInternal,
+        absl::StrCat("Failed registering the key manager for ",
+                     typeid(*manager).name(),
+                     " as it is not FIPS compatible: ", fips_status.message()));
+  }
+
+  std::string type_url = manager->get_key_type();
+  crypto::tink::util::Status status = IsInsertable(
+      type_url, std::type_index(typeid(*manager)), new_key_allowed);
+  if (!status.ok()) {
+    return status;
+  }
+
+  auto info = absl::make_unique<Info>(manager.release(), new_key_allowed);
+  Add(type_url, std::move(info), new_key_allowed);
+  return crypto::tink::util::OkStatus();
+}
+
+template <class PrivateKeyTypeManager, class PublicKeyTypeManager>
+crypto::tink::util::Status KeyTypeInfoStore::AddAsymmetricKeyTypeManagers(
+    std::unique_ptr<PrivateKeyTypeManager> private_manager,
+    std::unique_ptr<PublicKeyTypeManager> public_manager,
+    bool new_key_allowed) {
+  std::string private_type_url = private_manager->get_key_type();
+  std::string public_type_url = public_manager->get_key_type();
+  if (private_type_url == public_type_url) {
+    return crypto::tink::util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Passed in key managers must have different get_key_type() results.");
+  }
+
+  // Check FIPS status.
+  auto private_fips_status =
+      internal::ChecksFipsCompatibility(private_manager->FipsStatus());
+  if (!private_fips_status.ok()) {
+    return crypto::tink::util::Status(
+        absl::StatusCode::kInternal,
+        absl::StrCat(
+            "Failed registering the key manager for ",
+            typeid(*private_manager).name(),
+            " as it is not FIPS compatible: ", private_fips_status.message()));
+  }
+  auto public_fips_status =
+      internal::ChecksFipsCompatibility(public_manager->FipsStatus());
+  if (!public_fips_status.ok()) {
+    return crypto::tink::util::Status(
+        absl::StatusCode::kInternal,
+        absl::StrCat(
+            "Failed registering the key manager for ",
+            typeid(*public_manager).name(),
+            " as it is not FIPS compatible: ", public_fips_status.message()));
+  }
+
+  crypto::tink::util::Status private_status =
+      IsInsertable(private_type_url, std::type_index(typeid(*private_manager)),
+                   new_key_allowed);
+  if (!private_status.ok()) {
+    return private_status;
+  }
+  crypto::tink::util::Status public_status =
+      IsInsertable(public_type_url, std::type_index(typeid(*public_manager)),
+                   new_key_allowed);
+  if (!public_status.ok()) {
+    return public_status;
+  }
+
+  util::StatusOr<KeyTypeInfoStore::Info*> private_found = Get(private_type_url);
+  util::StatusOr<const KeyTypeInfoStore::Info*> public_found =
+      Get(public_type_url);
+
+  // Only one of the private and public key type managers is found.
+  if (private_found.ok() && !public_found.ok()) {
+    return crypto::tink::util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat(
+            "Private key manager corresponding to ",
+            typeid(*private_manager).name(),
+            " was previously registered, but key manager corresponding to ",
+            typeid(*public_manager).name(),
+            " was not, so it's impossible to register them jointly"));
+  }
+  if (!private_found.ok() && public_found.ok()) {
+    return crypto::tink::util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Key manager corresponding to ",
+                     typeid(*public_manager).name(),
+                     " was previously registered, but private key manager "
+                     "corresponding to ",
+                     typeid(*private_manager).name(),
+                     " was not, so it's impossible to register them jointly"));
+  }
+
+  // Both private and public key type managers are found.
+  if (private_found.ok() && public_found.ok()) {
+    if (!(*private_found)->public_key_type_manager_type_index().has_value()) {
+      return crypto::tink::util::Status(
+          absl::StatusCode::kInvalidArgument,
+          absl::StrCat("private key manager corresponding to ",
+                       typeid(*private_manager).name(),
+                       " is already registered without public key manager, "
+                       "cannot be re-registered with public key manager. "));
+    }
+    if ((*private_found)->public_key_type_manager_type_index() !=
+        std::type_index(typeid(*public_manager))) {
+      return crypto::tink::util::Status(
+          absl::StatusCode::kInvalidArgument,
+          absl::StrCat(
+              "private key manager corresponding to ",
+              typeid(*private_manager).name(), " is already registered with ",
+              (*private_found)->public_key_type_manager_type_index()->name(),
+              ", cannot be re-registered with ",
+              typeid(*public_manager).name()));
+    }
+    // Since `private_manager` passed the `IsInsertable` check above, the
+    // `set_new_key_allowed` operation is permissible.
+    (*private_found)->set_new_key_allowed(new_key_allowed);
+    return crypto::tink::util::OkStatus();
+  }
+
+  // Both private and public key type managers were not found.
+  auto private_info = absl::make_unique<Info>(
+      private_manager.release(), public_manager.get(), new_key_allowed);
+  Add(private_type_url, std::move(private_info), new_key_allowed);
+  // TODO(b/265705174): Store public key type managers in an asymmetric pair
+  // with new_key_allowed = false.
+  auto public_info =
+      absl::make_unique<Info>(public_manager.release(), new_key_allowed);
+  Add(public_type_url, std::move(public_info), new_key_allowed);
+
+  return crypto::tink::util::OkStatus();
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_KEY_TYPE_INFO_STORE_H_
diff --git a/cc/internal/key_type_info_store_test.cc b/cc/internal/key_type_info_store_test.cc
new file mode 100644
index 0000000..3768ac9
--- /dev/null
+++ b/cc/internal/key_type_info_store_test.cc
@@ -0,0 +1,443 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/key_type_info_store.h"
+
+#include <memory>
+#include <string>
+#include <typeindex>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/aead.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/aead/cord_aead.h"
+#include "tink/aead/kms_envelope_aead_key_manager.h"
+#include "tink/core/key_manager_impl.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/key_manager.h"
+#include "tink/signature/ecdsa_sign_key_manager.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/util/test_matchers.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/common.pb.h"
+#include "proto/ecdsa.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::EcdsaKeyFormat;
+using ::google::crypto::tink::EcdsaParams;
+using ::google::crypto::tink::EcdsaSignatureEncoding;
+using ::google::crypto::tink::EllipticCurveType;
+using ::google::crypto::tink::HashType;
+
+// TODO(b/265705174): Use fake key managers to avoid relying on key manager
+// implementations.
+TEST(KeyTypeInfoStoreTest, AddKeyTypeManager) {
+  KeyTypeInfoStore store;
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/true),
+              IsOk());
+
+  std::string type_url = AesGcmKeyManager().get_key_type();
+  util::StatusOr<KeyTypeInfoStore::Info*> info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), true);
+
+  util::StatusOr<const KeyManager<Aead>*> manager =
+      (*info)->get_key_manager<Aead>(type_url);
+  ASSERT_THAT(manager, IsOk());
+  EXPECT_EQ((*manager)->get_key_type(), type_url);
+}
+
+TEST(KeyTypeInfoStoreTest, AddKeyTypeManagerNoBoringCrypto) {
+  if (!kUseOnlyFips || IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Only supported in FIPS-mode with BoringCrypto not available.";
+  }
+  KeyTypeInfoStore store;
+  EXPECT_THAT(
+      store.AddKeyTypeManager(absl::make_unique<KmsEnvelopeAeadKeyManager>(),
+                              /*new_key_allowed=*/true),
+      StatusIs(absl::StatusCode::kInternal));
+}
+
+TEST(KeyTypeInfoStoreTest, AddKeyTypeManagerAndChangeNewKeyAllowed) {
+  KeyTypeInfoStore store;
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/true),
+              IsOk());
+
+  std::string type_url = AesGcmKeyManager().get_key_type();
+  util::StatusOr<KeyTypeInfoStore::Info*> info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), true);
+
+  // new_key_allowed true -> true is allowed.
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/true),
+              IsOk());
+  info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), true);
+
+  // new_key_allowed true -> false is allowed.
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/false),
+              IsOk());
+  info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), false);
+
+  // new_key_allowed false -> false is allowed.
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/false),
+              IsOk());
+  info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), false);
+
+  // new_key_allowed false -> true is not allowed.
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/true),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(KeyTypeInfoStoreTest, AddAsymmetricKeyTypeManagers) {
+  KeyTypeInfoStore store;
+  ASSERT_THAT(store.AddAsymmetricKeyTypeManagers(
+                  absl::make_unique<EcdsaSignKeyManager>(),
+                  absl::make_unique<EcdsaVerifyKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+
+  {
+    std::string private_type_url = EcdsaSignKeyManager().get_key_type();
+    util::StatusOr<KeyTypeInfoStore::Info*> info = store.Get(private_type_url);
+    ASSERT_THAT(info, IsOk());
+
+    util::StatusOr<const KeyManager<PublicKeySign>*> manager =
+        (*info)->get_key_manager<PublicKeySign>(private_type_url);
+    ASSERT_THAT(manager, IsOk());
+    EXPECT_EQ((*manager)->get_key_type(), private_type_url);
+  }
+  {
+    std::string public_type_url = EcdsaVerifyKeyManager().get_key_type();
+    util::StatusOr<KeyTypeInfoStore::Info*> info = store.Get(public_type_url);
+    ASSERT_THAT(info, IsOk());
+
+    util::StatusOr<const KeyManager<PublicKeyVerify>*> manager =
+        (*info)->get_key_manager<PublicKeyVerify>(public_type_url);
+    ASSERT_THAT(manager, IsOk());
+    EXPECT_EQ((*manager)->get_key_type(), public_type_url);
+  }
+}
+
+TEST(KeyTypeInfoStoreTest, AddAsymmetricKeyTypeManagersAlreadyExists) {
+  {
+    KeyTypeInfoStore store;
+    ASSERT_THAT(
+        store.AddKeyTypeManager(absl::make_unique<EcdsaSignKeyManager>(),
+                                /*new_key_allowed=*/true),
+        IsOk());
+    EXPECT_THAT(store.AddAsymmetricKeyTypeManagers(
+                    absl::make_unique<EcdsaSignKeyManager>(),
+                    absl::make_unique<EcdsaVerifyKeyManager>(),
+                    /*new_key_allowed=*/true),
+                StatusIs(absl::StatusCode::kInvalidArgument));
+  }
+  {
+    KeyTypeInfoStore store;
+    ASSERT_THAT(
+        store.AddKeyTypeManager(absl::make_unique<EcdsaVerifyKeyManager>(),
+                                /*new_key_allowed=*/true),
+        IsOk());
+    EXPECT_THAT(store.AddAsymmetricKeyTypeManagers(
+                    absl::make_unique<EcdsaSignKeyManager>(),
+                    absl::make_unique<EcdsaVerifyKeyManager>(),
+                    /*new_key_allowed=*/true),
+                StatusIs(absl::StatusCode::kInvalidArgument));
+  }
+  {
+    KeyTypeInfoStore store;
+    EXPECT_THAT(store.AddAsymmetricKeyTypeManagers(
+                    absl::make_unique<EcdsaSignKeyManager>(),
+                    absl::make_unique<EcdsaVerifyKeyManager>(),
+                    /*new_key_allowed=*/true),
+                IsOk());
+    EXPECT_THAT(store.AddAsymmetricKeyTypeManagers(
+                    absl::make_unique<EcdsaSignKeyManager>(),
+                    absl::make_unique<EcdsaVerifyKeyManager>(),
+                    /*new_key_allowed=*/true),
+                IsOk());
+  }
+}
+
+TEST(KeyTypeInfoStoreTest, AddAsymmetricKeyTypeManagersAndChangeNewKeyAllowed) {
+  KeyTypeInfoStore store;
+  ASSERT_THAT(store.AddAsymmetricKeyTypeManagers(
+                  absl::make_unique<EcdsaSignKeyManager>(),
+                  absl::make_unique<EcdsaVerifyKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+
+  std::string private_type_url = EcdsaSignKeyManager().get_key_type();
+  std::string public_type_url = EcdsaVerifyKeyManager().get_key_type();
+
+  util::StatusOr<KeyTypeInfoStore::Info*> private_info =
+      store.Get(private_type_url);
+  ASSERT_THAT(private_info, IsOk());
+  EXPECT_EQ((*private_info)->new_key_allowed(), true);
+  util::StatusOr<KeyTypeInfoStore::Info*> public_info =
+      store.Get(public_type_url);
+  ASSERT_THAT(public_info, IsOk());
+  EXPECT_EQ((*public_info)->new_key_allowed(), true);
+
+  // new_key_allowed true -> true is allowed.
+  ASSERT_THAT(store.AddAsymmetricKeyTypeManagers(
+                  absl::make_unique<EcdsaSignKeyManager>(),
+                  absl::make_unique<EcdsaVerifyKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+  private_info = store.Get(private_type_url);
+  ASSERT_THAT(private_info, IsOk());
+  EXPECT_EQ((*private_info)->new_key_allowed(), true);
+  public_info = store.Get(public_type_url);
+  ASSERT_THAT(public_info, IsOk());
+  EXPECT_EQ((*public_info)->new_key_allowed(), true);
+
+  // new_key_allowed true -> false is allowed.
+  ASSERT_THAT(store.AddAsymmetricKeyTypeManagers(
+                  absl::make_unique<EcdsaSignKeyManager>(),
+                  absl::make_unique<EcdsaVerifyKeyManager>(),
+                  /*new_key_allowed=*/false),
+              IsOk());
+  private_info = store.Get(private_type_url);
+  ASSERT_THAT(private_info, IsOk());
+  EXPECT_EQ((*private_info)->new_key_allowed(), false);
+  public_info = store.Get(public_type_url);
+  ASSERT_THAT(public_info, IsOk());
+  EXPECT_EQ((*public_info)->new_key_allowed(), true);
+
+  // new_key_allowed false -> false is allowed.
+  ASSERT_THAT(store.AddAsymmetricKeyTypeManagers(
+                  absl::make_unique<EcdsaSignKeyManager>(),
+                  absl::make_unique<EcdsaVerifyKeyManager>(),
+                  /*new_key_allowed=*/false),
+              IsOk());
+  private_info = store.Get(private_type_url);
+  ASSERT_THAT(private_info, IsOk());
+  EXPECT_EQ((*private_info)->new_key_allowed(), false);
+  public_info = store.Get(public_type_url);
+  ASSERT_THAT(public_info, IsOk());
+  EXPECT_EQ((*public_info)->new_key_allowed(), true);
+
+  // new_key_allowed false -> true is not allowed.
+  ASSERT_THAT(store.AddAsymmetricKeyTypeManagers(
+                  absl::make_unique<EcdsaSignKeyManager>(),
+                  absl::make_unique<EcdsaVerifyKeyManager>(),
+                  /*new_key_allowed=*/true),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(KeyTypeInfoStoreTest, AddKeyManager) {
+  KeyTypeInfoStore store;
+  AesGcmKeyManager manager;
+  ASSERT_THAT(store.AddKeyManager(MakeKeyManager<Aead>(&manager),
+                                  /*new_key_allowed=*/true),
+              IsOk());
+
+  std::string type_url = manager.get_key_type();
+  util::StatusOr<KeyTypeInfoStore::Info*> info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+
+  util::StatusOr<const KeyManager<Aead>*> got_manager =
+      (*info)->get_key_manager<Aead>(type_url);
+  ASSERT_THAT(got_manager, IsOk());
+  EXPECT_EQ((*got_manager)->get_key_type(), type_url);
+}
+
+TEST(KeyTypeInfoStoreTest, AddKeyManagerAndChangeNewKeyAllowed) {
+  KeyTypeInfoStore store;
+  AesGcmKeyManager manager;
+  ASSERT_THAT(store.AddKeyManager(MakeKeyManager<Aead>(&manager),
+                                  /*new_key_allowed=*/true),
+              IsOk());
+
+  std::string type_url = manager.get_key_type();
+  util::StatusOr<KeyTypeInfoStore::Info*> info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), true);
+
+  // new_key_allowed true -> true is allowed.
+  ASSERT_THAT(store.AddKeyManager(MakeKeyManager<Aead>(&manager),
+                                  /*new_key_allowed=*/true),
+              IsOk());
+  info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), true);
+
+  // new_key_allowed true -> false is allowed.
+  ASSERT_THAT(store.AddKeyManager(MakeKeyManager<Aead>(&manager),
+                                  /*new_key_allowed=*/false),
+              IsOk());
+  info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), false);
+
+  // new_key_allowed false -> false is allowed.
+  ASSERT_THAT(store.AddKeyManager(MakeKeyManager<Aead>(&manager),
+                                  /*new_key_allowed=*/false),
+              IsOk());
+  info = store.Get(type_url);
+  ASSERT_THAT(info, IsOk());
+  EXPECT_EQ((*info)->new_key_allowed(), false);
+
+  // new_key_allowed false -> true is not allowed.
+  ASSERT_THAT(store.AddKeyManager(MakeKeyManager<Aead>(&manager),
+                                  /*new_key_allowed=*/true),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(KeyTypeInfoStoreTest, Get) {
+  KeyTypeInfoStore store;
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/true),
+              IsOk());
+  util::StatusOr<KeyTypeInfoStore::Info*> info =
+      store.Get(AesGcmKeyManager().get_key_type());
+  EXPECT_THAT(info, IsOk());
+
+  EXPECT_THAT(store.Get("nonexistent.type.url").status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(KeyTypeInfoStoreTest, IsEmpty) {
+  KeyTypeInfoStore store;
+  EXPECT_EQ(store.IsEmpty(), true);
+
+  ASSERT_THAT(store.AddKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                      /*new_key_allowed=*/true),
+              IsOk());
+  EXPECT_THAT(store.IsEmpty(), false);
+}
+
+TEST(KeyTypeInfoStoreInfoTest, ConstructWithKeyTypeManager) {
+  KeyTypeInfoStore::Info info(absl::make_unique<AesGcmKeyManager>().release(),
+                              /*new_key_allowed=*/false);
+
+  EXPECT_EQ(info.key_manager_type_index(),
+            std::type_index(typeid(AesGcmKeyManager)));
+  EXPECT_EQ(info.public_key_type_manager_type_index(), absl::nullopt);
+
+  EXPECT_EQ(info.new_key_allowed(), false);
+  info.set_new_key_allowed(true);
+  EXPECT_EQ(info.new_key_allowed(), true);
+
+  std::string type_url = AesGcmKeyManager().get_key_type();
+  util::StatusOr<const KeyManager<Aead>*> aead_manager =
+      info.get_key_manager<Aead>(type_url);
+  ASSERT_THAT(aead_manager, IsOk());
+  EXPECT_EQ((*aead_manager)->DoesSupport(type_url), true);
+  util::StatusOr<const KeyManager<CordAead>*> cord_aead_manager =
+      info.get_key_manager<CordAead>(type_url);
+  ASSERT_THAT(aead_manager, IsOk());
+  EXPECT_EQ((*aead_manager)->DoesSupport(type_url), true);
+
+  AesGcmKeyFormat format;
+  format.set_key_size(32);
+  EXPECT_THAT(info.key_factory().NewKeyData(format.SerializeAsString()),
+              IsOk());
+
+  EXPECT_EQ((bool)info.key_deriver(), true);
+}
+
+TEST(KeyTypeInfoStoreInfoTest, ConstructWithAsymmetricKeyTypeManagers) {
+  KeyTypeInfoStore::Info info(
+      absl::make_unique<EcdsaSignKeyManager>().release(),
+      absl::make_unique<EcdsaVerifyKeyManager>().get(),
+      /*new_key_allowed=*/false);
+
+  EXPECT_EQ(info.key_manager_type_index(),
+            std::type_index(typeid(EcdsaSignKeyManager)));
+  EXPECT_EQ(info.public_key_type_manager_type_index(),
+            std::type_index(typeid(EcdsaVerifyKeyManager)));
+
+  EXPECT_EQ(info.new_key_allowed(), false);
+  info.set_new_key_allowed(true);
+  EXPECT_EQ(info.new_key_allowed(), true);
+
+  std::string type_url = EcdsaSignKeyManager().get_key_type();
+  util::StatusOr<const KeyManager<PublicKeySign>*> manager =
+      info.get_key_manager<PublicKeySign>(type_url);
+  ASSERT_THAT(manager, IsOk());
+  EXPECT_EQ((*manager)->DoesSupport(type_url), true);
+
+  EcdsaKeyFormat format;
+  EcdsaParams* params = format.mutable_params();
+  params->set_hash_type(HashType::SHA256);
+  params->set_curve(EllipticCurveType::NIST_P256);
+  params->set_encoding(EcdsaSignatureEncoding::DER);
+  EXPECT_THAT(info.key_factory().NewKeyData(format.SerializeAsString()),
+              IsOk());
+
+  EXPECT_EQ((bool)info.key_deriver(), true);
+}
+
+TEST(KeyTypeInfoStoreInfoTest, ConstructWithKeyManager) {
+  AesGcmKeyManager key_type_manager;
+  std::unique_ptr<KeyManager<Aead>> manager =
+      MakeKeyManager<Aead>(&key_type_manager);
+  std::type_index type_index = std::type_index(typeid(*manager));
+  KeyTypeInfoStore::Info info(manager.release(),
+                              /*new_key_allowed=*/false);
+
+  EXPECT_EQ(info.key_manager_type_index(), type_index);
+  EXPECT_EQ(info.public_key_type_manager_type_index(), absl::nullopt);
+
+  EXPECT_EQ(info.new_key_allowed(), false);
+  info.set_new_key_allowed(true);
+  EXPECT_EQ(info.new_key_allowed(), true);
+
+  std::string type_url = AesGcmKeyManager().get_key_type();
+  util::StatusOr<const KeyManager<Aead>*> got_manager =
+      info.get_key_manager<Aead>(type_url);
+  ASSERT_THAT(got_manager, IsOk());
+  EXPECT_EQ((*got_manager)->DoesSupport(type_url), true);
+  // Inserted KeyManager only supports Aead, not CordAead.
+  EXPECT_THAT(info.get_key_manager<CordAead>(type_url).status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+
+  AesGcmKeyFormat format;
+  format.set_key_size(32);
+  EXPECT_THAT(info.key_factory().NewKeyData(format.SerializeAsString()),
+              IsOk());
+
+  EXPECT_EQ((bool)info.key_deriver(), false);
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/keyset_handle_builder_entry.cc b/cc/internal/keyset_handle_builder_entry.cc
new file mode 100644
index 0000000..d407cc9
--- /dev/null
+++ b/cc/internal/keyset_handle_builder_entry.cc
@@ -0,0 +1,216 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/keyset_handle_builder_entry.h"
+
+#include <memory>
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/key_status_util.h"
+#include "tink/internal/legacy_proto_key.h"
+#include "tink/internal/legacy_proto_parameters.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/internal/serialization.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/registry.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+
+Keyset::Key ToKeysetKey(int id, KeyStatusType status,
+                        const ProtoKeySerialization& serialization) {
+  KeyData key_data;
+  key_data.set_type_url(std::string(serialization.TypeUrl()));
+  // OSS proto library complains if serialized key is not converted to string.
+  key_data.set_value(std::string(serialization.SerializedKeyProto().GetSecret(
+      InsecureSecretKeyAccess::Get())));
+  key_data.set_key_material_type(serialization.KeyMaterialType());
+  Keyset::Key key;
+  key.set_status(status);
+  key.set_key_id(id);
+  key.set_output_prefix_type(serialization.GetOutputPrefixType());
+  *key.mutable_key_data() = key_data;
+  return key;
+}
+
+util::StatusOr<ProtoParametersSerialization> SerializeParameters(
+    const Parameters& params) {
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<ProtoParametersSerialization>(params);
+  if (!serialization.ok()) return serialization.status();
+
+  const ProtoParametersSerialization* proto_serialization =
+      dynamic_cast<const ProtoParametersSerialization*>(serialization->get());
+  if (proto_serialization == nullptr) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Failed to serialize proto parameters.");
+  }
+
+  return *proto_serialization;
+}
+
+util::StatusOr<ProtoParametersSerialization> SerializeLegacyParameters(
+    const Parameters* params) {
+  const LegacyProtoParameters* proto_params =
+      dynamic_cast<const LegacyProtoParameters*>(params);
+  if (proto_params == nullptr) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to serialize legacy proto parameters.");
+  }
+  return proto_params->Serialization();
+}
+
+util::StatusOr<ProtoKeySerialization> SerializeKey(const Key& key) {
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<ProtoKeySerialization>(key,
+                                               InsecureSecretKeyAccess::Get());
+  if (!serialization.ok()) return serialization.status();
+
+  const ProtoKeySerialization* serialized_proto_key =
+      dynamic_cast<const ProtoKeySerialization*>(serialization->get());
+  if (serialized_proto_key == nullptr) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Failed to serialize proto key.");
+  }
+
+  return *serialized_proto_key;
+}
+
+util::StatusOr<ProtoKeySerialization> SerializeLegacyKey(const Key* key) {
+  const LegacyProtoKey* proto_key = dynamic_cast<const LegacyProtoKey*>(key);
+  if (proto_key == nullptr) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to serialize legacy proto key.");
+  }
+  util::StatusOr<const ProtoKeySerialization*> serialized_key =
+      proto_key->Serialization(InsecureSecretKeyAccess::Get());
+  if (!serialized_key.ok()) return serialized_key.status();
+
+  return **serialized_key;
+}
+
+util::StatusOr<Keyset::Key> CreateKeysetKeyFromProtoParametersSerialization(
+    const ProtoParametersSerialization& serialization, int id,
+    KeyStatusType status) {
+  util::StatusOr<std::unique_ptr<KeyData>> key_data =
+      Registry::NewKeyData(serialization.GetKeyTemplate());
+  if (!key_data.ok()) return key_data.status();
+
+  Keyset::Key key;
+  key.set_status(status);
+  key.set_key_id(id);
+  key.set_output_prefix_type(
+      serialization.GetKeyTemplate().output_prefix_type());
+  *key.mutable_key_data() = **key_data;
+  return key;
+}
+
+util::StatusOr<Keyset::Key> CreateKeysetKeyFromProtoKeySerialization(
+    const ProtoKeySerialization& key, int id, KeyStatusType status) {
+  absl::optional<int> id_requirement = key.IdRequirement();
+  if (id_requirement.has_value() && *id_requirement != id) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong ID set for key with ID requirement.");
+  }
+  return ToKeysetKey(id, status, key);
+}
+
+}  // namespace
+
+void KeysetHandleBuilderEntry::SetFixedId(int id) {
+  strategy_.strategy = KeyIdStrategyEnum::kFixedId;
+  strategy_.id_requirement = id;
+}
+
+void KeysetHandleBuilderEntry::SetRandomId() {
+  strategy_.strategy = KeyIdStrategyEnum::kRandomId;
+  strategy_.id_requirement = absl::nullopt;
+}
+
+util::StatusOr<Keyset::Key> KeyEntry::CreateKeysetKey(int id) {
+  util::StatusOr<KeyStatusType> key_status = ToKeyStatusType(key_status_);
+  if (!key_status.ok()) return key_status.status();
+
+  if (GetKeyIdRequirement().has_value() && GetKeyIdRequirement() != id) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Requested id does not match id requirement.");
+  }
+
+  util::StatusOr<ProtoKeySerialization> serialization = SerializeKey(*key_);
+  if (!serialization.ok() &&
+      serialization.status().code() != absl::StatusCode::kNotFound) {
+    return serialization.status();
+  }
+
+  if (serialization.status().code() == absl::StatusCode::kNotFound) {
+    // Fallback to legacy proto key.
+    serialization = SerializeLegacyKey(key_.get());
+    if (!serialization.ok()) return serialization.status();
+  }
+
+  return CreateKeysetKeyFromProtoKeySerialization(*serialization, id,
+                                                  *key_status);
+}
+
+util::StatusOr<Keyset::Key> ParametersEntry::CreateKeysetKey(int id) {
+  util::StatusOr<KeyStatusType> key_status = ToKeyStatusType(key_status_);
+  if (!key_status.ok()) return key_status.status();
+
+  if (GetKeyIdRequirement().has_value() && GetKeyIdRequirement() != id) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Requested id does not match id requirement.");
+  }
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      SerializeParameters(*parameters_);
+  if (!serialization.ok() &&
+      serialization.status().code() != absl::StatusCode::kNotFound) {
+    return serialization.status();
+  }
+
+  if (serialization.status().code() == absl::StatusCode::kNotFound) {
+    // Fallback to legacy proto parameters.
+    serialization = SerializeLegacyParameters(parameters_.get());
+    if (!serialization.ok()) return serialization.status();
+  }
+
+  return CreateKeysetKeyFromProtoParametersSerialization(*serialization, id,
+                                                         *key_status);
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/keyset_handle_builder_entry.h b/cc/internal/keyset_handle_builder_entry.h
new file mode 100644
index 0000000..a1d0a6c
--- /dev/null
+++ b/cc/internal/keyset_handle_builder_entry.h
@@ -0,0 +1,132 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_KEYSET_HANDLE_BUILDER_ENTRY_H_
+#define TINK_INTERNAL_KEYSET_HANDLE_BUILDER_ENTRY_H_
+
+#include <memory>
+#include <utility>
+
+#include "absl/types/optional.h"
+#include "tink/key.h"
+#include "tink/key_status.h"
+#include "tink/parameters.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+enum class KeyIdStrategyEnum : int {
+  kFixedId = 1,
+  kRandomId = 2,
+  // Added to guard from failures that may be caused by future expansions.
+  kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+};
+
+struct KeyIdStrategy {
+  KeyIdStrategyEnum strategy;
+  absl::optional<int> id_requirement;
+};
+
+// Internal keyset handle builder entry. The public keyset handle builder
+// entry will delegate its method calls to an instance of this class.
+class KeysetHandleBuilderEntry {
+ public:
+  KeysetHandleBuilderEntry() = default;
+  virtual ~KeysetHandleBuilderEntry() = default;
+
+  // Sets the key `status` of this entry.
+  void SetStatus(KeyStatus status) { key_status_ = status; }
+  // Returns key status of this entry.
+  KeyStatus GetStatus() const { return key_status_; }
+
+  // Assigns a fixed `id` when this keyset is built.
+  void SetFixedId(int id);
+  // Assigns an unused random id when this keyset is built.
+  void SetRandomId();
+
+  // Sets this entry as the primary key.
+  void SetPrimary() { is_primary_ = true; }
+  // Unsets this entry as the primary key.
+  void UnsetPrimary() { is_primary_ = false; }
+  // Returns whether or not this entry has been marked as a primary.
+  bool IsPrimary() const { return is_primary_; }
+
+  // Returns key id strategy.
+  KeyIdStrategy GetKeyIdStrategy() { return strategy_; }
+  // Returns key id strategy enum.
+  KeyIdStrategyEnum GetKeyIdStrategyEnum() { return strategy_.strategy; }
+  // Returns key id requirement.
+  absl::optional<int> GetKeyIdRequirement() { return strategy_.id_requirement; }
+
+  // Creates a Keyset::Key proto with the specified key `id` from either a
+  // `Key` object or a `Parameters` object.
+  virtual crypto::tink::util::StatusOr<google::crypto::tink::Keyset::Key>
+  CreateKeysetKey(int id) = 0;
+
+ protected:
+  KeyStatus key_status_ = KeyStatus::kDisabled;
+
+ private:
+  bool is_primary_ = false;
+  KeyIdStrategy strategy_ =
+      KeyIdStrategy{KeyIdStrategyEnum::kRandomId, absl::nullopt};
+};
+
+// Internal keyset handle builder entry constructed from a `Key` object.
+class KeyEntry : public KeysetHandleBuilderEntry {
+ public:
+  // Movable, but not copyable.
+  KeyEntry(KeyEntry&& other) = default;
+  KeyEntry& operator=(KeyEntry&& other) = default;
+  KeyEntry(const KeyEntry& other) = delete;
+  KeyEntry& operator=(const KeyEntry& other) = delete;
+
+  explicit KeyEntry(std::shared_ptr<const Key> key) : key_(std::move(key)) {}
+
+  crypto::tink::util::StatusOr<google::crypto::tink::Keyset::Key>
+  CreateKeysetKey(int id) override;
+
+ private:
+  std::shared_ptr<const Key> key_;
+};
+
+// Internal keyset handle builder entry constructed from a `Parameters` object.
+class ParametersEntry : public KeysetHandleBuilderEntry {
+ public:
+  // Movable, but not copyable.
+  ParametersEntry(ParametersEntry&& other) = default;
+  ParametersEntry& operator=(ParametersEntry&& other) = default;
+  ParametersEntry(const ParametersEntry& other) = delete;
+  ParametersEntry& operator=(const ParametersEntry& other) = delete;
+
+  explicit ParametersEntry(std::shared_ptr<const Parameters> parameters)
+      : parameters_(std::move(parameters)) {}
+
+  crypto::tink::util::StatusOr<google::crypto::tink::Keyset::Key>
+  CreateKeysetKey(int id) override;
+
+ private:
+  std::shared_ptr<const Parameters> parameters_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_KEYSET_HANDLE_BUILDER_ENTRY_H_
diff --git a/cc/internal/keyset_handle_builder_entry_test.cc b/cc/internal/keyset_handle_builder_entry_test.cc
new file mode 100644
index 0000000..f26b6a1
--- /dev/null
+++ b/cc/internal/keyset_handle_builder_entry_test.cc
@@ -0,0 +1,290 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/keyset_handle_builder_entry.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/config/tink_config.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/legacy_proto_key.h"
+#include "tink/internal/legacy_proto_parameters.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/key.h"
+#include "tink/key_status.h"
+#include "tink/keyset_handle.h"
+#include "tink/keyset_handle_builder.h"
+#include "tink/mac/aes_cmac_key.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/mac/mac_key_templates.h"
+#include "tink/parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+using ::testing::Test;
+
+util::StatusOr<LegacyProtoParameters> CreateLegacyProtoParameters() {
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create(MacKeyTemplates::AesCmac());
+  if (!serialization.ok()) return serialization.status();
+
+  return LegacyProtoParameters(*serialization);
+}
+
+TEST(KeysetHandleBuilderEntryTest, Status) {
+  util::StatusOr<LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  ParametersEntry entry =
+      ParametersEntry(absl::make_unique<LegacyProtoParameters>(*parameters));
+
+  entry.SetStatus(KeyStatus::kEnabled);
+  EXPECT_THAT(entry.GetStatus(), KeyStatus::kEnabled);
+
+  entry.SetStatus(KeyStatus::kDisabled);
+  EXPECT_THAT(entry.GetStatus(), KeyStatus::kDisabled);
+
+  entry.SetStatus(KeyStatus::kDestroyed);
+  EXPECT_THAT(entry.GetStatus(), KeyStatus::kDestroyed);
+}
+
+TEST(KeysetHandleBuilderEntryTest, IdStrategy) {
+  util::StatusOr<LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  ParametersEntry entry =
+      ParametersEntry(absl::make_unique<LegacyProtoParameters>(*parameters));
+
+  entry.SetFixedId(123);
+  EXPECT_THAT(entry.GetKeyIdStrategyEnum(), KeyIdStrategyEnum::kFixedId);
+  EXPECT_THAT(entry.GetKeyIdStrategy().strategy, KeyIdStrategyEnum::kFixedId);
+  EXPECT_THAT(entry.GetKeyIdStrategy().id_requirement, 123);
+  EXPECT_THAT(entry.GetKeyIdRequirement(), 123);
+
+  entry.SetRandomId();
+  EXPECT_THAT(entry.GetKeyIdStrategyEnum(), KeyIdStrategyEnum::kRandomId);
+  EXPECT_THAT(entry.GetKeyIdStrategy().strategy, KeyIdStrategyEnum::kRandomId);
+  EXPECT_THAT(entry.GetKeyIdStrategy().id_requirement, absl::nullopt);
+  EXPECT_THAT(entry.GetKeyIdRequirement(), absl::nullopt);
+}
+
+TEST(KeysetHandleBuilderEntryTest, Primary) {
+  util::StatusOr<LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  ParametersEntry entry =
+      ParametersEntry(absl::make_unique<LegacyProtoParameters>(*parameters));
+
+  entry.SetPrimary();
+  EXPECT_THAT(entry.IsPrimary(), IsTrue());
+
+  entry.UnsetPrimary();
+  EXPECT_THAT(entry.IsPrimary(), IsFalse());
+}
+
+class CreateKeysetKeyTest : public Test {
+ protected:
+  void SetUp() override { ASSERT_THAT(TinkConfig::Register(), IsOk()); }
+};
+
+TEST_F(CreateKeysetKeyTest, CreateKeysetKeyFromParameters) {
+  util::StatusOr<LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  ParametersEntry entry =
+      ParametersEntry(absl::make_unique<LegacyProtoParameters>(*parameters));
+  entry.SetStatus(KeyStatus::kEnabled);
+  entry.SetFixedId(123);
+  util::StatusOr<Keyset::Key> keyset_key = entry.CreateKeysetKey(/*id=*/123);
+  ASSERT_THAT(keyset_key, IsOk());
+
+  EXPECT_THAT(keyset_key->status(), Eq(KeyStatusType::ENABLED));
+  EXPECT_THAT(keyset_key->key_id(), Eq(123));
+  EXPECT_THAT(
+      keyset_key->output_prefix_type(),
+      Eq(parameters->Serialization().GetKeyTemplate().output_prefix_type()));
+  EXPECT_THAT(keyset_key->key_data().type_url(),
+              Eq(parameters->Serialization().GetKeyTemplate().type_url()));
+}
+
+TEST_F(CreateKeysetKeyTest, CreateKeysetKeyFromParametersWithDifferentKeyId) {
+  util::StatusOr<LegacyProtoParameters> parameters =
+      CreateLegacyProtoParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  ParametersEntry entry =
+      ParametersEntry(absl::make_unique<LegacyProtoParameters>(*parameters));
+  entry.SetStatus(KeyStatus::kEnabled);
+  entry.SetFixedId(123);
+  util::StatusOr<Keyset::Key> keyset_key = entry.CreateKeysetKey(/*id=*/456);
+  EXPECT_THAT(keyset_key.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(CreateKeysetKeyTest, CreateKeysetKeyFromKey) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/123);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  KeyEntry entry = KeyEntry(absl::make_unique<LegacyProtoKey>(*key));
+  entry.SetStatus(KeyStatus::kEnabled);
+  entry.SetFixedId(123);
+  util::StatusOr<Keyset::Key> keyset_key = entry.CreateKeysetKey(/*id=*/123);
+  ASSERT_THAT(keyset_key, IsOk());
+
+  EXPECT_THAT(keyset_key->status(), Eq(KeyStatusType::ENABLED));
+  EXPECT_THAT(keyset_key->key_id(), Eq(123));
+  EXPECT_THAT(keyset_key->output_prefix_type(), OutputPrefixType::TINK);
+  EXPECT_THAT(keyset_key->key_data().type_url(), Eq("type_url"));
+  EXPECT_THAT(keyset_key->key_data().key_material_type(),
+              Eq(KeyData::SYMMETRIC));
+  EXPECT_THAT(keyset_key->key_data().value(), Eq("serialized_key"));
+}
+
+TEST_F(CreateKeysetKeyTest, CreateKeysetKeyFromKeyWithDifferentEntryKeyId) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/123);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  KeyEntry entry = KeyEntry(absl::make_unique<LegacyProtoKey>(*key));
+  entry.SetStatus(KeyStatus::kEnabled);
+  entry.SetFixedId(123);
+  util::StatusOr<Keyset::Key> keyset_key = entry.CreateKeysetKey(/*id=*/456);
+  EXPECT_THAT(keyset_key.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(CreateKeysetKeyTest,
+       CreateKeysetKeyFromKeyWithDifferentSerializationKeyId) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/123);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  KeyEntry entry = KeyEntry(absl::make_unique<LegacyProtoKey>(*key));
+  entry.SetStatus(KeyStatus::kEnabled);
+  util::StatusOr<Keyset::Key> keyset_key = entry.CreateKeysetKey(/*id=*/456);
+  EXPECT_THAT(keyset_key.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(CreateKeysetKeyTest, CreateKeysetFromNonLegacyParameters) {
+  util::StatusOr<AesCmacParameters> aes_cmac_parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(aes_cmac_parameters, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *aes_cmac_parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+              /*id=*/123))
+          .Build();
+  ASSERT_THAT(handle, IsOk());
+}
+
+TEST_F(CreateKeysetKeyTest,
+       CreateKeysetWithAllowedParametersProhibitedByKeyManager) {
+  util::StatusOr<AesCmacParameters> aes_cmac_parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/16,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(aes_cmac_parameters, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *aes_cmac_parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+              /*id=*/123))
+          .Build();
+  ASSERT_THAT(handle.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(CreateKeysetKeyTest, CreateKeysetFromNonLegacyKey) {
+  util::StatusOr<AesCmacParameters> aes_cmac_parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(aes_cmac_parameters, IsOk());
+  util::StatusOr<AesCmacKey> aes_cmac_key = AesCmacKey::Create(
+      *aes_cmac_parameters, RestrictedData(32), 123, GetPartialKeyAccess());
+  ASSERT_THAT(aes_cmac_key.status(), IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableKey(
+              *aes_cmac_key, KeyStatus::kEnabled, /*is_primary=*/true))
+          .Build();
+  ASSERT_THAT(handle, IsOk());
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/keyset_wrapper.h b/cc/internal/keyset_wrapper.h
index ab10b9e..3258b57 100644
--- a/cc/internal/keyset_wrapper.h
+++ b/cc/internal/keyset_wrapper.h
@@ -16,6 +16,7 @@
 #ifndef TINK_INTERNAL_KEYSET_WRAPPER_H_
 #define TINK_INTERNAL_KEYSET_WRAPPER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/container/flat_hash_map.h"
@@ -41,7 +42,7 @@
 template <typename Primitive>
 class KeysetWrapper {
  public:
-  virtual ~KeysetWrapper() {}
+  virtual ~KeysetWrapper() = default;
 
   // Wraps a given `keyset` with annotations `annotations`.
   virtual crypto::tink::util::StatusOr<std::unique_ptr<Primitive>> Wrap(
diff --git a/cc/internal/keyset_wrapper_impl.h b/cc/internal/keyset_wrapper_impl.h
index 4dc092f..9273e4e 100644
--- a/cc/internal/keyset_wrapper_impl.h
+++ b/cc/internal/keyset_wrapper_impl.h
@@ -16,13 +16,17 @@
 #ifndef TINK_INTERNAL_KEYSET_WRAPPER_IMPL_H_
 #define TINK_INTERNAL_KEYSET_WRAPPER_IMPL_H_
 
+#include <functional>
+#include <memory>
 #include <string>
+#include <utility>
 
 #include "absl/container/flat_hash_map.h"
 #include "tink/internal/key_info.h"
 #include "tink/internal/keyset_wrapper.h"
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/validation.h"
 #include "proto/tink.pb.h"
@@ -50,22 +54,27 @@
       const override {
     crypto::tink::util::Status status = ValidateKeyset(keyset);
     if (!status.ok()) return status;
-    auto primitives = absl::make_unique<PrimitiveSet<P>>(annotations);
+    typename PrimitiveSet<P>::Builder primitives_builder;
+    primitives_builder.AddAnnotations(annotations);
     for (const google::crypto::tink::Keyset::Key& key : keyset.key()) {
       if (key.status() != google::crypto::tink::KeyStatusType::ENABLED) {
         continue;
       }
       auto primitive = primitive_getter_(key.key_data());
       if (!primitive.ok()) return primitive.status();
-      auto entry = primitives->AddPrimitive(std::move(primitive.value()),
-                                            KeyInfoFromKey(key));
-      if (!entry.ok()) return entry.status();
       if (key.key_id() == keyset.primary_key_id()) {
-        auto primary_result = primitives->set_primary(entry.value());
-        if (!primary_result.ok()) return primary_result;
+        primitives_builder.AddPrimaryPrimitive(std::move(primitive.value()),
+                                               KeyInfoFromKey(key));
+      } else {
+        primitives_builder.AddPrimitive(std::move(primitive.value()),
+                                        KeyInfoFromKey(key));
       }
     }
-    return transforming_wrapper_.Wrap(std::move(primitives));
+    crypto::tink::util::StatusOr<PrimitiveSet<P>> primitives =
+        std::move(primitives_builder).Build();
+    if (!primitives.ok()) return primitives.status();
+    return transforming_wrapper_.Wrap(
+        absl::make_unique<PrimitiveSet<P>>(*std::move(primitives)));
   }
 
  private:
diff --git a/cc/internal/keyset_wrapper_impl_test.cc b/cc/internal/keyset_wrapper_impl_test.cc
index 0606aa1..7c2955b 100644
--- a/cc/internal/keyset_wrapper_impl_test.cc
+++ b/cc/internal/keyset_wrapper_impl_test.cc
@@ -15,9 +15,13 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/keyset_wrapper_impl.h"
 
+#include <stdint.h>
+
 #include <memory>
 #include <string>
+#include <tuple>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -25,8 +29,10 @@
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
diff --git a/cc/internal/keyset_wrapper_store.h b/cc/internal/keyset_wrapper_store.h
new file mode 100644
index 0000000..dab9f66
--- /dev/null
+++ b/cc/internal/keyset_wrapper_store.h
@@ -0,0 +1,208 @@
+// Copyright 2018 Google Inc.
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_KEYSET_WRAPPER_STORE_H_
+#define TINK_INTERNAL_KEYSET_WRAPPER_STORE_H_
+
+#include <memory>
+#include <typeindex>
+
+#include "tink/internal/keyset_wrapper.h"
+#include "tink/internal/keyset_wrapper_impl.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Stores KeysetWrappers constructed from their PrimitiveWrapper. This is used
+// by the Configuration and Registry classes.
+//
+// Once inserted, elements in Info, which include the PrimitiveWrapper, must not
+// be replaced.
+//
+// Example:
+//  KeysetWrapperStore store;
+//  crypto::tink::util::Status status = store.Add<Aead, Aead>(
+//      absl::make_unique<AeadWrapper>(), primitive_getter);
+//  crypto::tink::util::StatusOr<const KeysetWrapper<Aead>*> wrapper =
+//      store.Get<Aead>();
+class KeysetWrapperStore {
+ public:
+  KeysetWrapperStore() = default;
+
+  // Movable, but not copyable.
+  KeysetWrapperStore(KeysetWrapperStore&& other) = default;
+  KeysetWrapperStore& operator=(KeysetWrapperStore&& other) = default;
+
+  // Adds a crypto::tink::PrimitiveWrapper and `primitive_getter` function to
+  // KeysetWrapperStore.
+  template <class P, class Q>
+  crypto::tink::util::Status Add(
+      std::unique_ptr<PrimitiveWrapper<P, Q>> wrapper,
+      std::function<crypto::tink::util::StatusOr<std::unique_ptr<P>>(
+          const google::crypto::tink::KeyData& key_data)>
+          primitive_getter);
+
+  // Gets the PrimitiveWrapper that produces primitive P. This is a legacy
+  // function.
+  template <class P>
+  crypto::tink::util::StatusOr<const PrimitiveWrapper<P, P>*>
+  GetPrimitiveWrapper() const;
+
+  // Gets the KeysetWrapper that produces primitive Q.
+  template <class Q>
+  crypto::tink::util::StatusOr<const KeysetWrapper<Q>*> Get() const;
+
+  bool IsEmpty() const { return primitive_to_info_.empty(); }
+
+ private:
+  class Info {
+   public:
+    template <typename P, typename Q>
+    explicit Info(
+        std::unique_ptr<PrimitiveWrapper<P, Q>> wrapper,
+        std::function<crypto::tink::util::StatusOr<std::unique_ptr<P>>(
+            const google::crypto::tink::KeyData& key_data)>
+            primitive_getter)
+        : is_same_primitive_wrapping_(std::is_same<P, Q>::value),
+          wrapper_type_index_(std::type_index(typeid(*wrapper))),
+          q_type_index_(std::type_index(typeid(Q))) {
+      keyset_wrapper_ = absl::make_unique<KeysetWrapperImpl<P, Q>>(
+          wrapper.get(), primitive_getter);
+      original_wrapper_ = std::move(wrapper);
+    }
+
+    template <typename Q>
+    crypto::tink::util::StatusOr<const KeysetWrapper<Q>*> Get() const {
+      if (q_type_index_ != std::type_index(typeid(Q))) {
+        return crypto::tink::util::Status(
+            absl::StatusCode::kInternal,
+            "RegistryImpl::KeysetWrapper() called with wrong type");
+      }
+      return static_cast<KeysetWrapper<Q>*>(keyset_wrapper_.get());
+    }
+
+    // TODO(b/171021679): Deprecate this and upstream functions.
+    template <typename P>
+    crypto::tink::util::StatusOr<const PrimitiveWrapper<P, P>*>
+    GetPrimitiveWrapper() const {
+      if (!is_same_primitive_wrapping_) {
+        // This happens if a user uses a legacy method (like Registry::Wrap)
+        // directly or has a custom key manager for a primitive which has a
+        // PrimitiveWrapper<P,Q> with P != Q.
+        return crypto::tink::util::Status(
+            absl::StatusCode::kFailedPrecondition,
+            absl::StrCat("Cannot use primitive type ", typeid(P).name(),
+                         " with a custom key manager."));
+      }
+      if (q_type_index_ != std::type_index(typeid(P))) {
+        return crypto::tink::util::Status(
+            absl::StatusCode::kInternal,
+            "RegistryImpl::LegacyWrapper() called with wrong type");
+      }
+      return static_cast<const PrimitiveWrapper<P, P>*>(
+          original_wrapper_.get());
+    }
+
+    // Returns true if the PrimitiveWrapper is the same class as the one used
+    // to construct this Info.
+    template <typename P, typename Q>
+    bool HasSameType(const PrimitiveWrapper<P, Q>& wrapper) {
+      return wrapper_type_index_ == std::type_index(typeid(wrapper));
+    }
+
+   private:
+    bool is_same_primitive_wrapping_;
+    // dynamic std::type_index of the actual PrimitiveWrapper<P,Q> class for
+    // which this key was inserted.
+    std::type_index wrapper_type_index_;
+    // dynamic std::type_index of Q, when PrimitiveWrapper<P,Q> was inserted.
+    std::type_index q_type_index_;
+    // The primitive_wrapper passed in. We use a shared_ptr because
+    // unique_ptr<void> is invalid.
+    std::shared_ptr<void> original_wrapper_;
+    // The keyset_wrapper_. We use a shared_ptr because unique_ptr<void> is
+    // invalid.
+    std::shared_ptr<void> keyset_wrapper_;
+  };
+
+  // Map from primitive type_index to Info.
+  absl::flat_hash_map<std::type_index, Info> primitive_to_info_;
+};
+
+template <class P, class Q>
+crypto::tink::util::Status KeysetWrapperStore::Add(
+    std::unique_ptr<PrimitiveWrapper<P, Q>> wrapper,
+    std::function<crypto::tink::util::StatusOr<std::unique_ptr<P>>(
+        const google::crypto::tink::KeyData& key_data)>
+        primitive_getter) {
+  if (wrapper == nullptr) {
+    return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
+                                      "Parameter 'wrapper' must be non-null.");
+  }
+  if (primitive_getter == nullptr) {
+    return crypto::tink::util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Parameter 'primitive_getter' must be non-null.");
+  }
+
+  auto it = primitive_to_info_.find(std::type_index(typeid(Q)));
+  if (it != primitive_to_info_.end()) {
+    if (!it->second.HasSameType(*wrapper)) {
+      return util::Status(absl::StatusCode::kAlreadyExists,
+                          "A wrapper named for this primitive already exists.");
+    }
+    return crypto::tink::util::OkStatus();
+  }
+
+  primitive_to_info_.insert(
+      {std::type_index(typeid(Q)), Info(std::move(wrapper), primitive_getter)});
+
+  return crypto::tink::util::OkStatus();
+}
+
+template <class P>
+crypto::tink::util::StatusOr<const PrimitiveWrapper<P, P>*>
+KeysetWrapperStore::GetPrimitiveWrapper() const {
+  auto it = primitive_to_info_.find(std::type_index(typeid(P)));
+  if (it == primitive_to_info_.end()) {
+    return util::Status(
+        absl::StatusCode::kNotFound,
+        absl::StrCat("No wrapper registered for type ", typeid(P).name()));
+  }
+  return it->second.GetPrimitiveWrapper<P>();
+}
+
+template <class P>
+crypto::tink::util::StatusOr<const KeysetWrapper<P>*> KeysetWrapperStore::Get()
+    const {
+  auto it = primitive_to_info_.find(std::type_index(typeid(P)));
+  if (it == primitive_to_info_.end()) {
+    return util::Status(
+        absl::StatusCode::kNotFound,
+        absl::StrCat("No wrapper registered for type ", typeid(P).name()));
+  }
+  return it->second.Get<P>();
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_KEYSET_WRAPPER_STORE_H_
diff --git a/cc/internal/keyset_wrapper_store_test.cc b/cc/internal/keyset_wrapper_store_test.cc
new file mode 100644
index 0000000..9c7231b
--- /dev/null
+++ b/cc/internal/keyset_wrapper_store_test.cc
@@ -0,0 +1,408 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/keyset_wrapper_store.h"
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/internal/registry_impl.h"
+#include "tink/mac/mac_wrapper.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/subtle/random.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_gcm.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeysetInfo;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+
+class FakePrimitive {
+ public:
+  explicit FakePrimitive(std::string s) : s_(s) {}
+  std::string get() { return s_; }
+
+ private:
+  std::string s_;
+};
+
+class FakeKeyTypeManager
+    : public KeyTypeManager<AesGcmKey, AesGcmKeyFormat, List<FakePrimitive>> {
+ public:
+  class FakePrimitiveFactory : public PrimitiveFactory<FakePrimitive> {
+   public:
+    util::StatusOr<std::unique_ptr<FakePrimitive>> Create(
+        const AesGcmKey& key) const override {
+      return absl::make_unique<FakePrimitive>(key.key_value());
+    }
+  };
+
+  FakeKeyTypeManager()
+      : KeyTypeManager(absl::make_unique<FakePrimitiveFactory>()) {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
+
+  uint32_t get_version() const override { return 0; }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  util::Status ValidateKey(const AesGcmKey& key) const override {
+    return util::OkStatus();
+  }
+
+  util::Status ValidateKeyFormat(
+      const AesGcmKeyFormat& key_format) const override {
+    return util::OkStatus();
+  }
+
+  util::StatusOr<AesGcmKey> CreateKey(
+      const AesGcmKeyFormat& key_format) const override {
+    return AesGcmKey();
+  }
+
+  util::StatusOr<AesGcmKey> DeriveKey(
+      const AesGcmKeyFormat& key_format,
+      InputStream* input_stream) const override {
+    return AesGcmKey();
+  }
+
+ private:
+  const std::string key_type_ =
+      "type.googleapis.com/google.crypto.tink.AesGcmKey";
+};
+
+class FakePrimitiveWrapper
+    : public PrimitiveWrapper<FakePrimitive, FakePrimitive> {
+ public:
+  util::StatusOr<std::unique_ptr<FakePrimitive>> Wrap(
+      std::unique_ptr<PrimitiveSet<FakePrimitive>> primitive_set)
+      const override {
+    return absl::make_unique<FakePrimitive>(
+        primitive_set->get_primary()->get_primitive().get());
+  }
+};
+
+class FakePrimitiveWrapper2
+    : public PrimitiveWrapper<FakePrimitive, FakePrimitive> {
+ public:
+  util::StatusOr<std::unique_ptr<FakePrimitive>> Wrap(
+      std::unique_ptr<PrimitiveSet<FakePrimitive>> primitive_set)
+      const override {
+    return absl::make_unique<FakePrimitive>(
+        primitive_set->get_primary()->get_primitive().get());
+  }
+};
+
+std::string AddAesGcmKeyToKeyset(Keyset& keyset, uint32_t key_id,
+                                 OutputPrefixType output_prefix_type,
+                                 KeyStatusType key_status_type) {
+  AesGcmKey key;
+  key.set_version(0);
+  key.set_key_value(subtle::Random::GetRandomBytes(16));
+  KeyData key_data;
+  key_data.set_value(key.SerializeAsString());
+  key_data.set_type_url("type.googleapis.com/google.crypto.tink.AesGcmKey");
+  test::AddKeyData(key_data, key_id, output_prefix_type, key_status_type,
+                   &keyset);
+  return key.key_value();
+}
+
+// Returns the function that relies on `registry` to transform `key_data` into
+// FakePrimitive.
+util::StatusOr<std::function<
+    util::StatusOr<std::unique_ptr<FakePrimitive>>(const KeyData& key_data)>>
+PrimitiveGetter(RegistryImpl& registry) {
+  util::Status status =
+      registry.RegisterKeyTypeManager<AesGcmKey, AesGcmKeyFormat,
+                                      List<FakePrimitive>>(
+          absl::make_unique<FakeKeyTypeManager>(),
+          /*new_key_allowed=*/true);
+  if (!status.ok()) {
+    return status;
+  }
+  return [&registry](const KeyData& key_data) {
+    return registry.GetPrimitive<FakePrimitive>(key_data);
+  };
+}
+
+TEST(KeysetWrapperStoreTest, Add) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  EXPECT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+}
+
+TEST(KeysetWrapperStoreTest, AddNull) {
+  KeysetWrapperStore store;
+  EXPECT_THAT((store.Add<FakePrimitive, FakePrimitive>(nullptr, nullptr)),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+
+  EXPECT_THAT((store.Add<FakePrimitive, FakePrimitive>(
+                  absl::make_unique<FakePrimitiveWrapper>(), nullptr)),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  EXPECT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(nullptr, *primitive_getter)),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(KeysetWrapperStoreTest, AddWrappersForDifferentPrimitivesSucceeds) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+
+  std::function<util::StatusOr<std::unique_ptr<Mac>>(const KeyData& key_data)>
+      primitive_getter_mac = [&registry](const KeyData& key_data) {
+        return registry.GetPrimitive<Mac>(key_data);
+      };
+  EXPECT_THAT((store.Add<Mac, Mac>(absl::make_unique<MacWrapper>(),
+                                   primitive_getter_mac)),
+              IsOk());
+}
+
+TEST(KeysetWrapperStoreTest, AddSameWrapperTwiceSucceeds) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+  EXPECT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+}
+
+TEST(KeysetWrapperStoreTest, AddDifferentWrappersForSamePrimitiveFails) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+  EXPECT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper2>(), *primitive_getter)),
+      StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(KeysetWrapperStoreTest, GetPrimitiveWrapper) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+
+  util::StatusOr<const PrimitiveWrapper<FakePrimitive, FakePrimitive>*>
+      legacy_wrapper = store.GetPrimitiveWrapper<FakePrimitive>();
+  ASSERT_THAT(legacy_wrapper, IsOk());
+
+  Keyset keyset;
+  std::string raw_key = AddAesGcmKeyToKeyset(keyset, 13, OutputPrefixType::TINK,
+                                             KeyStatusType::ENABLED);
+  KeysetInfo keyset_info;
+  keyset_info.add_key_info();
+  keyset_info.mutable_key_info(0)->set_output_prefix_type(
+      OutputPrefixType::TINK);
+  keyset_info.mutable_key_info(0)->set_key_id(1234543);
+  keyset_info.mutable_key_info(0)->set_status(KeyStatusType::ENABLED);
+  keyset_info.set_primary_key_id(1234543);
+  std::unique_ptr<PrimitiveSet<FakePrimitive>> primitive_set(
+      new PrimitiveSet<FakePrimitive>());
+  auto entry = primitive_set->AddPrimitive(
+      absl::make_unique<FakePrimitive>(raw_key), keyset_info.key_info(0));
+  ASSERT_THAT(entry, IsOk());
+  ASSERT_THAT(primitive_set->set_primary(*entry), IsOk());
+
+  util::StatusOr<std::unique_ptr<FakePrimitive>> legacy_aead =
+      (*legacy_wrapper)->Wrap(std::move(primitive_set));
+  ASSERT_THAT(legacy_aead, IsOk());
+  EXPECT_THAT((*legacy_aead)->get(), Eq(raw_key));
+}
+
+TEST(KeysetWrapperStoreTest, GetPrimitiveWrapperNonexistentWrapperFails) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+
+  EXPECT_THAT(store.GetPrimitiveWrapper<Mac>().status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(KeysetWrapperStoreTest, Get) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+
+  util::StatusOr<const KeysetWrapper<FakePrimitive>*> wrapper =
+      store.Get<FakePrimitive>();
+  ASSERT_THAT(wrapper, IsOk());
+
+  Keyset keyset;
+  std::string raw_key = AddAesGcmKeyToKeyset(keyset, 13, OutputPrefixType::TINK,
+                                             KeyStatusType::ENABLED);
+  keyset.set_primary_key_id(13);
+
+  util::StatusOr<std::unique_ptr<FakePrimitive>> aead =
+      (*wrapper)->Wrap(keyset, /*annotations=*/{});
+  ASSERT_THAT(aead, IsOk());
+  EXPECT_THAT((*aead)->get(), Eq(raw_key));
+}
+
+TEST(KeysetWrapperStoreTest, GetNonexistentWrapperFails) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+
+  EXPECT_THAT(store.Get<Mac>().status(), StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(KeysetWrapperStoreTest, IsEmpty) {
+  KeysetWrapperStore store;
+  EXPECT_EQ(store.IsEmpty(), true);
+
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+  EXPECT_THAT(store.IsEmpty(), false);
+}
+
+TEST(KeysetWrapperStoreTest, Move) {
+  RegistryImpl registry;
+  util::StatusOr<std::function<util::StatusOr<std::unique_ptr<FakePrimitive>>(
+      const KeyData& key_data)>>
+      primitive_getter = PrimitiveGetter(registry);
+  ASSERT_THAT(primitive_getter, IsOk());
+
+  KeysetWrapperStore store;
+  ASSERT_THAT(
+      (store.Add<FakePrimitive, FakePrimitive>(
+          absl::make_unique<FakePrimitiveWrapper>(), *primitive_getter)),
+      IsOk());
+
+  util::StatusOr<const KeysetWrapper<FakePrimitive>*> wrapper =
+      store.Get<FakePrimitive>();
+  ASSERT_THAT(wrapper, IsOk());
+
+  KeysetWrapperStore new_store = std::move(store);
+  wrapper = new_store.Get<FakePrimitive>();
+  ASSERT_THAT(wrapper, IsOk());
+
+  Keyset keyset;
+  std::string raw_key = AddAesGcmKeyToKeyset(keyset, 13, OutputPrefixType::TINK,
+                                             KeyStatusType::ENABLED);
+  keyset.set_primary_key_id(13);
+
+  util::StatusOr<std::unique_ptr<FakePrimitive>> aead =
+      (*wrapper)->Wrap(keyset, /*annotations=*/{});
+  ASSERT_THAT(aead, IsOk());
+  EXPECT_THAT((*aead)->get(), Eq(raw_key));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/legacy_proto_key.cc b/cc/internal/legacy_proto_key.cc
new file mode 100644
index 0000000..6a95107
--- /dev/null
+++ b/cc/internal/legacy_proto_key.cc
@@ -0,0 +1,94 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/legacy_proto_key.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::google::crypto::tink::KeyData;
+
+util::Status CheckKeyAccess(KeyData::KeyMaterialType key_material_type,
+                            absl::optional<SecretKeyAccessToken> token) {
+  if (key_material_type == KeyData::SYMMETRIC ||
+      key_material_type == KeyData::ASYMMETRIC_PRIVATE) {
+    if (!token.has_value()) {
+      return util::Status(
+          absl::StatusCode::kPermissionDenied,
+          "Missing secret key access token for legacy proto key.");
+    }
+  }
+  return util::OkStatus();
+}
+
+}  // namespace
+
+bool UnusableLegacyProtoParameters::operator==(const Parameters& other) const {
+  const UnusableLegacyProtoParameters* that =
+      dynamic_cast<const UnusableLegacyProtoParameters*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  return type_url_ == that->type_url_ &&
+         output_prefix_type_ == that->output_prefix_type_;
+}
+
+util::StatusOr<LegacyProtoKey> LegacyProtoKey::Create(
+    ProtoKeySerialization serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  util::Status access_check_status =
+      CheckKeyAccess(serialization.KeyMaterialType(), token);
+  if (!access_check_status.ok()) {
+    return access_check_status;
+  }
+  return LegacyProtoKey(serialization);
+}
+
+bool LegacyProtoKey::operator==(const Key& other) const {
+  const LegacyProtoKey* that = dynamic_cast<const LegacyProtoKey*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  return serialization_.EqualsWithPotentialFalseNegatives(that->serialization_);
+}
+
+util::StatusOr<const ProtoKeySerialization*> LegacyProtoKey::Serialization(
+    absl::optional<SecretKeyAccessToken> token) const {
+  util::Status access_check_status =
+      CheckKeyAccess(serialization_.KeyMaterialType(), token);
+  if (!access_check_status.ok()) {
+    return access_check_status;
+  }
+  return &serialization_;
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/legacy_proto_key.h b/cc/internal/legacy_proto_key.h
new file mode 100644
index 0000000..9372cdf
--- /dev/null
+++ b/cc/internal/legacy_proto_key.h
@@ -0,0 +1,109 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_LEGACY_PROTO_KEY_H_
+#define TINK_INTERNAL_LEGACY_PROTO_KEY_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Parameters returned by `LegacyProtoKey::GetParameters()` that cannot be used
+// to create other LegacyProtoKey instances.
+class UnusableLegacyProtoParameters : public Parameters {
+ public:
+  // Copyable and movable.
+  UnusableLegacyProtoParameters(const UnusableLegacyProtoParameters& other) =
+      default;
+  UnusableLegacyProtoParameters& operator=(
+      const UnusableLegacyProtoParameters& other) = default;
+  UnusableLegacyProtoParameters(UnusableLegacyProtoParameters&& other) =
+      default;
+  UnusableLegacyProtoParameters& operator=(
+      UnusableLegacyProtoParameters&& other) = default;
+
+  explicit UnusableLegacyProtoParameters(
+      absl::string_view type_url,
+      google::crypto::tink::OutputPrefixType output_prefix_type)
+      : type_url_(type_url), output_prefix_type_(output_prefix_type) {}
+
+  bool HasIdRequirement() const override {
+    return output_prefix_type_ != google::crypto::tink::OutputPrefixType::RAW;
+  }
+
+  bool operator==(const Parameters& other) const override;
+
+ private:
+  std::string type_url_;
+  google::crypto::tink::OutputPrefixType output_prefix_type_;
+};
+
+// Key type for legacy proto keys.
+class LegacyProtoKey : public Key {
+ public:
+  // Copyable and movable.
+  LegacyProtoKey(const LegacyProtoKey& other) = default;
+  LegacyProtoKey& operator=(const LegacyProtoKey& other) = default;
+  LegacyProtoKey(LegacyProtoKey&& other) = default;
+  LegacyProtoKey& operator=(LegacyProtoKey&& other) = default;
+
+  // Creates `LegacyProtoKey` object from `serialization`.  Requires `token` if
+  // the key material type is either SYMMETRIC or ASYMMETRIC_PRIVATE.
+  static util::StatusOr<LegacyProtoKey> Create(
+      ProtoKeySerialization serialization,
+      absl::optional<SecretKeyAccessToken> token);
+
+  const Parameters& GetParameters() const override {
+    return unusable_proto_parameters_;
+  }
+
+  absl::optional<int> GetIdRequirement() const override {
+    return serialization_.IdRequirement();
+  }
+
+  bool operator==(const Key& other) const override;
+
+  // Returns `ProtoKeySerialization` pointer for this object.  Requires `token`
+  // if the key material type is either SYMMETRIC or ASYMMETRIC_PRIVATE.
+  util::StatusOr<const ProtoKeySerialization*> Serialization(
+      absl::optional<SecretKeyAccessToken> token) const;
+
+ private:
+  explicit LegacyProtoKey(ProtoKeySerialization serialization)
+      : serialization_(serialization),
+        unusable_proto_parameters_(serialization.TypeUrl(),
+                                   serialization.GetOutputPrefixType()) {}
+
+  ProtoKeySerialization serialization_;
+  UnusableLegacyProtoParameters unusable_proto_parameters_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_LEGACY_PROTO_KEY_H_
diff --git a/cc/internal/legacy_proto_key_test.cc b/cc/internal/legacy_proto_key_test.cc
new file mode 100644
index 0000000..800d630
--- /dev/null
+++ b/cc/internal/legacy_proto_key_test.cc
@@ -0,0 +1,417 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/legacy_proto_key.h"
+
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+class LegacyProtoKeyTest : public ::testing::Test {
+ protected:
+  // Although this is a friend class, this utility function is necessary to
+  // access `ProtoKeySerialization::EqualsWithPotentialFalseNegatives()`
+  // since the test fixtures are subclasses that would not have direct access.
+  bool Equals(ProtoKeySerialization serialization,
+              ProtoKeySerialization other) {
+    return serialization.EqualsWithPotentialFalseNegatives(other);
+  }
+};
+
+TEST_F(LegacyProtoKeyTest, CreateAndSerialization) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  EXPECT_THAT(key->GetIdRequirement(), Eq(12345));
+  EXPECT_THAT(key->GetParameters().HasIdRequirement(), IsTrue());
+  EXPECT_THAT(key->Serialization(InsecureSecretKeyAccess::Get()), IsOk());
+
+  util::StatusOr<const ProtoKeySerialization*> key_serialization =
+      key->Serialization(InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key_serialization.status(), IsOk());
+  EXPECT_THAT(Equals(**key_serialization, *serialization), IsTrue());
+}
+
+TEST_F(LegacyProtoKeyTest, Equals) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> other_key = LegacyProtoKey::Create(
+      *other_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key == *other_key);
+  EXPECT_TRUE(*other_key == *key);
+  EXPECT_FALSE(*key != *other_key);
+  EXPECT_FALSE(*other_key != *key);
+}
+
+TEST_F(LegacyProtoKeyTest, TypeUrlNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("other_type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> other_key = LegacyProtoKey::Create(
+      *other_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST_F(LegacyProtoKeyTest, SerializedKeyNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  RestrictedData other_serialized_key =
+      RestrictedData("other_serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", other_serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> other_key = LegacyProtoKey::Create(
+      *other_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST_F(LegacyProtoKeyTest, KeyMaterialTypeNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key, KeyData::REMOTE,
+                                    OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> other_key = LegacyProtoKey::Create(
+      *other_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST_F(LegacyProtoKeyTest, OutputPrefixTypeNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC,
+                                    OutputPrefixType::CRUNCHY,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> other_key = LegacyProtoKey::Create(
+      *other_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST_F(LegacyProtoKeyTest, IdRequirementNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/6789);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> other_key = LegacyProtoKey::Create(
+      *other_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+using AllOutputPrefixTypesTest =
+    TestWithParam<std::tuple<OutputPrefixType, absl::optional<int>>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOutputPrefixTypesTestSuite, AllOutputPrefixTypesTest,
+    Values(std::make_tuple(OutputPrefixType::RAW, absl::nullopt),
+           std::make_tuple(OutputPrefixType::TINK, 123),
+           std::make_tuple(OutputPrefixType::CRUNCHY, 456),
+           std::make_tuple(OutputPrefixType::LEGACY, 789)));
+
+TEST_P(AllOutputPrefixTypesTest, GetIdRequirement) {
+  OutputPrefixType output_prefix_type;
+  absl::optional<int> id_requirement;
+  std::tie(output_prefix_type, id_requirement) = GetParam();
+
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, output_prefix_type,
+                                    id_requirement);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  EXPECT_THAT(key->GetIdRequirement(), Eq(id_requirement));
+}
+
+using AllKeyMaterialTypesTest = TestWithParam<KeyData::KeyMaterialType>;
+
+INSTANTIATE_TEST_SUITE_P(AllKeyMaterialTypesTestSuite, AllKeyMaterialTypesTest,
+                         Values(KeyData::SYMMETRIC, KeyData::ASYMMETRIC_PRIVATE,
+                                KeyData::ASYMMETRIC_PUBLIC, KeyData::REMOTE));
+
+TEST_P(AllKeyMaterialTypesTest, CreateAndSerializationWithSecretAccessToken) {
+  KeyData::KeyMaterialType key_material_type = GetParam();
+
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    key_material_type, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<const ProtoKeySerialization*> key_serialization =
+      key->Serialization(InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key_serialization.status(), IsOk());
+}
+
+using SecretKeyMaterialTypesTest = TestWithParam<KeyData::KeyMaterialType>;
+
+INSTANTIATE_TEST_SUITE_P(SecretKeyMaterialTypesTestSuite,
+                         SecretKeyMaterialTypesTest,
+                         Values(KeyData::SYMMETRIC,
+                                KeyData::ASYMMETRIC_PRIVATE));
+
+TEST_P(SecretKeyMaterialTypesTest, CreateWithoutSecretAccessToken) {
+  KeyData::KeyMaterialType key_material_type = GetParam();
+
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    key_material_type, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, /*token=*/absl::nullopt);
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kPermissionDenied));
+}
+
+TEST_P(SecretKeyMaterialTypesTest, SerializationWithoutSecretAccessToken) {
+  KeyData::KeyMaterialType key_material_type = GetParam();
+
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    key_material_type, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  // Must use token for key creation.
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<const ProtoKeySerialization*> key_serialization =
+      key->Serialization(/*token=*/absl::nullopt);
+  ASSERT_THAT(key_serialization.status(),
+              StatusIs(absl::StatusCode::kPermissionDenied));
+}
+
+using NonSecretKeyMaterialTypesTest = TestWithParam<KeyData::KeyMaterialType>;
+
+INSTANTIATE_TEST_SUITE_P(NonSecretKeyMaterialTypesTestSuite,
+                         NonSecretKeyMaterialTypesTest,
+                         Values(KeyData::ASYMMETRIC_PUBLIC, KeyData::REMOTE));
+
+TEST_P(NonSecretKeyMaterialTypesTest, CreateWithoutSecretAccessToken) {
+  KeyData::KeyMaterialType key_material_type = GetParam();
+
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    key_material_type, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, /*token=*/absl::nullopt);
+  ASSERT_THAT(key.status(), IsOk());
+}
+
+TEST_P(NonSecretKeyMaterialTypesTest, SerializationWithoutSecretAccessToken) {
+  KeyData::KeyMaterialType key_material_type = GetParam();
+
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    key_material_type, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  // Must use token for key creation.
+  util::StatusOr<LegacyProtoKey> key =
+      LegacyProtoKey::Create(*serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<const ProtoKeySerialization*> key_serialization =
+      key->Serialization(/*token=*/absl::nullopt);
+  ASSERT_THAT(key_serialization.status(), IsOk());
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/legacy_proto_parameters.cc b/cc/internal/legacy_proto_parameters.cc
new file mode 100644
index 0000000..bf512f7
--- /dev/null
+++ b/cc/internal/legacy_proto_parameters.cc
@@ -0,0 +1,37 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/legacy_proto_parameters.h"
+
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/parameters.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+bool LegacyProtoParameters::operator==(const Parameters& other) const {
+  const LegacyProtoParameters* that =
+      dynamic_cast<const LegacyProtoParameters*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  return serialization_.EqualsWithPotentialFalseNegatives(that->serialization_);
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/legacy_proto_parameters.h b/cc/internal/legacy_proto_parameters.h
new file mode 100644
index 0000000..e204c95
--- /dev/null
+++ b/cc/internal/legacy_proto_parameters.h
@@ -0,0 +1,61 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_LEGACY_PROTO_PARAMETERS_H_
+#define TINK_INTERNAL_LEGACY_PROTO_PARAMETERS_H_
+
+#include <utility>
+
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/parameters.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+class LegacyProtoParameters : public Parameters {
+ public:
+  // Copyable and movable.
+  LegacyProtoParameters(const LegacyProtoParameters& other) = default;
+  LegacyProtoParameters& operator=(const LegacyProtoParameters& other) =
+      default;
+  LegacyProtoParameters(LegacyProtoParameters&& other) = default;
+  LegacyProtoParameters& operator=(LegacyProtoParameters&& other) = default;
+
+  explicit LegacyProtoParameters(ProtoParametersSerialization serialization)
+      : serialization_(std::move(serialization)) {}
+
+  bool HasIdRequirement() const override {
+    return serialization_.GetKeyTemplate().output_prefix_type() !=
+           google::crypto::tink::OutputPrefixType::RAW;
+  }
+
+  bool operator==(const Parameters& other) const override;
+
+  const ProtoParametersSerialization& Serialization() const {
+    return serialization_;
+  }
+
+ private:
+  ProtoParametersSerialization serialization_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_LEGACY_PROTO_PARAMETERS_H_
diff --git a/cc/internal/legacy_proto_parameters_test.cc b/cc/internal/legacy_proto_parameters_test.cc
new file mode 100644
index 0000000..ad720c1
--- /dev/null
+++ b/cc/internal/legacy_proto_parameters_test.cc
@@ -0,0 +1,176 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/legacy_proto_parameters.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/parameters.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/test_proto.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::crypto::tink::test::IsOk;
+using ::google::crypto::tink::OutputPrefixType;
+using ::google::crypto::tink::TestProto;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
+class LegacyProtoParametersTest : public ::testing::Test {
+ protected:
+  // Although this is a friend class, this utility function is necessary to
+  // access `ProtoParametersSerialization::EqualsWithPotentialFalseNegatives()`
+  // since the test fixtures are subclasses that would not have direct access.
+  bool Equals(ProtoParametersSerialization serialization,
+              ProtoParametersSerialization other) {
+    return serialization.EqualsWithPotentialFalseNegatives(other);
+  }
+};
+
+TEST_F(LegacyProtoParametersTest, CreateWithIdRequirement) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::TINK,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  LegacyProtoParameters parameters(*serialization);
+
+  EXPECT_THAT(parameters.HasIdRequirement(), IsTrue());
+  EXPECT_THAT(Equals(*serialization, parameters.Serialization()), IsTrue());
+}
+
+TEST_F(LegacyProtoParametersTest, CreateWithoutIdRequirement) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  LegacyProtoParameters parameters(*serialization);
+
+  EXPECT_THAT(parameters.HasIdRequirement(), IsFalse());
+  EXPECT_THAT(Equals(*serialization, parameters.Serialization()), IsTrue());
+}
+
+TEST_F(LegacyProtoParametersTest, Equals) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  LegacyProtoParameters parameters(*serialization);
+  LegacyProtoParameters other_parameters(*other_serialization);
+
+  EXPECT_TRUE(parameters == other_parameters);
+  EXPECT_TRUE(other_parameters == parameters);
+  EXPECT_FALSE(parameters != other_parameters);
+  EXPECT_FALSE(other_parameters != parameters);
+}
+
+TEST_F(LegacyProtoParametersTest, TypeUrlNotEqual) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("other_type_url",
+                                           OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  LegacyProtoParameters parameters(*serialization);
+  LegacyProtoParameters other_parameters(*other_serialization);
+
+  EXPECT_TRUE(parameters != other_parameters);
+  EXPECT_TRUE(other_parameters != parameters);
+  EXPECT_FALSE(parameters == other_parameters);
+  EXPECT_FALSE(other_parameters == parameters);
+}
+
+TEST_F(LegacyProtoParametersTest, OutputPrefixTypeNotEqual) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::TINK,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  LegacyProtoParameters parameters(*serialization);
+  LegacyProtoParameters other_parameters(*other_serialization);
+
+  EXPECT_TRUE(parameters != other_parameters);
+  EXPECT_TRUE(other_parameters != parameters);
+  EXPECT_FALSE(parameters == other_parameters);
+  EXPECT_FALSE(other_parameters == parameters);
+}
+
+TEST_F(LegacyProtoParametersTest, DifferentValueNotEqual) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+  TestProto other_proto;
+  other_proto.set_num(67890);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           other_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  LegacyProtoParameters parameters(*serialization);
+  LegacyProtoParameters other_parameters(*other_serialization);
+
+  EXPECT_TRUE(parameters != other_parameters);
+  EXPECT_TRUE(other_parameters != parameters);
+  EXPECT_FALSE(parameters == other_parameters);
+  EXPECT_FALSE(other_parameters == parameters);
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/md_util.cc b/cc/internal/md_util.cc
index 36d36a0..acca9fe 100644
--- a/cc/internal/md_util.cc
+++ b/cc/internal/md_util.cc
@@ -15,6 +15,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/md_util.h"
 
+#include <stdint.h>
+
 #include <string>
 
 #include "absl/status/status.h"
@@ -26,6 +28,7 @@
 #include "tink/subtle/common_enums.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/util/status.h"
+#include "tink/util/statusor.h"
 
 namespace crypto {
 namespace tink {
diff --git a/cc/internal/md_util_test.cc b/cc/internal/md_util_test.cc
index e40f390..0802070 100644
--- a/cc/internal/md_util_test.cc
+++ b/cc/internal/md_util_test.cc
@@ -15,15 +15,16 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/md_util.h"
 
-#include <cstdint>
 #include <string>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/strings/escaping.h"
-#include "absl/types/span.h"
+#include "absl/strings/string_view.h"
 #include "openssl/evp.h"
 #include "tink/subtle/common_enums.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 
diff --git a/cc/internal/monitoring_util.h b/cc/internal/monitoring_util.h
index dbf5e81..a2b2c13 100644
--- a/cc/internal/monitoring_util.h
+++ b/cc/internal/monitoring_util.h
@@ -22,8 +22,12 @@
 #include "absl/container/flat_hash_map.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/strip.h"
+#include "tink/internal/key_status_util.h"
+#include "tink/key_status.h"
 #include "tink/monitoring/monitoring.h"
 #include "tink/primitive_set.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
 
@@ -31,6 +35,8 @@
 namespace tink {
 namespace internal {
 
+constexpr char kKeyTypePrefix[] = "type.googleapis.com/google.crypto.";
+
 // Constructs a MonitoringKeySetInfo object from a PrimitiveSet `primitive_set`
 // for a given primitive P.
 template <class P>
@@ -48,30 +54,14 @@
   }
   std::vector<MonitoringKeySetInfo::Entry> keyset_info_entries = {};
   for (const auto& entry : primitive_set_entries) {
-    MonitoringKeySetInfo::Entry::KeyStatus key_status;
-    switch (entry->get_status()) {
-      case google::crypto::tink::KeyStatusType::ENABLED: {
-        key_status = MonitoringKeySetInfo::Entry::KeyStatus::kEnabled;
-        break;
-      }
-      case google::crypto::tink::KeyStatusType::DISABLED: {
-        key_status = MonitoringKeySetInfo::Entry::KeyStatus::kDisabled;
-        break;
-      }
-      case google::crypto::tink::KeyStatusType::DESTROYED: {
-        key_status = MonitoringKeySetInfo::Entry::KeyStatus::kDestroyed;
-        break;
-      }
-      default:
-        return util::Status(
-            absl::StatusCode::kInvalidArgument,
-            absl::StrCat("Unknown key status ", entry->get_status()));
-    }
+    util::StatusOr<KeyStatus> key_status =
+        FromKeyStatusType(entry->get_status());
+    if (!key_status.ok()) return key_status.status();
 
-    // TODO(b/222245356): Populate key_format_as_string with the actual key
-    // format when available. For now, we use the key type URL.
     auto keyset_info_entry = MonitoringKeySetInfo::Entry(
-        key_status, entry->get_key_id(), entry->get_key_type_url());
+        *key_status, entry->get_key_id(),
+        absl::StripPrefix(entry->get_key_type_url(), kKeyTypePrefix),
+        OutputPrefixType_Name(entry->get_output_prefix_type()));
     keyset_info_entries.push_back(keyset_info_entry);
   }
   MonitoringKeySetInfo keyset_info(primitive_set.get_annotations(),
diff --git a/cc/internal/monitoring_util_test.cc b/cc/internal/monitoring_util_test.cc
index dd81870..c08b4fa 100644
--- a/cc/internal/monitoring_util_test.cc
+++ b/cc/internal/monitoring_util_test.cc
@@ -15,17 +15,24 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/monitoring_util.h"
 
+#include <stdint.h>
+
 #include <memory>
 #include <string>
-#include <tuple>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/string_view.h"
+#include "tink/key_status.h"
 #include "tink/monitoring/monitoring.h"
 #include "tink/primitive_set.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 #include "proto/tink.pb.h"
 
@@ -89,7 +96,8 @@
 MATCHER_P(MonitoringKeySetInfoEntryEq, other, "") {
   return arg.GetStatus() == other.GetStatus() &&
          arg.GetKeyId() == other.GetKeyId() &&
-         arg.GetParametersAsString() == other.GetParametersAsString();
+         arg.GetKeyPrefix() == other.GetKeyPrefix() &&
+         arg.GetKeyType() == other.GetKeyType();
 }
 
 TEST(MonitoringUtilTest, MonitoringKeySetInfoFromPrimitiveSetValid) {
@@ -134,14 +142,15 @@
               UnorderedElementsAreArray(kAnnotations));
   const std::vector<MonitoringKeySetInfo::Entry> &monitoring_entries =
       monitoring_keyset_info->GetEntries();
-  EXPECT_THAT(monitoring_entries,
-              UnorderedElementsAre(
-                  MonitoringKeySetInfoEntryEq(MonitoringKeySetInfo::Entry(
-                      MonitoringKeySetInfo::Entry::KeyStatus::kEnabled,
-                      /*key_id=*/1, kPrimitive1KeyTyepUrl)),
-                  MonitoringKeySetInfoEntryEq(MonitoringKeySetInfo::Entry(
-                      MonitoringKeySetInfo::Entry::KeyStatus::kEnabled,
-                      /*key_id=*/2, kPrimitive2KeyTypeUrl))));
+  EXPECT_THAT(
+      monitoring_entries,
+      UnorderedElementsAre(
+          MonitoringKeySetInfoEntryEq(MonitoringKeySetInfo::Entry(
+              KeyStatus::kEnabled,
+              /*key_id=*/1, "tink.SomePrimitiveInstance", "TINK")),
+          MonitoringKeySetInfoEntryEq(MonitoringKeySetInfo::Entry(
+              KeyStatus::kEnabled,
+              /*key_id=*/2, "tink.SomeOtherPrimitiveInstance", "TINK"))));
 }
 
 }  // namespace
diff --git a/cc/internal/mutable_serialization_registry.cc b/cc/internal/mutable_serialization_registry.cc
new file mode 100644
index 0000000..250d026
--- /dev/null
+++ b/cc/internal/mutable_serialization_registry.cc
@@ -0,0 +1,121 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/mutable_serialization_registry.h"
+
+#include <memory>
+
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/synchronization/mutex.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/legacy_proto_key.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_registry.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+MutableSerializationRegistry& MutableSerializationRegistry::GlobalInstance() {
+  static MutableSerializationRegistry* instance =
+      new MutableSerializationRegistry();
+  return *instance;
+}
+
+util::Status MutableSerializationRegistry::RegisterParametersParser(
+    ParametersParser* parser) {
+  absl::MutexLock lock(&registry_mutex_);
+  SerializationRegistry::Builder builder(registry_);
+  util::Status status = builder.RegisterParametersParser(parser);
+  if (!status.ok()) return status;
+  registry_ = builder.Build();
+  return util::OkStatus();
+}
+
+util::Status MutableSerializationRegistry::RegisterParametersSerializer(
+    ParametersSerializer* serializer) {
+  absl::MutexLock lock(&registry_mutex_);
+  SerializationRegistry::Builder builder(registry_);
+  util::Status status = builder.RegisterParametersSerializer(serializer);
+  if (!status.ok()) return status;
+  registry_ = builder.Build();
+  return util::OkStatus();
+}
+
+util::Status MutableSerializationRegistry::RegisterKeyParser(
+    KeyParser* parser) {
+  absl::MutexLock lock(&registry_mutex_);
+  SerializationRegistry::Builder builder(registry_);
+  util::Status status = builder.RegisterKeyParser(parser);
+  if (!status.ok()) return status;
+  registry_ = builder.Build();
+  return util::OkStatus();
+}
+
+util::Status MutableSerializationRegistry::RegisterKeySerializer(
+    KeySerializer* serializer) {
+  absl::MutexLock lock(&registry_mutex_);
+  SerializationRegistry::Builder builder(registry_);
+  util::Status status = builder.RegisterKeySerializer(serializer);
+  if (!status.ok()) return status;
+  registry_ = builder.Build();
+  return util::OkStatus();
+}
+
+util::StatusOr<std::unique_ptr<Parameters>>
+MutableSerializationRegistry::ParseParameters(
+    const Serialization& serialization) {
+  absl::MutexLock lock(&registry_mutex_);
+  return registry_.ParseParameters(serialization);
+}
+
+util::StatusOr<std::unique_ptr<Key>> MutableSerializationRegistry::ParseKey(
+    const Serialization& serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  absl::MutexLock lock(&registry_mutex_);
+  return registry_.ParseKey(serialization, token);
+}
+
+util::StatusOr<std::unique_ptr<Key>>
+MutableSerializationRegistry::ParseKeyWithLegacyFallback(
+    const Serialization& serialization, SecretKeyAccessToken token) {
+  util::StatusOr<std::unique_ptr<Key>> key = ParseKey(serialization, token);
+  if (key.status().code() == absl::StatusCode::kNotFound) {
+    const ProtoKeySerialization* proto_serialization =
+        dynamic_cast<const ProtoKeySerialization*>(&serialization);
+    util::StatusOr<LegacyProtoKey> proto_key = internal::LegacyProtoKey::Create(
+        *proto_serialization, InsecureSecretKeyAccess::Get());
+    if (!proto_key.ok()) return proto_key.status();
+    return {absl::make_unique<LegacyProtoKey>(*proto_key)};
+  }
+  if (!key.ok()) return key.status();
+  return key;
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/mutable_serialization_registry.h b/cc/internal/mutable_serialization_registry.h
new file mode 100644
index 0000000..a9914dd
--- /dev/null
+++ b/cc/internal/mutable_serialization_registry.h
@@ -0,0 +1,117 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_MUTABLE_SERIALIZATION_REGISTRY_H_
+#define TINK_INTERNAL_MUTABLE_SERIALIZATION_REGISTRY_H_
+
+#include <memory>
+
+#include "absl/base/thread_annotations.h"
+#include "absl/synchronization/mutex.h"
+#include "absl/types/optional.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_registry.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// This class provides a global, mutable serialization registry by wrapping an
+// instance of an immutable `SerializationRegistry`.  This registry will enable
+// the Tink 2.0 C++ Keyset API in the near term.
+class MutableSerializationRegistry {
+ public:
+  // Returns the global serialization registry.
+  static MutableSerializationRegistry& GlobalInstance();
+
+  // Registers parameters `parser`. Returns an error if a different parameters
+  // parser with the same parser index has already been registered.
+  util::Status RegisterParametersParser(ParametersParser* parser)
+      ABSL_LOCKS_EXCLUDED(registry_mutex_);
+
+  // Registers parameters `serializer`. Returns an error if a different
+  // parameters serializer with the same serializer index has already been
+  // registered.
+  util::Status RegisterParametersSerializer(ParametersSerializer* serializer)
+      ABSL_LOCKS_EXCLUDED(registry_mutex_);
+
+  // Registers key `parser`. Returns an error if a different key parser with the
+  // same parser index has already been registered.
+  util::Status RegisterKeyParser(KeyParser* parser)
+      ABSL_LOCKS_EXCLUDED(registry_mutex_);
+
+  // Registers key `serializer`. Returns an error if a different key serializer
+  // with the same serializer index has already been registered.
+  util::Status RegisterKeySerializer(KeySerializer* serializer)
+      ABSL_LOCKS_EXCLUDED(registry_mutex_);
+
+  // Parses `serialization` into a `Parameters` instance.
+  util::StatusOr<std::unique_ptr<Parameters>> ParseParameters(
+      const Serialization& serialization) ABSL_LOCKS_EXCLUDED(registry_mutex_);
+
+  // Serializes `parameters` into a `Serialization` instance.
+  template <typename SerializationT>
+  util::StatusOr<std::unique_ptr<Serialization>> SerializeParameters(
+      const Parameters& parameters) ABSL_LOCKS_EXCLUDED(registry_mutex_) {
+    absl::MutexLock lock(&registry_mutex_);
+    return registry_.SerializeParameters<SerializationT>(parameters);
+  }
+
+  // Parses `serialization` into a `Key` instance.
+  util::StatusOr<std::unique_ptr<Key>> ParseKey(
+      const Serialization& serialization,
+      absl::optional<SecretKeyAccessToken> token)
+      ABSL_LOCKS_EXCLUDED(registry_mutex_);
+
+  // Similar to `ParseKey` but falls back to legacy proto key serialization if
+  // the corresponding key parser is not found.
+  util::StatusOr<std::unique_ptr<Key>> ParseKeyWithLegacyFallback(
+      const Serialization& serialization, SecretKeyAccessToken token);
+
+  // Serializes `parameters` into a `Serialization` instance.
+  template <typename SerializationT>
+  util::StatusOr<std::unique_ptr<Serialization>> SerializeKey(
+      const Key& key, absl::optional<SecretKeyAccessToken> token)
+      ABSL_LOCKS_EXCLUDED(registry_mutex_) {
+    absl::MutexLock lock(&registry_mutex_);
+    return registry_.SerializeKey<SerializationT>(key, token);
+  }
+
+  // Resets to a new empty registry.
+  void Reset() ABSL_LOCKS_EXCLUDED(registry_mutex_) {
+    absl::MutexLock lock(&registry_mutex_);
+    registry_ = SerializationRegistry();
+  }
+
+ private:
+  mutable absl::Mutex registry_mutex_;
+  SerializationRegistry registry_ ABSL_GUARDED_BY(registry_mutex_);
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_MUTABLE_SERIALIZATION_REGISTRY_H_
diff --git a/cc/internal/mutable_serialization_registry_test.cc b/cc/internal/mutable_serialization_registry_test.cc
new file mode 100644
index 0000000..7bea8b8
--- /dev/null
+++ b/cc/internal/mutable_serialization_registry_test.cc
@@ -0,0 +1,379 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/mutable_serialization_registry.h"
+
+#include <memory>
+#include <string_view>
+#include <typeindex>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_test_util.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
+TEST(MutableSerializationRegistryTest, ParseParameters) {
+  MutableSerializationRegistry registry;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser1(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+  ParametersParserImpl<IdParamsSerialization, IdParams> parser2(kIdTypeUrl,
+                                                                ParseIdParams);
+  ASSERT_THAT(registry.RegisterParametersParser(&parser1), IsOk());
+  ASSERT_THAT(registry.RegisterParametersParser(&parser2), IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> no_id_params =
+      registry.ParseParameters(NoIdSerialization());
+  ASSERT_THAT(no_id_params, IsOk());
+  EXPECT_THAT((*no_id_params)->HasIdRequirement(), IsFalse());
+  EXPECT_THAT(std::type_index(typeid(**no_id_params)),
+              std::type_index(typeid(NoIdParams)));
+
+  util::StatusOr<std::unique_ptr<Parameters>> id_params =
+      registry.ParseParameters(IdParamsSerialization());
+  ASSERT_THAT(id_params, IsOk());
+  EXPECT_THAT((*id_params)->HasIdRequirement(), IsTrue());
+  EXPECT_THAT(std::type_index(typeid(**id_params)),
+              std::type_index(typeid(IdParams)));
+}
+
+TEST(MutableSerializationRegistryTest, ParseParametersWithoutRegistration) {
+  MutableSerializationRegistry registry;
+
+  ASSERT_THAT(registry.ParseParameters(NoIdSerialization()).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(MutableSerializationRegistryTest, RegisterSameParametersParser) {
+  MutableSerializationRegistry registry;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser(kNoIdTypeUrl,
+                                                             ParseNoIdParams);
+
+  EXPECT_THAT(registry.RegisterParametersParser(&parser), IsOk());
+  EXPECT_THAT(registry.RegisterParametersParser(&parser), IsOk());
+}
+
+TEST(MutableSerializationRegistryTest,
+     RegisterDifferentParametersParserWithSameIndex) {
+  MutableSerializationRegistry registry;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser1(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser2(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+
+  EXPECT_THAT(registry.RegisterParametersParser(&parser1), IsOk());
+  EXPECT_THAT(registry.RegisterParametersParser(&parser2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(MutableSerializationRegistryTest, SerializeParameters) {
+  MutableSerializationRegistry registry;
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer1(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  ParametersSerializerImpl<IdParams, IdParamsSerialization> serializer2(
+      kIdTypeUrl, SerializeIdParams);
+  ASSERT_THAT(registry.RegisterParametersSerializer(&serializer1), IsOk());
+  ASSERT_THAT(registry.RegisterParametersSerializer(&serializer2), IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization1 =
+      registry.SerializeParameters<NoIdSerialization>(NoIdParams());
+  ASSERT_THAT(serialization1, IsOk());
+  EXPECT_THAT((*serialization1)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization2 =
+      registry.SerializeParameters<IdParamsSerialization>(IdParams());
+  ASSERT_THAT(serialization2, IsOk());
+  EXPECT_THAT((*serialization2)->ObjectIdentifier(), Eq(kIdTypeUrl));
+}
+
+TEST(MutableSerializationRegistryTest, SerializeParametersWithoutRegistration) {
+  MutableSerializationRegistry registry;
+
+  ASSERT_THAT(
+      registry.SerializeParameters<NoIdSerialization>(NoIdParams()).status(),
+      StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(MutableSerializationRegistryTest, RegisterSameParametersSerializer) {
+  MutableSerializationRegistry registry;
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer(
+      kNoIdTypeUrl, SerializeNoIdParams);
+
+  EXPECT_THAT(registry.RegisterParametersSerializer(&serializer), IsOk());
+  EXPECT_THAT(registry.RegisterParametersSerializer(&serializer), IsOk());
+}
+
+TEST(MutableSerializationRegistryTest,
+     RegisterDifferentParametersSerializerWithSameIndex) {
+  MutableSerializationRegistry registry;
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer1(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer2(
+      kNoIdTypeUrl, SerializeNoIdParams);
+
+  EXPECT_THAT(registry.RegisterParametersSerializer(&serializer1), IsOk());
+  EXPECT_THAT(registry.RegisterParametersSerializer(&serializer2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(MutableSerializationRegistryTest, ParseKey) {
+  MutableSerializationRegistry registry;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser1(kNoIdTypeUrl, ParseNoIdKey);
+  KeyParserImpl<IdKeySerialization, IdKey> parser2(kIdTypeUrl, ParseIdKey);
+  ASSERT_THAT(registry.RegisterKeyParser(&parser1), IsOk());
+  ASSERT_THAT(registry.RegisterKeyParser(&parser2), IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> no_id_key =
+      registry.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(no_id_key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**no_id_key)),
+              std::type_index(typeid(NoIdKey)));
+
+  util::StatusOr<std::unique_ptr<Key>> id_key = registry.ParseKey(
+      IdKeySerialization(/*id=*/123), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(id_key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**id_key)),
+              std::type_index(typeid(IdKey)));
+  EXPECT_THAT((*id_key)->GetIdRequirement(), Eq(123));
+}
+
+TEST(MutableSerializationRegistryTest, ParseKeyNoSecretAccess) {
+  MutableSerializationRegistry registry;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser(kNoIdTypeUrl, ParseNoIdKey);
+  ASSERT_THAT(registry.RegisterKeyParser(&parser), IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> no_id_public_key =
+      registry.ParseKey(NoIdSerialization(), absl::nullopt);
+  ASSERT_THAT(no_id_public_key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**no_id_public_key)),
+              std::type_index(typeid(NoIdKey)));
+}
+
+TEST(MutableSerializationRegistryTest, ParseKeyWithLegacyFallback) {
+  MutableSerializationRegistry registry;
+  KeyParserImpl<IdKeySerialization, IdKey> parser(kIdTypeUrl, ParseIdKey);
+  ASSERT_THAT(registry.RegisterKeyParser(&parser), IsOk());
+
+  // Parse key with registered key parser.
+  util::StatusOr<std::unique_ptr<Key>> id_key =
+      registry.ParseKeyWithLegacyFallback(IdKeySerialization(/*id=*/123),
+                                          InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(id_key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**id_key)),
+              std::type_index(typeid(IdKey)));
+  EXPECT_THAT((*id_key)->GetIdRequirement(), Eq(123));
+
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/456);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  // Fall back to legacy proto key.
+  util::StatusOr<std::unique_ptr<Key>> proto_key =
+      registry.ParseKeyWithLegacyFallback(*serialization,
+                                          InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(proto_key, IsOk());
+  EXPECT_THAT((*proto_key)->GetIdRequirement(), Eq(456));
+}
+
+TEST(MutableSerializationRegistryTest, ParseKeyWithoutRegistration) {
+  MutableSerializationRegistry registry;
+
+  ASSERT_THAT(
+      registry.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get())
+          .status(),
+      StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(MutableSerializationRegistryTest, RegisterSameKeyParser) {
+  MutableSerializationRegistry registry;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser(kNoIdTypeUrl, ParseNoIdKey);
+
+  EXPECT_THAT(registry.RegisterKeyParser(&parser), IsOk());
+  EXPECT_THAT(registry.RegisterKeyParser(&parser), IsOk());
+}
+
+TEST(MutableSerializationRegistryTest,
+     RegisterDifferentKeyParserWithSameIndex) {
+  MutableSerializationRegistry registry;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser1(kNoIdTypeUrl, ParseNoIdKey);
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser2(kNoIdTypeUrl, ParseNoIdKey);
+
+  EXPECT_THAT(registry.RegisterKeyParser(&parser1), IsOk());
+  EXPECT_THAT(registry.RegisterKeyParser(&parser2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(MutableSerializationRegistryTest, SerializeKey) {
+  MutableSerializationRegistry registry;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer1(SerializeNoIdKey);
+  KeySerializerImpl<IdKey, IdKeySerialization> serializer2(SerializeIdKey);
+  ASSERT_THAT(registry.RegisterKeySerializer(&serializer1), IsOk());
+  ASSERT_THAT(registry.RegisterKeySerializer(&serializer2), IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization1 =
+      registry.SerializeKey<NoIdSerialization>(NoIdKey(),
+                                               InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization1, IsOk());
+  EXPECT_THAT((*serialization1)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization2 =
+      registry.SerializeKey<IdKeySerialization>(IdKey(123),
+                                                InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization2, IsOk());
+  EXPECT_THAT((*serialization2)->ObjectIdentifier(), Eq(kIdTypeUrl));
+}
+
+TEST(MutableSerializationRegistryTest, SerializeKeyNoSecretAccess) {
+  MutableSerializationRegistry registry;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer(SerializeNoIdKey);
+  ASSERT_THAT(registry.RegisterKeySerializer(&serializer), IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      registry.SerializeKey<NoIdSerialization>(NoIdKey(), absl::nullopt);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+TEST(MutableSerializationRegistryTest, SerializeKeyWithoutRegistration) {
+  MutableSerializationRegistry registry;
+
+  ASSERT_THAT(registry
+                  .SerializeKey<NoIdSerialization>(
+                      NoIdKey(), InsecureSecretKeyAccess::Get())
+                  .status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(MutableSerializationRegistryTest, RegisterSameKeySerializer) {
+  MutableSerializationRegistry registry;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer(SerializeNoIdKey);
+
+  EXPECT_THAT(registry.RegisterKeySerializer(&serializer), IsOk());
+  EXPECT_THAT(registry.RegisterKeySerializer(&serializer), IsOk());
+}
+
+TEST(MutableSerializationRegistryTest,
+     RegisterDifferentKeySerializerWithSameIndex) {
+  MutableSerializationRegistry registry;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer1(SerializeNoIdKey);
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer2(SerializeNoIdKey);
+
+  EXPECT_THAT(registry.RegisterKeySerializer(&serializer1), IsOk());
+  EXPECT_THAT(registry.RegisterKeySerializer(&serializer2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(MutableSerializationRegistryTest, Reset) {
+  MutableSerializationRegistry registry;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> params_parser(
+      kNoIdTypeUrl, ParseNoIdParams);
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> params_serializer(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  KeyParserImpl<NoIdSerialization, NoIdKey> key_parser(kNoIdTypeUrl,
+                                                       ParseNoIdKey);
+  KeySerializerImpl<NoIdKey, NoIdSerialization> key_serializer(
+      SerializeNoIdKey);
+
+  ASSERT_THAT(registry.RegisterParametersParser(&params_parser), IsOk());
+  ASSERT_THAT(registry.RegisterParametersSerializer(&params_serializer),
+              IsOk());
+  ASSERT_THAT(registry.RegisterKeyParser(&key_parser), IsOk());
+  ASSERT_THAT(registry.RegisterKeySerializer(&key_serializer), IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      registry.ParseParameters(NoIdSerialization());
+  ASSERT_THAT(params, IsOk());
+  util::StatusOr<std::unique_ptr<Serialization>> serialization1 =
+      registry.SerializeParameters<NoIdSerialization>(NoIdParams());
+  ASSERT_THAT(serialization1, IsOk());
+  util::StatusOr<std::unique_ptr<Key>> key =
+      registry.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  util::StatusOr<std::unique_ptr<Serialization>> serialization2 =
+      registry.SerializeKey<NoIdSerialization>(NoIdKey(),
+                                               InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization2, IsOk());
+
+  registry.Reset();
+
+  ASSERT_THAT(registry.ParseParameters(NoIdSerialization()).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+  ASSERT_THAT(
+      registry.SerializeParameters<NoIdSerialization>(NoIdParams()).status(),
+      StatusIs(absl::StatusCode::kNotFound));
+  ASSERT_THAT(
+      registry.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get())
+          .status(),
+      StatusIs(absl::StatusCode::kNotFound));
+  ASSERT_THAT(registry
+                  .SerializeKey<NoIdSerialization>(
+                      NoIdKey(), InsecureSecretKeyAccess::Get())
+                  .status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(MutableSerializationRegistryTest, GlobalInstance) {
+  MutableSerializationRegistry::GlobalInstance().Reset();
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser(kNoIdTypeUrl,
+                                                             ParseNoIdParams);
+  ASSERT_THAT(
+      MutableSerializationRegistry::GlobalInstance().RegisterParametersParser(
+          &parser),
+      IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          NoIdSerialization());
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), IsFalse());
+  EXPECT_THAT(std::type_index(typeid(**params)),
+              std::type_index(typeid(NoIdParams)));
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/parameters_parser.h b/cc/internal/parameters_parser.h
new file mode 100644
index 0000000..81c8eb0
--- /dev/null
+++ b/cc/internal/parameters_parser.h
@@ -0,0 +1,114 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_PARAMETERS_PARSER_H_
+#define TINK_INTERNAL_PARAMETERS_PARSER_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <typeindex>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/internal/parser_index.h"
+#include "tink/internal/serialization.h"
+#include "tink/parameters.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Non-template base class that can be used with internal registry map.
+class ParametersParser {
+ public:
+  // Parses `serialization` into a parameters object.
+  //
+  // This function is usually called on a `Serialization` subclass matching the
+  // value returned by `ObjectIdentifier()`. However, implementations should
+  // verify that this is the case.
+  virtual util::StatusOr<std::unique_ptr<Parameters>> ParseParameters(
+      const Serialization& serialization) const = 0;
+
+  // Returns the object identifier for `SerializationT`, which is only valid
+  // for the lifetime of this object.
+  //
+  // The object identifier is a unique identifier per registry for this object
+  // (in the standard proto serialization, it is the type URL). In other words,
+  // when registering a `ParametersParser`, the registry will invoke this to get
+  // the handled object identifier. In order to parse an object of
+  // `SerializationT`, the registry will then obtain the object identifier of
+  // this serialization object, and call the parser corresponding to this
+  // object.
+  virtual absl::string_view ObjectIdentifier() const = 0;
+
+  // Returns an index that can be used to look up the `ParametersParser`
+  // object registered for the `ParametersT` type in a registry.
+  virtual ParserIndex Index() const = 0;
+
+  virtual ~ParametersParser() = default;
+};
+
+// Parses `SerializationT` objects into `ParametersT` objects.
+template <typename SerializationT, typename ParametersT>
+class ParametersParserImpl : public ParametersParser {
+ public:
+  explicit ParametersParserImpl(
+      absl::string_view object_identifier,
+      const std::function<util::StatusOr<ParametersT>(SerializationT)>&
+          function)
+      : object_identifier_(object_identifier), function_(function) {}
+
+  util::StatusOr<std::unique_ptr<Parameters>> ParseParameters(
+      const Serialization& serialization) const override {
+    if (serialization.ObjectIdentifier() != object_identifier_) {
+      return util::Status(
+          absl::StatusCode::kInvalidArgument,
+          "Invalid object identifier for this parameters parser.");
+    }
+    const SerializationT* st =
+        dynamic_cast<const SerializationT*>(&serialization);
+    if (st == nullptr) {
+      return util::Status(
+          absl::StatusCode::kInvalidArgument,
+          "Invalid serialization type for this parameters parser.");
+    }
+    util::StatusOr<ParametersT> parameters = function_(*st);
+    if (!parameters.ok()) return parameters.status();
+    return {absl::make_unique<ParametersT>(std::move(*parameters))};
+  }
+
+  absl::string_view ObjectIdentifier() const override {
+    return object_identifier_;
+  }
+
+  ParserIndex Index() const override {
+    return ParserIndex::Create<SerializationT>(object_identifier_);
+  }
+
+ private:
+  std::string object_identifier_;
+  std::function<util::StatusOr<ParametersT>(SerializationT)> function_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_PARAMETERS_PARSER_H_
diff --git a/cc/internal/parameters_parser_test.cc b/cc/internal/parameters_parser_test.cc
new file mode 100644
index 0000000..9480b91
--- /dev/null
+++ b/cc/internal/parameters_parser_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/parameters_parser.h"
+
+#include <memory>
+#include <string_view>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "tink/internal/parser_index.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_test_util.h"
+#include "tink/parameters.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Eq;
+using ::testing::IsFalse;
+
+TEST(ParametersParserTest, Create) {
+  std::unique_ptr<ParametersParser> parser =
+      absl::make_unique<ParametersParserImpl<NoIdSerialization, NoIdParams>>(
+          kNoIdTypeUrl, ParseNoIdParams);
+
+  EXPECT_THAT(parser->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+  EXPECT_THAT(parser->Index(),
+              Eq(ParserIndex::Create<NoIdSerialization>(kNoIdTypeUrl)));
+}
+
+TEST(ParametersParserTest, ParseParameters) {
+  std::unique_ptr<ParametersParser> parser =
+      absl::make_unique<ParametersParserImpl<NoIdSerialization, NoIdParams>>(
+          kNoIdTypeUrl, ParseNoIdParams);
+
+  NoIdSerialization serialization;
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      parser->ParseParameters(serialization);
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), IsFalse());
+}
+
+TEST(ParametersParserTest, ParseParametersWithInvalidSerializationType) {
+  std::unique_ptr<ParametersParser> parser =
+      absl::make_unique<ParametersParserImpl<NoIdSerialization, NoIdParams>>(
+          kNoIdTypeUrl, ParseNoIdParams);
+
+  IdParamsSerialization serialization;
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      parser->ParseParameters(serialization);
+  ASSERT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(ParametersParserTest, ParseParametersWithInvalidObjectIdentifier) {
+  std::unique_ptr<ParametersParser> parser =
+      absl::make_unique<ParametersParserImpl<NoIdSerialization, NoIdParams>>(
+          "mismatched_type_url", ParseNoIdParams);
+
+  IdParamsSerialization serialization;
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      parser->ParseParameters(serialization);
+  ASSERT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/parameters_serializer.h b/cc/internal/parameters_serializer.h
new file mode 100644
index 0000000..a26c803
--- /dev/null
+++ b/cc/internal/parameters_serializer.h
@@ -0,0 +1,104 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_PARAMETERS_SERIALIZER_H_
+#define TINK_INTERNAL_PARAMETERS_SERIALIZER_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <typeindex>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serializer_index.h"
+#include "tink/parameters.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Non-template base class that can be used with internal registry map.
+class ParametersSerializer {
+ public:
+  // Returns the serialization of `parameters`.
+  virtual util::StatusOr<std::unique_ptr<Serialization>> SerializeParameters(
+      const Parameters& parameters) const = 0;
+
+  // Returns the object identifier for this serialization, which is only valid
+  // for the lifetime of this object.
+  //
+  // The object identifier is a unique identifier per registry for this object
+  // (in the standard proto serialization, it is the type URL). In other words,
+  // when registering a `ParametersSerializer`, the registry will invoke this to
+  // get the handled object identifier. In order to serialize an object of
+  // `ParametersT`, the registry will then obtain the object identifier of
+  // this serialization object, and call the serializer corresponding to this
+  // object.
+  virtual absl::string_view ObjectIdentifier() const = 0;
+
+  // Returns an index that can be used to look up the `ParametersSerializer`
+  // object registered for the `ParametersT` type in a registry.
+  virtual SerializerIndex Index() const = 0;
+
+  virtual ~ParametersSerializer() = default;
+};
+
+// Serializes `ParametersT` objects into `SerializationT` objects.
+template <typename ParametersT, typename SerializationT>
+class ParametersSerializerImpl : public ParametersSerializer {
+ public:
+  explicit ParametersSerializerImpl(
+      absl::string_view object_identifier,
+      const std::function<util::StatusOr<SerializationT>(ParametersT)>&
+          function)
+      : object_identifier_(object_identifier), function_(function) {}
+
+  util::StatusOr<std::unique_ptr<Serialization>> SerializeParameters(
+      const Parameters& parameters) const override {
+    const ParametersT* pt = dynamic_cast<const ParametersT*>(&parameters);
+    if (pt == nullptr) {
+      return util::Status(
+          absl::StatusCode::kInvalidArgument,
+          "Invalid parameters type for this parameters serializer.");
+    }
+    util::StatusOr<SerializationT> serialization = function_(*pt);
+    if (!serialization.ok()) return serialization.status();
+    return {absl::make_unique<SerializationT>(std::move(*serialization))};
+  }
+
+  absl::string_view ObjectIdentifier() const override {
+    return object_identifier_;
+  }
+
+  SerializerIndex Index() const override {
+    return SerializerIndex::Create<ParametersT, SerializationT>();
+  }
+
+ private:
+  std::string object_identifier_;
+  std::function<util::StatusOr<SerializationT>(ParametersT)> function_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_PARAMETERS_SERIALIZER_H_
diff --git a/cc/internal/parameters_serializer_test.cc b/cc/internal/parameters_serializer_test.cc
new file mode 100644
index 0000000..0c76e08
--- /dev/null
+++ b/cc/internal/parameters_serializer_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/parameters_serializer.h"
+
+#include <memory>
+#include <string_view>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_test_util.h"
+#include "tink/internal/serializer_index.h"
+#include "tink/parameters.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Eq;
+
+TEST(ParametersSerializerTest, Create) {
+  std::unique_ptr<ParametersSerializer> serializer = absl::make_unique<
+      ParametersSerializerImpl<NoIdParams, NoIdSerialization>>(
+      kNoIdTypeUrl, SerializeNoIdParams);
+
+  EXPECT_THAT(serializer->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+  EXPECT_THAT(serializer->Index(),
+              Eq(SerializerIndex::Create<NoIdParams, NoIdSerialization>()));
+}
+
+TEST(ParametersSerializerTest, SerializeParameters) {
+  std::unique_ptr<ParametersSerializer> serializer = absl::make_unique<
+      ParametersSerializerImpl<NoIdParams, NoIdSerialization>>(
+      kNoIdTypeUrl, SerializeNoIdParams);
+
+  NoIdParams parameters;
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      serializer->SerializeParameters(parameters);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+TEST(ParametersSerializerTest, SerializeParametersWithInvalidParametersType) {
+  std::unique_ptr<ParametersSerializer> serializer = absl::make_unique<
+      ParametersSerializerImpl<NoIdParams, NoIdSerialization>>(
+      kNoIdTypeUrl, SerializeNoIdParams);
+
+  IdParams parameters;
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      serializer->SerializeParameters(parameters);
+  ASSERT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/parser_index.h b/cc/internal/parser_index.h
new file mode 100644
index 0000000..2ddb52d
--- /dev/null
+++ b/cc/internal/parser_index.h
@@ -0,0 +1,71 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_PARSER_INDEX_H_
+#define TINK_INTERNAL_PARSER_INDEX_H_
+
+#include <string>
+#include <typeindex>
+
+#include "absl/strings/string_view.h"
+#include "tink/internal/serialization.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+class ParserIndex {
+ public:
+  // Create registry lookup key for a `SerializationT` type object with
+  // `object_identifier`. Useful for key and parameters parsers.
+  template <typename SerializationT>
+  static ParserIndex Create(absl::string_view object_identifier) {
+    return ParserIndex(std::type_index(typeid(SerializationT)),
+                       object_identifier);
+  }
+
+  // Create registry lookup key for `serialization`. Useful for the
+  // serialization registry.
+  static ParserIndex Create(const Serialization& serialization) {
+    return ParserIndex(std::type_index(typeid(serialization)),
+                       serialization.ObjectIdentifier());
+  }
+
+  // Returns true if serialization type index and object identifier match.
+  bool operator==(const ParserIndex& other) const {
+    return index_ == other.index_ &&
+           object_identifier_ == other.object_identifier_;
+  }
+
+  // Required function to make `ParserIndex` hashable for Abseil hash maps.
+  template <typename H>
+  friend H AbslHashValue(H h, const ParserIndex& index) {
+    return H::combine(std::move(h), index.index_, index.object_identifier_);
+  }
+
+ private:
+  ParserIndex(std::type_index index, absl::string_view object_identifier)
+      : index_(index), object_identifier_(object_identifier) {}
+
+  std::type_index index_;
+  std::string object_identifier_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_PARSER_INDEX_H_
diff --git a/cc/internal/parser_index_test.cc b/cc/internal/parser_index_test.cc
new file mode 100644
index 0000000..583b0ff
--- /dev/null
+++ b/cc/internal/parser_index_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/parser_index.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
+#include "tink/internal/serialization.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::testing::Eq;
+using ::testing::Not;
+
+class ExampleSerialization : public Serialization {
+ public:
+  explicit ExampleSerialization(absl::string_view object_identifier)
+      : object_identifier_(object_identifier) {}
+
+  absl::string_view ObjectIdentifier() const override {
+    return object_identifier_;
+  }
+
+ protected:
+  std::string object_identifier_;
+};
+
+class DifferentSerialization : public ExampleSerialization {
+ public:
+  explicit DifferentSerialization(absl::string_view object_identifier)
+      : ExampleSerialization(object_identifier) {}
+};
+
+TEST(ParserIndex, CreateEquivalent) {
+  ASSERT_THAT(ParserIndex::Create<ExampleSerialization>("id"),
+              Eq(ParserIndex::Create<ExampleSerialization>("id")));
+  ASSERT_THAT(ParserIndex::Create<ExampleSerialization>("id"),
+              Eq(ParserIndex::Create(ExampleSerialization("id"))));
+  ASSERT_THAT(ParserIndex::Create(ExampleSerialization("id")),
+              Eq(ParserIndex::Create(ExampleSerialization("id"))));
+}
+
+TEST(ParserIndex, CreateWithDifferentObjectIdentifier) {
+  ASSERT_THAT(
+      ParserIndex::Create<ExampleSerialization>("id"),
+      Not(Eq(ParserIndex::Create<ExampleSerialization>("different id"))));
+  ASSERT_THAT(
+      ParserIndex::Create(ExampleSerialization("id")),
+      Not(Eq(ParserIndex::Create(ExampleSerialization("different id")))));
+}
+
+TEST(ParserIndex, CreateWithDifferentSerializationType) {
+  ASSERT_THAT(ParserIndex::Create<ExampleSerialization>("id"),
+              Not(Eq(ParserIndex::Create<DifferentSerialization>("id"))));
+  ASSERT_THAT(ParserIndex::Create(ExampleSerialization("id")),
+              Not(Eq(ParserIndex::Create(DifferentSerialization("id")))));
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/proto_key_serialization.cc b/cc/internal/proto_key_serialization.cc
new file mode 100644
index 0000000..f92eb02
--- /dev/null
+++ b/cc/internal/proto_key_serialization.cc
@@ -0,0 +1,76 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/proto_key_serialization.h"
+
+#include <string>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/internal/util.h"
+#include "tink/restricted_data.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+
+util::StatusOr<ProtoKeySerialization> ProtoKeySerialization::Create(
+    absl::string_view type_url, RestrictedData serialized_key,
+    KeyData::KeyMaterialType key_material_type,
+    OutputPrefixType output_prefix_type, absl::optional<int> id_requirement) {
+  if (!IsPrintableAscii(type_url)) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Non-printable ASCII character in type URL.");
+  }
+  if (output_prefix_type == OutputPrefixType::RAW &&
+      id_requirement.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Keys with a RAW output prefix type should not have an "
+                        "ID requirement.");
+  }
+  if (output_prefix_type != OutputPrefixType::RAW &&
+      !id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Keys without a RAW output prefix type should have an ID requirement.");
+  }
+  return ProtoKeySerialization(type_url, type_url, std::move(serialized_key),
+                               key_material_type, output_prefix_type,
+                               id_requirement);
+}
+
+bool ProtoKeySerialization::EqualsWithPotentialFalseNegatives(
+    const ProtoKeySerialization& other) const {
+  if (type_url_ != other.type_url_) return false;
+  if (object_identifier_ != other.object_identifier_) return false;
+  if (key_material_type_ != other.key_material_type_) return false;
+  if (output_prefix_type_ != other.output_prefix_type_) return false;
+  if (id_requirement_ != other.id_requirement_) return false;
+  // RestrictedData::operator== is a constant-time comparison.
+  return serialized_key_ == other.serialized_key_;
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/proto_key_serialization.h b/cc/internal/proto_key_serialization.h
new file mode 100644
index 0000000..7c3972a
--- /dev/null
+++ b/cc/internal/proto_key_serialization.h
@@ -0,0 +1,110 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_PROTO_KEY_SERIALIZATION_H_
+#define TINK_INTERNAL_PROTO_KEY_SERIALIZATION_H_
+
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/internal/serialization.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Represents a `Key` object serialized with binary protocol buffer
+// serialization.
+class ProtoKeySerialization : public Serialization {
+ public:
+  // Copyable and movable.
+  ProtoKeySerialization(const ProtoKeySerialization& other) = default;
+  ProtoKeySerialization& operator=(const ProtoKeySerialization& other) =
+      default;
+  ProtoKeySerialization(ProtoKeySerialization&& other) = default;
+  ProtoKeySerialization& operator=(ProtoKeySerialization&& other) = default;
+
+  // Creates a `ProtoKeySerialization` object from individual components.
+  static util::StatusOr<ProtoKeySerialization> Create(
+      absl::string_view type_url, RestrictedData serialized_key,
+      google::crypto::tink::KeyData::KeyMaterialType key_material_type,
+      google::crypto::tink::OutputPrefixType output_prefix_type,
+      absl::optional<int> id_requirement);
+
+  // Returned value is only valid for the lifetime of this object.
+  absl::string_view TypeUrl() const { return type_url_; }
+
+  // Returned value is only valid for the lifetime of this object.
+  absl::string_view ObjectIdentifier() const override {
+    return object_identifier_;
+  }
+
+  // Returned value is only valid for the lifetime of this object.
+  RestrictedData SerializedKeyProto() const { return serialized_key_; }
+
+  google::crypto::tink::KeyData::KeyMaterialType KeyMaterialType() const {
+    return key_material_type_;
+  }
+
+  google::crypto::tink::OutputPrefixType GetOutputPrefixType() const {
+    return output_prefix_type_;
+  }
+
+  absl::optional<int> IdRequirement() const { return id_requirement_; }
+
+ private:
+  friend class ProtoKeySerializationTest;
+  friend class LegacyProtoKey;
+  friend class LegacyProtoKeyTest;
+
+  ProtoKeySerialization(
+      absl::string_view type_url, absl::string_view object_identifier,
+      RestrictedData serialized_key,
+      google::crypto::tink::KeyData::KeyMaterialType key_material_type,
+      google::crypto::tink::OutputPrefixType output_prefix_type,
+      absl::optional<int> id_requirement)
+      : type_url_(type_url),
+        object_identifier_(object_identifier),
+        serialized_key_(std::move(serialized_key)),
+        key_material_type_(key_material_type),
+        output_prefix_type_(output_prefix_type),
+        id_requirement_(id_requirement) {}
+
+  // Returns `true` if this `ProtoKeySerialization` object is equal to
+  // `other` (with the possibility of false negatives due to lack of
+  // determinism during serialization).  Should only be used temporarily by the
+  // `LegacyKeyParameters` class.
+  bool EqualsWithPotentialFalseNegatives(
+      const ProtoKeySerialization& other) const;
+
+  std::string type_url_;
+  std::string object_identifier_;
+  RestrictedData serialized_key_;
+  google::crypto::tink::KeyData::KeyMaterialType key_material_type_;
+  google::crypto::tink::OutputPrefixType output_prefix_type_;
+  absl::optional<int> id_requirement_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_PROTO_KEY_SERIALIZATION_H_
diff --git a/cc/internal/proto_key_serialization_test.cc b/cc/internal/proto_key_serialization_test.cc
new file mode 100644
index 0000000..89eff96
--- /dev/null
+++ b/cc/internal/proto_key_serialization_test.cc
@@ -0,0 +1,213 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/proto_key_serialization.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
+class ProtoKeySerializationTest : public ::testing::Test {
+ protected:
+  bool Equals(ProtoKeySerialization serialization,
+              ProtoKeySerialization other) {
+    return serialization.EqualsWithPotentialFalseNegatives(other);
+  }
+};
+
+TEST_F(ProtoKeySerializationTest, CreateWithIdRequirement) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  EXPECT_THAT(serialization->TypeUrl(), Eq("type_url"));
+  EXPECT_THAT(serialization->SerializedKeyProto(), Eq(serialized_key));
+  EXPECT_THAT(serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC));
+  EXPECT_THAT(serialization->GetOutputPrefixType(), Eq(OutputPrefixType::TINK));
+  EXPECT_THAT(serialization->IdRequirement(), Eq(12345));
+  EXPECT_THAT(serialization->ObjectIdentifier(), Eq("type_url"));
+}
+
+TEST_F(ProtoKeySerializationTest, CreateWithoutIdRequirement) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::RAW,
+                                    /*id_requirement=*/absl::nullopt);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  EXPECT_THAT(serialization->TypeUrl(), Eq("type_url"));
+  EXPECT_THAT(serialization->SerializedKeyProto(), Eq(serialized_key));
+  EXPECT_THAT(serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC));
+  EXPECT_THAT(serialization->GetOutputPrefixType(), Eq(OutputPrefixType::RAW));
+  EXPECT_THAT(serialization->IdRequirement(), Eq(absl::nullopt));
+  EXPECT_THAT(serialization->ObjectIdentifier(), Eq("type_url"));
+}
+
+TEST_F(ProtoKeySerializationTest, OutputPrefixIncompatibleWithIdRequirement) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> tink_without_id =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/absl::nullopt);
+  ASSERT_THAT(tink_without_id.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+
+  util::StatusOr<ProtoKeySerialization> raw_with_id =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::RAW,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(raw_with_id.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(ProtoKeySerializationTest, Equals) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsTrue());
+}
+
+TEST_F(ProtoKeySerializationTest, TypeUrlAndObjectIdentifierNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("different_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+TEST_F(ProtoKeySerializationTest, SerializedKeyNotEqual) {
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create(
+          "type_url",
+          RestrictedData("serialized_key", InsecureSecretKeyAccess::Get()),
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create(
+          "type_url",
+          RestrictedData("different_key", InsecureSecretKeyAccess::Get()),
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+TEST_F(ProtoKeySerializationTest, KeyMaterialTypeNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key, KeyData::REMOTE,
+                                    OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+TEST_F(ProtoKeySerializationTest, OutputPrefixTypeNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC,
+                                    OutputPrefixType::CRUNCHY,
+                                    /*id_requirement=*/12345);
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+TEST_F(ProtoKeySerializationTest, IdRequirementNotEqual) {
+  RestrictedData serialized_key =
+      RestrictedData("serialized_key", InsecureSecretKeyAccess::Get());
+  util::StatusOr<ProtoKeySerialization> serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/12345);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoKeySerialization> other_serialization =
+      ProtoKeySerialization::Create("type_url", serialized_key,
+                                    KeyData::SYMMETRIC, OutputPrefixType::TINK,
+                                    /*id_requirement=*/6789);
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/proto_parameters_serialization.cc b/cc/internal/proto_parameters_serialization.cc
new file mode 100644
index 0000000..f3bd4bd
--- /dev/null
+++ b/cc/internal/proto_parameters_serialization.cc
@@ -0,0 +1,84 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/proto_parameters_serialization.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "tink/internal/util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+
+util::StatusOr<ProtoParametersSerialization>
+ProtoParametersSerialization::Create(absl::string_view type_url,
+                                     OutputPrefixType output_prefix_type,
+                                     absl::string_view serialized_proto) {
+  if (!IsPrintableAscii(type_url)) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Non-printable ASCII character in type URL.");
+  }
+  KeyTemplate key_template;
+  key_template.set_type_url(std::string(type_url));
+  key_template.set_output_prefix_type(output_prefix_type);
+  key_template.set_value(std::string(serialized_proto));
+  return ProtoParametersSerialization(key_template);
+}
+
+util::StatusOr<ProtoParametersSerialization>
+ProtoParametersSerialization::Create(KeyTemplate key_template) {
+  if (!IsPrintableAscii(key_template.type_url())) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Non-printable ASCII character in type URL.");
+  }
+  return ProtoParametersSerialization(key_template);
+}
+
+bool ProtoParametersSerialization::EqualsWithPotentialFalseNegatives(
+    const ProtoParametersSerialization& other) const {
+  const ProtoParametersSerialization* that =
+      dynamic_cast<const ProtoParametersSerialization*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (key_template_.type_url() != that->key_template_.type_url()) {
+    return false;
+  }
+  if (key_template_.output_prefix_type() !=
+      that->key_template_.output_prefix_type()) {
+    return false;
+  }
+  if (key_template_.value() != that->key_template_.value()) {
+    return false;
+  }
+  if (object_identifier_ != that->object_identifier_) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/proto_parameters_serialization.h b/cc/internal/proto_parameters_serialization.h
new file mode 100644
index 0000000..cb32d8f
--- /dev/null
+++ b/cc/internal/proto_parameters_serialization.h
@@ -0,0 +1,89 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_PROTO_PARAMETERS_SERIALIZATION_H_
+#define TINK_INTERNAL_PROTO_PARAMETERS_SERIALIZATION_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "tink/internal/serialization.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// Represents a `Parameters` object serialized with binary protocol buffer
+// serialization.
+class ProtoParametersSerialization : public Serialization {
+ public:
+  // Copyable and movable.
+  ProtoParametersSerialization(const ProtoParametersSerialization& other) =
+      default;
+  ProtoParametersSerialization& operator=(
+      const ProtoParametersSerialization& other) = default;
+  ProtoParametersSerialization(ProtoParametersSerialization&& other) = default;
+  ProtoParametersSerialization& operator=(
+      ProtoParametersSerialization&& other) = default;
+
+  // Creates a `ProtoParametersSerialization` object from individual components.
+  static util::StatusOr<ProtoParametersSerialization> Create(
+      absl::string_view type_url,
+      google::crypto::tink::OutputPrefixType output_prefix_type,
+      absl::string_view serialized_proto);
+
+  // Creates a `ProtoParametersSerialization` object from a key template.
+  static util::StatusOr<ProtoParametersSerialization> Create(
+      google::crypto::tink::KeyTemplate key_template);
+
+  const google::crypto::tink::KeyTemplate& GetKeyTemplate() const {
+    return key_template_;
+  }
+
+  absl::string_view ObjectIdentifier() const override {
+    return object_identifier_;
+  }
+
+ private:
+  // The following friend classes require access to
+  // `EqualsWithPotentialFalseNegatives()`.
+  friend class ProtoParametersSerializationTest;
+  friend class LegacyProtoParameters;
+  friend class LegacyProtoParametersTest;
+
+  explicit ProtoParametersSerialization(
+      google::crypto::tink::KeyTemplate key_template)
+      : key_template_(key_template),
+        object_identifier_(key_template.type_url()) {}
+
+  // Returns `true` if this `ProtoParametersSerialization` object is equal to
+  // `other` (with the possibility of false negatives due to lack of
+  // determinism during serialization).  Should only be used temporarily by the
+  // `LegacyProtoParameters` class.
+  bool EqualsWithPotentialFalseNegatives(
+      const ProtoParametersSerialization& other) const;
+
+  google::crypto::tink::KeyTemplate key_template_;
+  std::string object_identifier_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_PROTO_PARAMETERS_SERIALIZATION_H_
diff --git a/cc/internal/proto_parameters_serialization_test.cc b/cc/internal/proto_parameters_serialization_test.cc
new file mode 100644
index 0000000..1b13411
--- /dev/null
+++ b/cc/internal/proto_parameters_serialization_test.cc
@@ -0,0 +1,162 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/proto_parameters_serialization.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/test_proto.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::crypto::tink::test::IsOk;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+using ::google::crypto::tink::TestProto;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
+class ProtoParametersSerializationTest : public ::testing::Test {
+ protected:
+  bool Equals(ProtoParametersSerialization serialization,
+              ProtoParametersSerialization other) {
+    return serialization.EqualsWithPotentialFalseNegatives(other);
+  }
+};
+
+TEST_F(ProtoParametersSerializationTest, CreateFromIndividualComponents) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  EXPECT_THAT(serialization->ObjectIdentifier(), "type_url");
+  EXPECT_THAT(serialization->GetKeyTemplate().type_url(), "type_url");
+  EXPECT_THAT(serialization->GetKeyTemplate().output_prefix_type(),
+              OutputPrefixType::RAW);
+  EXPECT_THAT(serialization->GetKeyTemplate().value(),
+              test_proto.SerializeAsString());
+  TestProto parsed_proto;
+  parsed_proto.ParseFromString(serialization->GetKeyTemplate().value());
+  EXPECT_THAT(parsed_proto.num(), Eq(12345));
+}
+
+TEST_F(ProtoParametersSerializationTest, CreateFromKeyTemplate) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+  KeyTemplate key_template;
+  key_template.set_value(test_proto.SerializeAsString());
+  key_template.set_output_prefix_type(OutputPrefixType::TINK);
+  key_template.set_type_url("type_url");
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create(key_template);
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  EXPECT_THAT(serialization->ObjectIdentifier(), "type_url");
+  EXPECT_THAT(serialization->GetKeyTemplate().type_url(), "type_url");
+  EXPECT_THAT(serialization->GetKeyTemplate().output_prefix_type(),
+              OutputPrefixType::TINK);
+  EXPECT_THAT(serialization->GetKeyTemplate().value(),
+              test_proto.SerializeAsString());
+  TestProto parsed_proto;
+  parsed_proto.ParseFromString(serialization->GetKeyTemplate().value());
+  EXPECT_THAT(parsed_proto.num(), Eq(12345));
+}
+
+TEST_F(ProtoParametersSerializationTest, Equals) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsTrue());
+}
+
+TEST_F(ProtoParametersSerializationTest, TypeUrlNotEqual) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("other_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+TEST_F(ProtoParametersSerializationTest, OutputPrefixTypeNotEqual) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::TINK,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+TEST_F(ProtoParametersSerializationTest, DifferentValueNotEqual) {
+  TestProto test_proto;
+  test_proto.set_num(12345);
+  TestProto other_proto;
+  other_proto.set_num(67890);
+
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           test_proto.SerializeAsString());
+  ASSERT_THAT(serialization.status(), IsOk());
+
+  util::StatusOr<ProtoParametersSerialization> other_serialization =
+      ProtoParametersSerialization::Create("type_url", OutputPrefixType::RAW,
+                                           other_proto.SerializeAsString());
+  ASSERT_THAT(other_serialization.status(), IsOk());
+
+  EXPECT_THAT(Equals(*serialization, *other_serialization), IsFalse());
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/registry_impl.cc b/cc/internal/registry_impl.cc
index 2b68992..ba74919 100644
--- a/cc/internal/registry_impl.cc
+++ b/cc/internal/registry_impl.cc
@@ -13,58 +13,66 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+
 #include "tink/internal/registry_impl.h"
 
+#include <functional>
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
+#include "tink/input_stream.h"
+#include "tink/internal/keyset_wrapper_store.h"
+#include "tink/key_manager.h"
 #include "tink/monitoring/monitoring.h"
 #include "tink/util/errors.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::util::StatusOr;
-using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
-
 namespace crypto {
 namespace tink {
 namespace internal {
 
 using ::crypto::tink::MonitoringClientFactory;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::KeyTemplate;
 
-StatusOr<const RegistryImpl::KeyTypeInfo*> RegistryImpl::get_key_type_info(
+util::StatusOr<const KeyTypeInfoStore::Info*> RegistryImpl::get_key_type_info(
     absl::string_view type_url) const {
   absl::MutexLock lock(&maps_mutex_);
-  auto it = type_url_to_info_.find(type_url);
-  if (it == type_url_to_info_.end()) {
-    return ToStatusF(absl::StatusCode::kNotFound,
-                     "No manager for type '%s' has been registered.", type_url);
-  }
-  return &it->second;
+  return key_type_info_store_.Get(type_url);
 }
 
-StatusOr<std::unique_ptr<KeyData>> RegistryImpl::NewKeyData(
+util::StatusOr<std::unique_ptr<KeyData>> RegistryImpl::NewKeyData(
     const KeyTemplate& key_template) const {
-  auto key_type_info_or = get_key_type_info(key_template.type_url());
-  if (!key_type_info_or.ok()) return key_type_info_or.status();
-  if (!key_type_info_or.value()->new_key_allowed()) {
+  util::StatusOr<const internal::KeyTypeInfoStore::Info*> info =
+      get_key_type_info(key_template.type_url());
+  if (!info.ok()) {
+    return info.status();
+  }
+  if (!(*info)->new_key_allowed()) {
     return crypto::tink::util::Status(
         absl::StatusCode::kInvalidArgument,
         absl::StrCat("KeyManager for type ", key_template.type_url(),
                      " does not allow for creation of new keys."));
   }
-  return key_type_info_or.value()->key_factory().NewKeyData(
-      key_template.value());
+  return (*info)->key_factory().NewKeyData(key_template.value());
 }
 
-StatusOr<std::unique_ptr<KeyData>> RegistryImpl::GetPublicKeyData(
+util::StatusOr<std::unique_ptr<KeyData>> RegistryImpl::GetPublicKeyData(
     absl::string_view type_url,
     absl::string_view serialized_private_key) const {
-  auto key_type_info_or = get_key_type_info(type_url);
-  if (!key_type_info_or.ok()) return key_type_info_or.status();
-  auto factory = dynamic_cast<const PrivateKeyFactory*>(
-      &key_type_info_or.value()->key_factory());
+  util::StatusOr<const internal::KeyTypeInfoStore::Info*> info =
+      get_key_type_info(type_url);
+  if (!info.ok()) {
+    return info.status();
+  }
+  auto factory =
+      dynamic_cast<const PrivateKeyFactory*>(&(*info)->key_factory());
   if (factory == nullptr) {
     return ToStatusF(absl::StatusCode::kInvalidArgument,
                      "KeyManager for type '%s' does not have "
@@ -75,41 +83,20 @@
   return result;
 }
 
-crypto::tink::util::Status RegistryImpl::CheckInsertable(
-    absl::string_view type_url, const std::type_index& key_manager_type_index,
-    bool new_key_allowed) const {
-  auto it = type_url_to_info_.find(type_url);
-
-  if (it == type_url_to_info_.end()) {
-    return crypto::tink::util::OkStatus();
+util::StatusOr<KeyData> RegistryImpl::DeriveKey(const KeyTemplate& key_template,
+                                                InputStream* randomness) const {
+  util::StatusOr<const internal::KeyTypeInfoStore::Info*> info =
+      get_key_type_info(key_template.type_url());
+  if (!info.ok()) {
+    return info.status();
   }
-  if (it->second.key_manager_type_index() != key_manager_type_index) {
-    return ToStatusF(absl::StatusCode::kAlreadyExists,
-                     "A manager for type '%s' has been already registered.",
-                     type_url);
-  }
-  if (!it->second.new_key_allowed() && new_key_allowed) {
-    return ToStatusF(absl::StatusCode::kAlreadyExists,
-                     "A manager for type '%s' has been already registered "
-                     "with forbidden new key operation.",
-                     type_url);
-  }
-  return crypto::tink::util::OkStatus();
-}
-
-crypto::tink::util::StatusOr<google::crypto::tink::KeyData>
-RegistryImpl::DeriveKey(const google::crypto::tink::KeyTemplate& key_template,
-                        InputStream* randomness) const {
-  auto key_type_info_or = get_key_type_info(key_template.type_url());
-  if (!key_type_info_or.ok()) return key_type_info_or.status();
-  if (!key_type_info_or.value()->key_deriver()) {
+  if (!(*info)->key_deriver()) {
     return crypto::tink::util::Status(
         absl::StatusCode::kInvalidArgument,
         absl::StrCat("Manager for type '", key_template.type_url(),
                      "' cannot derive keys."));
   }
-  return key_type_info_or.value()->key_deriver()(key_template.value(),
-                                                 randomness);
+  return (*info)->key_deriver()(key_template.value(), randomness);
 }
 
 util::Status RegistryImpl::RegisterMonitoringClientFactory(
@@ -126,9 +113,8 @@
 void RegistryImpl::Reset() {
   {
     absl::MutexLock lock(&maps_mutex_);
-    type_url_to_info_.clear();
-    name_to_catalogue_map_.clear();
-    primitive_to_wrapper_.clear();
+    key_type_info_store_ = KeyTypeInfoStore();
+    keyset_wrapper_store_ = KeysetWrapperStore();
   }
   {
     absl::MutexLock lock(&monitoring_factory_mutex_);
diff --git a/cc/internal/registry_impl.h b/cc/internal/registry_impl.h
index 5992b82..4bf79c1 100644
--- a/cc/internal/registry_impl.h
+++ b/cc/internal/registry_impl.h
@@ -13,44 +13,34 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+
 #ifndef TINK_INTERNAL_REGISTRY_IMPL_H_
 #define TINK_INTERNAL_REGISTRY_IMPL_H_
 
 #include <algorithm>
-#include <functional>
-#include <initializer_list>
 #include <memory>
 #include <string>
-#include <tuple>
-#include <typeindex>
-#include <typeinfo>
 #include <utility>
 
 #include "absl/base/thread_annotations.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
 #include "absl/strings/string_view.h"
 #include "absl/synchronization/mutex.h"
-#include "absl/types/optional.h"
-#include "tink/catalogue.h"
-#include "tink/core/key_manager_impl.h"
 #include "tink/core/key_type_manager.h"
-#include "tink/core/private_key_manager_impl.h"
 #include "tink/core/private_key_type_manager.h"
+#include "tink/input_stream.h"
 #include "tink/internal/fips_utils.h"
+#include "tink/internal/key_type_info_store.h"
 #include "tink/internal/keyset_wrapper.h"
-#include "tink/internal/keyset_wrapper_impl.h"
+#include "tink/internal/keyset_wrapper_store.h"
 #include "tink/key_manager.h"
 #include "tink/monitoring/monitoring.h"
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
-#include "tink/util/errors.h"
-#include "tink/util/protobuf_helper.h"
 #include "tink/util/status.h"
-#include "tink/util/validation.h"
+#include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
@@ -68,17 +58,9 @@
   RegistryImpl(const RegistryImpl&) = delete;
   RegistryImpl& operator=(const RegistryImpl&) = delete;
 
-  template <class P>
-  crypto::tink::util::StatusOr<const Catalogue<P>*> get_catalogue(
-      absl::string_view catalogue_name) const ABSL_LOCKS_EXCLUDED(maps_mutex_);
-
-  template <class P>
-  crypto::tink::util::Status AddCatalogue(absl::string_view catalogue_name,
-                                          Catalogue<P>* catalogue)
-      ABSL_LOCKS_EXCLUDED(maps_mutex_);
-
   // Registers the given 'manager' for the key type 'manager->get_key_type()'.
-  // Takes ownership of 'manager', which must be non-nullptr.
+  // Takes ownership of 'manager', which must be non-nullptr. KeyManager is the
+  // legacy/internal version of KeyTypeManager.
   template <class P>
   crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager,
                                                 bool new_key_allowed = true)
@@ -91,15 +73,15 @@
           manager,
       bool new_key_allowed) ABSL_LOCKS_EXCLUDED(maps_mutex_);
 
-  // Takes ownership of 'private_key_manager' and 'public_key_manager'. Both
-  // must be non-nullptr.
+  // Takes ownership of 'private_manager' and 'public_manager'. Both must be
+  // non-nullptr.
   template <class PrivateKeyProto, class KeyFormatProto, class PublicKeyProto,
             class PrivatePrimitivesList, class PublicPrimitivesList>
   crypto::tink::util::Status RegisterAsymmetricKeyManagers(
       PrivateKeyTypeManager<PrivateKeyProto, KeyFormatProto, PublicKeyProto,
-                            PrivatePrimitivesList>* private_key_manager,
+                            PrivatePrimitivesList>* private_manager,
       KeyTypeManager<PublicKeyProto, void, PublicPrimitivesList>*
-          public_key_manager,
+          public_manager,
       bool new_key_allowed) ABSL_LOCKS_EXCLUDED(maps_mutex_);
 
   template <class P>
@@ -116,11 +98,6 @@
       const google::crypto::tink::KeyData& key_data) const
       ABSL_LOCKS_EXCLUDED(maps_mutex_);
 
-  template <class P>
-  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
-      absl::string_view type_url, const portable_proto::MessageLite& key) const
-      ABSL_LOCKS_EXCLUDED(maps_mutex_);
-
   crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
   NewKeyData(const google::crypto::tink::KeyTemplate& key_template) const
       ABSL_LOCKS_EXCLUDED(maps_mutex_);
@@ -166,279 +143,23 @@
   }
 
  private:
-  // All information for a given type url.
-  class KeyTypeInfo {
-   public:
-    // Takes ownership of the 'key_manager'.
-    template <typename P>
-    KeyTypeInfo(KeyManager<P>* key_manager, bool new_key_allowed)
-        : key_manager_type_index_(std::type_index(typeid(*key_manager))),
-          public_key_manager_type_index_(absl::nullopt),
-          new_key_allowed_(new_key_allowed),
-          internal_key_factory_(nullptr),
-          key_factory_(&key_manager->get_key_factory()),
-          key_type_manager_(nullptr) {
-      primitive_to_manager_.emplace(std::type_index(typeid(P)),
-                                    absl::WrapUnique(key_manager));
-    }
-
-    // Takes ownership of the 'key_manager'.
-    template <typename KeyProto, typename KeyFormatProto,
-              typename... Primitives>
-    KeyTypeInfo(KeyTypeManager<KeyProto, KeyFormatProto, List<Primitives...>>*
-                    key_manager,
-                bool new_key_allowed)
-        : key_manager_type_index_(std::type_index(typeid(*key_manager))),
-          public_key_manager_type_index_(absl::nullopt),
-          new_key_allowed_(new_key_allowed),
-          internal_key_factory_(
-              absl::make_unique<internal::KeyFactoryImpl<KeyTypeManager<
-                  KeyProto, KeyFormatProto, List<Primitives...>>>>(
-                  key_manager)),
-          key_factory_(internal_key_factory_.get()),
-          key_deriver_(CreateDeriverFunctionFor(key_manager)),
-          key_type_manager_(absl::WrapUnique(key_manager)) {
-      // TODO(C++17) replace with a fold expression
-      (void)std::initializer_list<int>{
-          0, (primitive_to_manager_.emplace(
-                  std::type_index(typeid(Primitives)),
-                  internal::MakeKeyManager<Primitives>(key_manager)),
-              0)...};
-    }
-
-    // Takes ownership of the 'private_key_manager', but *not* of the
-    // 'public_key_manager'. The public_key_manager must only be alive for the
-    // duration of the constructor.
-    template <typename PrivateKeyProto, typename KeyFormatProto,
-              typename PublicKeyProto, typename PublicPrimitivesList,
-              typename... PrivatePrimitives>
-    KeyTypeInfo(
-        PrivateKeyTypeManager<PrivateKeyProto, KeyFormatProto, PublicKeyProto,
-                              List<PrivatePrimitives...>>* private_key_manager,
-        KeyTypeManager<PublicKeyProto, void, PublicPrimitivesList>*
-            public_key_manager,
-        bool new_key_allowed)
-        : key_manager_type_index_(
-              std::type_index(typeid(*private_key_manager))),
-          public_key_manager_type_index_(
-              std::type_index(typeid(*public_key_manager))),
-          new_key_allowed_(new_key_allowed),
-          internal_key_factory_(
-              absl::make_unique<internal::PrivateKeyFactoryImpl<
-                  PrivateKeyProto, KeyFormatProto, PublicKeyProto,
-                  List<PrivatePrimitives...>, PublicPrimitivesList>>(
-                  private_key_manager, public_key_manager)),
-          key_factory_(internal_key_factory_.get()),
-          key_deriver_(CreateDeriverFunctionFor(private_key_manager)),
-          key_type_manager_(absl::WrapUnique(private_key_manager)) {
-      // TODO(C++17) replace with a fold expression
-      (void)std::initializer_list<int>{
-          0, (primitive_to_manager_.emplace(
-                  std::type_index(typeid(PrivatePrimitives)),
-                  internal::MakePrivateKeyManager<PrivatePrimitives>(
-                      private_key_manager, public_key_manager)),
-              0)...};
-    }
-
-    template <typename P>
-    crypto::tink::util::StatusOr<const KeyManager<P>*> get_key_manager(
-        absl::string_view requested_type_url) const {
-      auto it = primitive_to_manager_.find(std::type_index(typeid(P)));
-      if (it == primitive_to_manager_.end()) {
-        return crypto::tink::util::Status(
-            absl::StatusCode::kInvalidArgument,
-            absl::StrCat(
-                "Primitive type ", typeid(P).name(),
-                " not among supported primitives ",
-                absl::StrJoin(
-                    primitive_to_manager_.begin(), primitive_to_manager_.end(),
-                    ", ",
-                    [](std::string* out,
-                       const std::pair<const std::type_index,
-                                       std::unique_ptr<KeyManagerBase>>& kv) {
-                      absl::StrAppend(out, kv.first.name());
-                    }),
-                " for type URL ", requested_type_url));
-      }
-      return static_cast<const KeyManager<P>*>(it->second.get());
-    }
-
-    const std::type_index& key_manager_type_index() const {
-      return key_manager_type_index_;
-    }
-
-    const absl::optional<std::type_index>& public_key_manager_type_index()
-        const {
-      return public_key_manager_type_index_;
-    }
-
-    bool new_key_allowed() const { return new_key_allowed_; }
-    void set_new_key_allowed(bool b) { new_key_allowed_ = b; }
-
-    const KeyFactory& key_factory() const { return *key_factory_; }
-
-    const std::function<crypto::tink::util::StatusOr<
-        google::crypto::tink::KeyData>(absl::string_view, InputStream*)>&
-    key_deriver() const {
-      return key_deriver_;
-    }
-
-   private:
-    // dynamic std::type_index of the actual key manager class for which this
-    // key was inserted.
-    std::type_index key_manager_type_index_;
-    // dynamic std::type_index of the public key manager corresponding to this
-    // class, in case it was inserted using RegisterAsymmetricKeyManagers,
-    // nullopt otherwise.
-    absl::optional<std::type_index> public_key_manager_type_index_;
-
-    // For each primitive, the corresponding names and key_manager.
-    absl::flat_hash_map<std::type_index, std::unique_ptr<KeyManagerBase>>
-        primitive_to_manager_;
-    // Whether the key manager allows creating new keys.
-    bool new_key_allowed_;
-    // A factory constructed from an internal key manager. Owned version of
-    // key_factory if constructed with a KeyTypeManager. This is nullptr if
-    // constructed with a KeyManager.
-    std::unique_ptr<const KeyFactory> internal_key_factory_;
-    // Unowned copy of internal_key_factory, always different from
-    // nullptr.
-    const KeyFactory* key_factory_;
-    // A function to call to derive a key. If the container was constructed with
-    // a KeyTypeManager which has non-void keyformat type, this will forward to
-    // the function DeriveKey of this container. Otherwise, the function is
-    // 'empty', i.e., "key_deriver_" will cast to false when cast to a bool.
-    std::function<crypto::tink::util::StatusOr<google::crypto::tink::KeyData>(
-        absl::string_view, InputStream*)>
-        key_deriver_;
-    // The owned pointer in case we use a KeyTypeManager, nullptr if
-    // constructed with a KeyManager.
-    const std::shared_ptr<void> key_type_manager_;
-  };
-
-  class WrapperInfo {
-   public:
-    template <typename P, typename Q>
-    explicit WrapperInfo(std::unique_ptr<PrimitiveWrapper<P, Q>> wrapper)
-        : is_same_primitive_wrapping_(std::is_same<P, Q>::value),
-          wrapper_type_index_(std::type_index(typeid(*wrapper))),
-          q_type_index_(std::type_index(typeid(Q))) {
-      auto keyset_wrapper_unique_ptr =
-          absl::make_unique<KeysetWrapperImpl<P, Q>>(
-              wrapper.get(), [](const google::crypto::tink::KeyData& key_data) {
-                return RegistryImpl::GlobalInstance().GetPrimitive<P>(key_data);
-              });
-      keyset_wrapper_ = std::move(keyset_wrapper_unique_ptr);
-      original_wrapper_ = std::move(wrapper);
-    }
-
-    template <typename Q>
-    crypto::tink::util::StatusOr<const KeysetWrapper<Q>*> GetKeysetWrapper()
-        const {
-      if (q_type_index_ != std::type_index(typeid(Q))) {
-        return crypto::tink::util::Status(
-            absl::StatusCode::kInternal,
-            "RegistryImpl::KeysetWrapper() called with wrong type");
-      }
-      return static_cast<KeysetWrapper<Q>*>(keyset_wrapper_.get());
-    }
-
-    template <typename P>
-    crypto::tink::util::StatusOr<const PrimitiveWrapper<P, P>*>
-    GetLegacyWrapper() const {
-      if (!is_same_primitive_wrapping_) {
-        // This happens if a user uses a legacy method (like Registry::Wrap)
-        // directly or has a custom key manager for a primitive which has a
-        // PrimitiveWrapper<P,Q> with P != Q.
-        return crypto::tink::util::Status(
-            absl::StatusCode::kFailedPrecondition,
-            absl::StrCat("Cannot use primitive type ", typeid(P).name(),
-                         " with a custom key manager."));
-      }
-      if (q_type_index_ != std::type_index(typeid(P))) {
-        return crypto::tink::util::Status(
-            absl::StatusCode::kInternal,
-            "RegistryImpl::LegacyWrapper() called with wrong type");
-      }
-      return static_cast<const PrimitiveWrapper<P, P>*>(
-          original_wrapper_.get());
-    }
-
-    // Returns true if the PrimitiveWrapper is the same class as the one used
-    // to construct this WrapperInfo
-    template <typename P, typename Q>
-    bool HasSameType(const PrimitiveWrapper<P, Q>& wrapper) {
-      return wrapper_type_index_ == std::type_index(typeid(wrapper));
-    }
-
-   private:
-    bool is_same_primitive_wrapping_;
-    // dynamic std::type_index of the actual PrimitiveWrapper<P,Q> class for
-    // which this key was inserted.
-    std::type_index wrapper_type_index_;
-    // dynamic std::type_index of Q, when PrimitiveWrapper<P,Q> was inserted.
-    std::type_index q_type_index_;
-    // The primitive_wrapper passed in. We use a shared_ptr because
-    // unique_ptr<void> is invalid.
-    std::shared_ptr<void> original_wrapper_;
-    // The keyset_wrapper_. We use a shared_ptr because unique_ptr<void> is
-    // invalid.
-    std::shared_ptr<void> keyset_wrapper_;
-  };
-
-  // All information for a given primitive label.
-  struct LabelInfo {
-    LabelInfo(std::shared_ptr<void> catalogue, std::type_index type_index,
-              const char* type_id_name)
-        : catalogue(std::move(catalogue)),
-          type_index(type_index),
-          type_id_name(type_id_name) {}
-    // A pointer to the underlying Catalogue<P>. We use a shared_ptr because
-    // shared_ptr<void> is valid (as opposed to unique_ptr<void>).
-    const std::shared_ptr<void> catalogue;
-    // std::type_index of the primitive for which this key was inserted.
-    std::type_index type_index;
-    // TypeId name of the primitive for which this key was inserted.
-    const std::string type_id_name;
-  };
-
-  template <class P>
-  crypto::tink::util::StatusOr<const PrimitiveWrapper<P, P>*> GetLegacyWrapper()
-      const ABSL_LOCKS_EXCLUDED(maps_mutex_);
-
-  template <class P>
-  crypto::tink::util::StatusOr<const KeysetWrapper<P>*> GetKeysetWrapper() const
-      ABSL_LOCKS_EXCLUDED(maps_mutex_);
-
   // Returns the key type info for a given type URL. Since we never replace
   // key type infos, the pointers will stay valid for the lifetime of the
   // binary.
-  crypto::tink::util::StatusOr<const KeyTypeInfo*> get_key_type_info(
+  crypto::tink::util::StatusOr<const KeyTypeInfoStore::Info*> get_key_type_info(
       absl::string_view type_url) const ABSL_LOCKS_EXCLUDED(maps_mutex_);
 
-  // Returns OK if the key manager with the given type index can be inserted
-  // for type url type_url and parameter new_key_allowed. Otherwise returns
-  // an error to be returned to the user.
-  crypto::tink::util::Status CheckInsertable(
-      absl::string_view type_url, const std::type_index& key_manager_type_index,
-      bool new_key_allowed) const ABSL_SHARED_LOCKS_REQUIRED(maps_mutex_);
-
   mutable absl::Mutex maps_mutex_;
-  // A map from the type_url to the given KeyTypeInfo. Once emplaced KeyTypeInfo
-  // objects must remain valid throughout the life time of the binary. Hence,
-  // one should /never/ replace any element of the KeyTypeInfo. This is because
-  // get_key_type_manager() needs to guarantee that the returned
-  // key_type_manager remains valid.
-  // NOTE: We require pointer stability of the value, as get_key_type_info
-  // returns a pointer which needs to stay alive.
-  absl::flat_hash_map<std::string, KeyTypeInfo> type_url_to_info_
-      ABSL_GUARDED_BY(maps_mutex_);
-  // A map from the type_id to the corresponding wrapper.
-  absl::flat_hash_map<std::type_index, WrapperInfo> primitive_to_wrapper_
-      ABSL_GUARDED_BY(maps_mutex_);
-
-  absl::flat_hash_map<std::string, LabelInfo> name_to_catalogue_map_
-      ABSL_GUARDED_BY(maps_mutex_);
+  // Stores information about key types constructed from their KeyTypeManager or
+  // KeyManager.
+  // Once inserted, KeyTypeInfoStore::Info objects must remain valid for the
+  // lifetime of the binary, and the Info object's pointer stability is
+  // required. Elements in Info, which include the KeyTypeManager or KeyManager,
+  // must not be replaced.
+  KeyTypeInfoStore key_type_info_store_ ABSL_GUARDED_BY(maps_mutex_);
+  // Stores information about keyset wrappers constructed from their
+  // PrimitiveWrapper.
+  KeysetWrapperStore keyset_wrapper_store_ ABSL_GUARDED_BY(maps_mutex_);
 
   mutable absl::Mutex monitoring_factory_mutex_;
   std::unique_ptr<crypto::tink::MonitoringClientFactory> monitoring_factory_
@@ -446,248 +167,57 @@
 };
 
 template <class P>
-crypto::tink::util::Status RegistryImpl::AddCatalogue(
-    absl::string_view catalogue_name, Catalogue<P>* catalogue) {
-  if (catalogue == nullptr) {
-    return crypto::tink::util::Status(
-        absl::StatusCode::kInvalidArgument,
-        "Parameter 'catalogue' must be non-null.");
-  }
-  std::shared_ptr<void> entry(catalogue);
-  absl::MutexLock lock(&maps_mutex_);
-  auto curr_catalogue = name_to_catalogue_map_.find(catalogue_name);
-  if (curr_catalogue != name_to_catalogue_map_.end()) {
-    auto existing =
-        static_cast<Catalogue<P>*>(curr_catalogue->second.catalogue.get());
-    if (std::type_index(typeid(*existing)) !=
-        std::type_index(typeid(*catalogue))) {
-      return ToStatusF(absl::StatusCode::kAlreadyExists,
-                       "A catalogue named '%s' has been already added.",
-                       catalogue_name);
-    }
-  } else {
-    name_to_catalogue_map_.emplace(
-        std::piecewise_construct, std::forward_as_tuple(catalogue_name),
-        std::forward_as_tuple(std::move(entry), std::type_index(typeid(P)),
-                              typeid(P).name()));
-  }
-  return crypto::tink::util::OkStatus();
-}
-
-template <class P>
-crypto::tink::util::StatusOr<const Catalogue<P>*> RegistryImpl::get_catalogue(
-    absl::string_view catalogue_name) const {
-  absl::MutexLock lock(&maps_mutex_);
-  auto catalogue_entry = name_to_catalogue_map_.find(catalogue_name);
-  if (catalogue_entry == name_to_catalogue_map_.end()) {
-    return ToStatusF(absl::StatusCode::kNotFound,
-                     "No catalogue named '%s' has been added.", catalogue_name);
-  }
-  if (catalogue_entry->second.type_id_name != typeid(P).name()) {
-    return ToStatusF(absl::StatusCode::kInvalidArgument,
-                     "Wrong Primitive type for catalogue named '%s': "
-                     "got '%s', expected '%s'",
-                     catalogue_name, typeid(P).name(),
-                     catalogue_entry->second.type_id_name);
-  }
-  return static_cast<Catalogue<P>*>(catalogue_entry->second.catalogue.get());
-}
-
-template <class P>
 crypto::tink::util::Status RegistryImpl::RegisterKeyManager(
     KeyManager<P>* manager, bool new_key_allowed) {
   auto owned_manager = absl::WrapUnique(manager);
-  if (owned_manager == nullptr) {
+  if (manager == nullptr) {
     return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
                                       "Parameter 'manager' must be non-null.");
   }
-  std::string type_url = owned_manager->get_key_type();
-  if (!manager->DoesSupport(type_url)) {
-    return ToStatusF(absl::StatusCode::kInvalidArgument,
-                     "The manager does not support type '%s'.", type_url);
-  }
   absl::MutexLock lock(&maps_mutex_);
-  crypto::tink::util::Status status = CheckInsertable(
-      type_url, std::type_index(typeid(*owned_manager)), new_key_allowed);
-  if (!status.ok()) return status;
-
-  auto it = type_url_to_info_.find(type_url);
-  if (it != type_url_to_info_.end()) {
-    it->second.set_new_key_allowed(new_key_allowed);
-  } else {
-    type_url_to_info_.emplace(
-        std::piecewise_construct, std::forward_as_tuple(type_url),
-        std::forward_as_tuple(owned_manager.release(), new_key_allowed));
-  }
-  return crypto::tink::util::OkStatus();
+  return key_type_info_store_.AddKeyManager(std::move(owned_manager),
+                                            new_key_allowed);
 }
 
 template <class KeyProto, class KeyFormatProto, class PrimitiveList>
 crypto::tink::util::Status RegistryImpl::RegisterKeyTypeManager(
     std::unique_ptr<KeyTypeManager<KeyProto, KeyFormatProto, PrimitiveList>>
-        owned_manager,
+        manager,
     bool new_key_allowed) {
-  if (owned_manager == nullptr) {
+  if (manager == nullptr) {
     return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
                                       "Parameter 'manager' must be non-null.");
   }
-  std::string type_url = owned_manager->get_key_type();
   absl::MutexLock lock(&maps_mutex_);
-
-  // Check FIPS status
-  internal::FipsCompatibility fips_compatible = owned_manager->FipsStatus();
-  auto fips_status = internal::ChecksFipsCompatibility(fips_compatible);
-  if (!fips_status.ok()) {
-    return crypto::tink::util::Status(
-        absl::StatusCode::kInternal,
-        absl::StrCat("Failed registering the key manager for ",
-                     typeid(*owned_manager).name(),
-                     " as it is not FIPS compatible."));
-  }
-
-  crypto::tink::util::Status status = CheckInsertable(
-      type_url, std::type_index(typeid(*owned_manager)), new_key_allowed);
-  if (!status.ok()) return status;
-
-  auto it = type_url_to_info_.find(type_url);
-  if (it != type_url_to_info_.end()) {
-    it->second.set_new_key_allowed(new_key_allowed);
-  } else {
-    type_url_to_info_.emplace(
-        std::piecewise_construct, std::forward_as_tuple(type_url),
-        std::forward_as_tuple(owned_manager.release(), new_key_allowed));
-  }
-  return crypto::tink::util::OkStatus();
+  return key_type_info_store_.AddKeyTypeManager(std::move(manager),
+                                                new_key_allowed);
 }
 
 template <class PrivateKeyProto, class KeyFormatProto, class PublicKeyProto,
           class PrivatePrimitivesList, class PublicPrimitivesList>
 crypto::tink::util::Status RegistryImpl::RegisterAsymmetricKeyManagers(
     PrivateKeyTypeManager<PrivateKeyProto, KeyFormatProto, PublicKeyProto,
-                          PrivatePrimitivesList>* private_key_manager,
-    KeyTypeManager<PublicKeyProto, void, PublicPrimitivesList>*
-        public_key_manager,
+                          PrivatePrimitivesList>* private_manager,
+    KeyTypeManager<PublicKeyProto, void, PublicPrimitivesList>* public_manager,
     bool new_key_allowed) ABSL_LOCKS_EXCLUDED(maps_mutex_) {
-  auto owned_private_key_manager = absl::WrapUnique(private_key_manager);
-  auto owned_public_key_manager = absl::WrapUnique(public_key_manager);
-  if (private_key_manager == nullptr) {
+  auto owned_private_manager = absl::WrapUnique(private_manager);
+  auto owned_public_manager = absl::WrapUnique(public_manager);
+
+  if (private_manager == nullptr) {
     return crypto::tink::util::Status(
         absl::StatusCode::kInvalidArgument,
-        "Parameter 'private_key_manager' must be non-null.");
+        "Parameter 'private_manager' must be non-null.");
   }
-  if (owned_public_key_manager == nullptr) {
+  if (public_manager == nullptr) {
     return crypto::tink::util::Status(
         absl::StatusCode::kInvalidArgument,
-        "Parameter 'public_key_manager' must be non-null.");
+        "Parameter 'public_manager' must be non-null.");
   }
-  std::string private_type_url = private_key_manager->get_key_type();
-  std::string public_type_url = public_key_manager->get_key_type();
 
   absl::MutexLock lock(&maps_mutex_);
-
-  // Check FIPS status
-  auto private_fips_status =
-      internal::ChecksFipsCompatibility(private_key_manager->FipsStatus());
-
-  if (!private_fips_status.ok()) {
-    return crypto::tink::util::Status(
-        absl::StatusCode::kInternal,
-        absl::StrCat("Failed registering the key manager for ",
-                     typeid(*private_key_manager).name(),
-                     " as it is not FIPS compatible."));
-  }
-
-  auto public_fips_status =
-      internal::ChecksFipsCompatibility(public_key_manager->FipsStatus());
-
-  if (!public_fips_status.ok()) {
-    return crypto::tink::util::Status(
-        absl::StatusCode::kInternal,
-        absl::StrCat("Failed registering the key manager for ",
-                     typeid(*public_key_manager).name(),
-                     " as it is not FIPS compatible."));
-  }
-
-  crypto::tink::util::Status status = CheckInsertable(
-      private_type_url, std::type_index(typeid(*private_key_manager)),
+  return key_type_info_store_.AddAsymmetricKeyTypeManagers(
+      std::move(owned_private_manager), std::move(owned_public_manager),
       new_key_allowed);
-  if (!status.ok()) return status;
-  status = CheckInsertable(public_type_url,
-                           std::type_index(typeid(*public_key_manager)),
-                           new_key_allowed);
-  if (!status.ok()) return status;
-
-  if (private_type_url == public_type_url) {
-    return crypto::tink::util::Status(
-        absl::StatusCode::kInvalidArgument,
-        "Passed in key managers must have different get_key_type() results.");
-  }
-
-  auto private_it = type_url_to_info_.find(private_type_url);
-  auto public_it = type_url_to_info_.find(public_type_url);
-  bool private_found = private_it != type_url_to_info_.end();
-  bool public_found = public_it != type_url_to_info_.end();
-
-  if (private_found && !public_found) {
-    return crypto::tink::util::Status(
-        absl::StatusCode::kInvalidArgument,
-        absl::StrCat(
-            "Private key manager corresponding to ",
-            typeid(*private_key_manager).name(),
-            " was previously registered, but key manager corresponding to ",
-            typeid(*public_key_manager).name(),
-            " was not, so it's impossible to register them jointly"));
-  }
-  if (!private_found && public_found) {
-    return crypto::tink::util::Status(
-        absl::StatusCode::kInvalidArgument,
-        absl::StrCat("Key manager corresponding to ",
-                     typeid(*public_key_manager).name(),
-                     " was previously registered, but private key manager "
-                     "corresponding to ",
-                     typeid(*private_key_manager).name(),
-                     " was not, so it's impossible to register them jointly"));
-  }
-
-  if (private_found) {
-    // implies public_found.
-    if (!private_it->second.public_key_manager_type_index().has_value()) {
-      return crypto::tink::util::Status(
-          absl::StatusCode::kInvalidArgument,
-          absl::StrCat("private key manager corresponding to ",
-                       typeid(*private_key_manager).name(),
-                       " is already registered without public key manager, "
-                       "cannot be re-registered with public key manager. "));
-    }
-    if (*private_it->second.public_key_manager_type_index() !=
-        std::type_index(typeid(*public_key_manager))) {
-      return crypto::tink::util::Status(
-          absl::StatusCode::kInvalidArgument,
-          absl::StrCat(
-              "private key manager corresponding to ",
-              typeid(*private_key_manager).name(),
-              " is already registered with ",
-              private_it->second.public_key_manager_type_index()->name(),
-              ", cannot be re-registered with ",
-              typeid(*public_key_manager).name()));
-    }
-  }
-
-  if (!private_found) {
-    // !public_found must hold.
-    type_url_to_info_.emplace(
-        std::piecewise_construct, std::forward_as_tuple(private_type_url),
-        std::forward_as_tuple(owned_private_key_manager.release(),
-                              owned_public_key_manager.get(), new_key_allowed));
-    type_url_to_info_.emplace(
-        std::piecewise_construct, std::forward_as_tuple(public_type_url),
-        std::forward_as_tuple(owned_public_key_manager.release(),
-                              new_key_allowed));
-  } else {
-    private_it->second.set_new_key_allowed(new_key_allowed);
-  }
-
-  return util::OkStatus();
 }
 
 template <class P, class Q>
@@ -697,35 +227,27 @@
     return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
                                       "Parameter 'wrapper' must be non-null.");
   }
-  std::unique_ptr<PrimitiveWrapper<P, Q>> entry(wrapper);
+  std::unique_ptr<PrimitiveWrapper<P, Q>> owned_wrapper(wrapper);
 
   absl::MutexLock lock(&maps_mutex_);
-  auto it = primitive_to_wrapper_.find(std::type_index(typeid(Q)));
-  if (it != primitive_to_wrapper_.end()) {
-    if (!it->second.HasSameType(*wrapper)) {
-      return util::Status(
-          absl::StatusCode::kAlreadyExists,
-          "A wrapper named for this primitive has already been added.");
-    }
-    return crypto::tink::util::OkStatus();
-  }
-  primitive_to_wrapper_.emplace(
-      std::piecewise_construct,
-      std::forward_as_tuple(std::type_index(typeid(Q))),
-      std::forward_as_tuple(std::move(entry)));
-  return crypto::tink::util::OkStatus();
+  std::function<crypto::tink::util::StatusOr<std::unique_ptr<P>>(
+      const google::crypto::tink::KeyData& key_data)>
+      primitive_getter = [this](const google::crypto::tink::KeyData& key_data) {
+        return this->GetPrimitive<P>(key_data);
+      };
+  return keyset_wrapper_store_.Add(std::move(owned_wrapper), primitive_getter);
 }
 
 template <class P>
 crypto::tink::util::StatusOr<const KeyManager<P>*>
 RegistryImpl::get_key_manager(absl::string_view type_url) const {
-  absl::MutexLock lock(&maps_mutex_);
-  auto it = type_url_to_info_.find(type_url);
-  if (it == type_url_to_info_.end()) {
-    return ToStatusF(absl::StatusCode::kNotFound,
-                     "No manager for type '%s' has been registered.", type_url);
+  crypto::tink::util::StatusOr<
+      const crypto::tink::internal::KeyTypeInfoStore::Info*>
+      info = get_key_type_info(type_url);
+  if (!info.ok()) {
+    return info.status();
   }
-  return it->second.get_key_manager<P>(type_url);
+  return (*info)->get_key_manager<P>(type_url);
 }
 
 template <class P>
@@ -739,42 +261,6 @@
 }
 
 template <class P>
-crypto::tink::util::StatusOr<std::unique_ptr<P>> RegistryImpl::GetPrimitive(
-    absl::string_view type_url, const portable_proto::MessageLite& key) const {
-  auto key_manager_result = get_key_manager<P>(type_url);
-  if (key_manager_result.ok()) {
-    return key_manager_result.value()->GetPrimitive(key);
-  }
-  return key_manager_result.status();
-}
-
-template <class P>
-crypto::tink::util::StatusOr<const PrimitiveWrapper<P, P>*>
-RegistryImpl::GetLegacyWrapper() const {
-  absl::MutexLock lock(&maps_mutex_);
-  auto it = primitive_to_wrapper_.find(std::type_index(typeid(P)));
-  if (it == primitive_to_wrapper_.end()) {
-    return util::Status(
-        absl::StatusCode::kNotFound,
-        absl::StrCat("No wrapper registered for type ", typeid(P).name()));
-  }
-  return it->second.GetLegacyWrapper<P>();
-}
-
-template <class P>
-crypto::tink::util::StatusOr<const KeysetWrapper<P>*>
-RegistryImpl::GetKeysetWrapper() const {
-  absl::MutexLock lock(&maps_mutex_);
-  auto it = primitive_to_wrapper_.find(std::type_index(typeid(P)));
-  if (it == primitive_to_wrapper_.end()) {
-    return util::Status(
-        absl::StatusCode::kNotFound,
-        absl::StrCat("No wrapper registered for type ", typeid(P).name()));
-  }
-  return it->second.GetKeysetWrapper<P>();
-}
-
-template <class P>
 crypto::tink::util::StatusOr<std::unique_ptr<P>> RegistryImpl::Wrap(
     std::unique_ptr<PrimitiveSet<P>> primitive_set) const {
   if (primitive_set == nullptr) {
@@ -782,29 +268,45 @@
         absl::StatusCode::kInvalidArgument,
         "Parameter 'primitive_set' must be non-null.");
   }
-  util::StatusOr<const PrimitiveWrapper<P, P>*> wrapper_result =
-      GetLegacyWrapper<P>();
-  if (!wrapper_result.ok()) {
-    return wrapper_result.status();
+  const PrimitiveWrapper<P, P>* wrapper = nullptr;
+  {
+    absl::MutexLock lock(&maps_mutex_);
+    crypto::tink::util::StatusOr<const PrimitiveWrapper<P, P>*> wrapper_status =
+        keyset_wrapper_store_.GetPrimitiveWrapper<P>();
+    if (!wrapper_status.ok()) {
+      return wrapper_status.status();
+    }
+    wrapper = *wrapper_status;
   }
-  return wrapper_result.value()->Wrap(std::move(primitive_set));
+  return wrapper->Wrap(std::move(primitive_set));
 }
 
 template <class P>
 crypto::tink::util::StatusOr<std::unique_ptr<P>> RegistryImpl::WrapKeyset(
     const google::crypto::tink::Keyset& keyset,
     const absl::flat_hash_map<std::string, std::string>& annotations) const {
-  crypto::tink::util::StatusOr<const KeysetWrapper<P>*> keyset_wrapper =
-      GetKeysetWrapper<P>();
-  if (!keyset_wrapper.ok()) {
-    return keyset_wrapper.status();
+  const KeysetWrapper<P>* keyset_wrapper = nullptr;
+  {
+    absl::MutexLock lock(&maps_mutex_);
+    crypto::tink::util::StatusOr<const KeysetWrapper<P>*>
+        keyset_wrapper_status = keyset_wrapper_store_.Get<P>();
+    if (!keyset_wrapper_status.ok()) {
+      return keyset_wrapper_status.status();
+    }
+    keyset_wrapper = *keyset_wrapper_status;
   }
-  return (*keyset_wrapper)->Wrap(keyset, annotations);
+  // `maps_mutex_` must be released before calling Wrap or this will deadlock,
+  // as Wrap calls get_key_manager.
+  return keyset_wrapper->Wrap(keyset, annotations);
 }
 
 inline crypto::tink::util::Status RegistryImpl::RestrictToFipsIfEmpty() const {
   absl::MutexLock lock(&maps_mutex_);
-  if (type_url_to_info_.empty()) {
+  // If we are already in FIPS mode, then do nothing..
+  if (IsFipsModeEnabled()) {
+    return util::OkStatus();
+  }
+  if (key_type_info_store_.IsEmpty()) {
     SetFipsRestricted();
     return util::OkStatus();
   }
diff --git a/cc/internal/registry_impl_test.cc b/cc/internal/registry_impl_test.cc
index d6aaf21..7b3ec51 100644
--- a/cc/internal/registry_impl_test.cc
+++ b/cc/internal/registry_impl_test.cc
@@ -14,46 +14,58 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
+#include "tink/internal/registry_impl.h"
+
+#include <stdint.h>
+
 #include <memory>
+#include <sstream>
 #include <string>
 #include <thread>  // NOLINT(build/c++11)
+#include <typeinfo>
 #include <utility>
-#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
 #include "openssl/crypto.h"
 #include "tink/aead.h"
 #include "tink/aead/aead_wrapper.h"
 #include "tink/aead/aes_gcm_key_manager.h"
-#include "tink/catalogue.h"
-#include "tink/config/tink_fips.h"
 #include "tink/core/key_manager_impl.h"
 #include "tink/core/key_type_manager.h"
-#include "tink/crypto_format.h"
+#include "tink/core/private_key_manager_impl.h"
+#include "tink/core/private_key_type_manager.h"
+#include "tink/core/template_util.h"
 #include "tink/hybrid/ecies_aead_hkdf_private_key_manager.h"
 #include "tink/hybrid/ecies_aead_hkdf_public_key_manager.h"
-#include "tink/keyset_manager.h"
-#include "tink/monitoring/monitoring.h"
+#include "tink/hybrid_decrypt.h"
+#include "tink/input_stream.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/key_manager.h"
+#include "tink/mac.h"
 #include "tink/monitoring/monitoring_client_mocks.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
 #include "tink/registry.h"
 #include "tink/subtle/aes_gcm_boringssl.h"
 #include "tink/subtle/random.h"
+#include "tink/util/input_stream_util.h"
 #include "tink/util/istream_input_stream.h"
 #include "tink/util/protobuf_helper.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
-#include "tink/util/test_keyset_handle.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 #include "proto/aes_ctr_hmac_aead.pb.h"
 #include "proto/aes_gcm.pb.h"
 #include "proto/common.pb.h"
 #include "proto/ecdsa.pb.h"
+#include "proto/ecies_aead_hkdf.pb.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
@@ -94,9 +106,7 @@
 
 class RegistryTest : public ::testing::Test {
  protected:
-  void SetUp() override {
-    Registry::Reset();
-  }
+  void SetUp() override { Registry::Reset(); }
 
   void TearDown() override {
     // Reset is needed here to ensure Mock objects get deleted and do not leak.
@@ -137,28 +147,23 @@
   explicit TestAeadKeyManager(const std::string& key_type)
       : key_type_(key_type), key_factory_(key_type) {}
 
-  util::StatusOr<std::unique_ptr<Aead>>
-  GetPrimitive(const KeyData& key) const override {
+  util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
+      const KeyData& key) const override {
     std::unique_ptr<Aead> aead(new DummyAead(key_type_));
     return std::move(aead);
   }
 
-  util::StatusOr<std::unique_ptr<Aead>>
-  GetPrimitive(const MessageLite& key) const override {
+  util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
+      const MessageLite& key) const override {
     return util::Status(absl::StatusCode::kUnknown,
                         "TestKeyFactory cannot construct an aead");
   }
 
-
-  uint32_t get_version() const override {
-    return 0;
-  }
+  uint32_t get_version() const override { return 0; }
 
   const std::string& get_key_type() const override { return key_type_; }
 
-  const KeyFactory& get_key_factory() const override {
-    return key_factory_;
-  }
+  const KeyFactory& get_key_factory() const override { return key_factory_; }
 
  private:
   std::string key_type_;
@@ -254,7 +259,7 @@
 template <typename P, typename Q = P>
 class TestWrapper : public PrimitiveWrapper<P, Q> {
  public:
-  TestWrapper() {}
+  TestWrapper() = default;
   crypto::tink::util::StatusOr<std::unique_ptr<Q>> Wrap(
       std::unique_ptr<PrimitiveSet<P>> primitive_set) const override {
     return util::Status(absl::StatusCode::kUnimplemented,
@@ -286,7 +291,8 @@
   for (int i = 0; i < manager_count; i++) {
     std::string key_type = key_type_prefix + std::to_string(i);
     util::Status status = Registry::RegisterKeyManager(
-        new TestAeadKeyManager(key_type));
+        absl::make_unique<TestAeadKeyManager>(key_type),
+        /* new_key_allowed= */ true);
     EXPECT_TRUE(status.ok()) << status;
   }
 }
@@ -375,10 +381,8 @@
   int count_b = 72;
 
   // Register some managers.
-  std::thread register_a(register_test_managers,
-                         key_type_prefix_a, count_a);
-  std::thread register_b(register_test_managers,
-                         key_type_prefix_b, count_b);
+  std::thread register_a(register_test_managers, key_type_prefix_a, count_a);
+  std::thread register_b(register_test_managers, key_type_prefix_b, count_b);
   register_a.join();
   register_b.join();
 
@@ -417,7 +421,6 @@
   auto status = Registry::RegisterKeyManager(
       absl::make_unique<TestAeadKeyManager>(key_type_1), true);
 
-
   EXPECT_TRUE(status.ok()) << status;
 
   status = Registry::RegisterKeyManager(
@@ -475,7 +478,8 @@
 TEST_F(RegistryTest, GetKeyManagerRemainsValid) {
   std::string key_type = AesGcmKeyManager().get_key_type();
   EXPECT_THAT(Registry::RegisterKeyManager(
-      absl::make_unique<TestAeadKeyManager>(key_type), true), IsOk());
+                  absl::make_unique<TestAeadKeyManager>(key_type), true),
+              IsOk());
 
   crypto::tink::util::StatusOr<const KeyManager<Aead>*> key_manager =
       Registry::get_key_manager<Aead>(key_type);
@@ -486,49 +490,6 @@
   EXPECT_THAT(key_manager.value()->get_key_type(), Eq(key_type));
 }
 
-class TestAeadCatalogue : public Catalogue<Aead> {
- public:
-  TestAeadCatalogue() {}
-
-  util::StatusOr<std::unique_ptr<KeyManager<Aead>>> GetKeyManager(
-      const std::string& type_url, const std::string& primitive_name,
-      uint32_t min_version) const override {
-    return util::Status(absl::StatusCode::kUnimplemented,
-                        "This is a test catalogue.");
-  }
-};
-
-class TestAeadCatalogue2 : public TestAeadCatalogue {};
-
-TEST_F(RegistryTest, testAddCatalogue) {
-  std::string catalogue_name = "SomeCatalogue";
-
-  std::unique_ptr<TestAeadCatalogue> null_catalogue = nullptr;
-  auto status =
-      Registry::AddCatalogue(catalogue_name, std::move(null_catalogue));
-  EXPECT_FALSE(status.ok());
-  EXPECT_EQ(absl::StatusCode::kInvalidArgument, status.code()) << status;
-
-  // Add a catalogue.
-  status = Registry::AddCatalogue(catalogue_name,
-                                  absl::make_unique<TestAeadCatalogue>());
-  EXPECT_TRUE(status.ok()) << status;
-
-  // Add the same catalogue again, it should work (idempotence).
-  status = Registry::AddCatalogue(catalogue_name,
-                                  absl::make_unique<TestAeadCatalogue>());
-  EXPECT_TRUE(status.ok()) << status;
-
-  // Try overriding a catalogue.
-  status = Registry::AddCatalogue(catalogue_name,
-                                  absl::make_unique<TestAeadCatalogue2>());
-  EXPECT_FALSE(status.ok());
-  EXPECT_EQ(absl::StatusCode::kAlreadyExists, status.code()) << status;
-
-  // Check the catalogue is still present.
-  EXPECT_THAT(Registry::get_catalogue<Aead>(catalogue_name), IsOk());
-}
-
 TEST_F(RegistryTest, testGettingPrimitives) {
   std::string key_type_1 = "google.crypto.tink.AesCtrHmacAeadKey";
   std::string key_type_2 = "google.crypto.tink.AesGcmKey";
@@ -653,8 +614,7 @@
     key_template.set_value("some totally other value 42");
     auto new_key_data_result = Registry::NewKeyData(key_template);
     EXPECT_FALSE(new_key_data_result.ok());
-    EXPECT_EQ(absl::StatusCode::kNotFound,
-              new_key_data_result.status().code());
+    EXPECT_EQ(absl::StatusCode::kNotFound, new_key_data_result.status().code());
     EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_type_url,
                         std::string(new_key_data_result.status().message()));
   }
@@ -892,7 +852,7 @@
 
 // Tests that wrapping of a keyset works in the usual case.
 TEST_F(RegistryTest, KeysetWrappingTest) {
-  if (!FIPS_mode()) {
+  if (!IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported when BoringSSL is not built in FIPS-mode.";
   }
 
@@ -906,9 +866,9 @@
   ON_CALL(*fips_key_manager, FipsStatus())
       .WillByDefault(testing::Return(FipsCompatibility::kRequiresBoringCrypto));
 
-  ASSERT_THAT(Registry::RegisterKeyTypeManager(
-                  std::move(fips_key_manager), true),
-              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(std::move(fips_key_manager), true),
+      IsOk());
   ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
                   absl::make_unique<AeadVariantWrapper>()),
               IsOk());
@@ -1010,7 +970,7 @@
 }
 
 TEST_F(RegistryTest, RegisterFipsKeyTypeManager) {
-  if (!kUseOnlyFips || !FIPS_mode()) {
+  if (!kUseOnlyFips || !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-mode with BoringCrypto available.";
   }
 
@@ -1025,7 +985,7 @@
 }
 
 TEST_F(RegistryTest, RegisterFipsKeyTypeManagerNoBoringCrypto) {
-  if (!kUseOnlyFips || FIPS_mode()) {
+  if (!kUseOnlyFips || IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Only supported in FIPS-mode with BoringCrypto not available.";
   }
@@ -1228,8 +1188,9 @@
 TEST_F(RegistryTest, KeyManagerDeriveKeyFail) {
   std::string key_type = "type.googleapis.com/google.crypto.tink.AesGcmKey";
   ASSERT_THAT(Registry::RegisterKeyManager(
-      absl::make_unique<TestAeadKeyManager>(key_type),
-      /* new_key_allowed= */ true), IsOk());
+                  absl::make_unique<TestAeadKeyManager>(key_type),
+                  /* new_key_allowed= */ true),
+              IsOk());
 
   KeyTemplate key_template;
   key_template.set_type_url("type.googleapis.com/google.crypto.tink.AesGcmKey");
@@ -1323,9 +1284,15 @@
               StatusIs(absl::StatusCode::kAlreadyExists));
 }
 
+}  // namespace
+
+// NOTE: These are outside of the anonymous namespace to allow compiling with
+// MSVC.
 class PrivatePrimitiveA {};
 class PrivatePrimitiveB {};
 
+namespace {
+
 class TestPrivateKeyTypeManager
     : public PrivateKeyTypeManager<EcdsaPrivateKey, EcdsaKeyFormat,
                                    EcdsaPublicKey,
@@ -1388,9 +1355,15 @@
       "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
 };
 
+}  // namespace
+
+// NOTE: These are outside of the anonymous namespace to allow compiling with
+// MSVC.
 class PublicPrimitiveA {};
 class PublicPrimitiveB {};
 
+namespace {
+
 class TestPublicKeyTypeManager
     : public KeyTypeManager<EcdsaPublicKey, void,
                             List<PublicPrimitiveA, PublicPrimitiveB>> {
@@ -1451,7 +1424,7 @@
 }
 
 TEST_F(RegistryTest, RegisterAsymmetricKeyManagers) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1463,7 +1436,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricMoreRestrictiveNewKey) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1480,7 +1453,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricSameNewKey) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1505,7 +1478,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricLessRestrictiveGivesError) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1526,7 +1499,7 @@
 // remains valid.
 
 TEST_F(RegistryTest, RegisterAsymmetricKeyManagersGetKeyManagerStaysValid) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1554,9 +1527,8 @@
               Eq(TestPublicKeyTypeManager().get_key_type()));
 }
 
-
 TEST_F(RegistryTest, AsymmetricPrivateRegisterAlone) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1583,7 +1555,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricGetPrimitiveA) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1601,7 +1573,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricGetPrimitiveB) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1619,7 +1591,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricGetPublicPrimitiveA) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1637,7 +1609,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricGetPublicPrimitiveB) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1655,7 +1627,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricGetWrongPrimitiveError) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1673,9 +1645,7 @@
 }
 
 class PrivateKeyManagerImplTest : public testing::Test {
-  void SetUp() override {
-    Registry::Reset();
-  }
+  void SetUp() override { Registry::Reset(); }
 
   void TearDown() override {
     // Reset is needed here to ensure Mock objects get deleted and do not leak.
@@ -1684,7 +1654,7 @@
 };
 
 TEST_F(PrivateKeyManagerImplTest, AsymmetricFactoryNewKeyFromMessage) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1711,7 +1681,7 @@
 }
 
 TEST_F(PrivateKeyManagerImplTest, AsymmetricNewKeyDisallowed) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1733,7 +1703,7 @@
 }
 
 TEST_F(RegistryTest, AsymmetricGetPublicKeyData) {
-  if (kUseOnlyFips && !FIPS_mode()) {
+  if (kUseOnlyFips && !IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -1923,8 +1893,8 @@
               std::move(delegating_key_manager), true);
   EXPECT_THAT(status, IsOk());
   status = registry_impl.RegisterKeyTypeManager<AesGcmKey, AesGcmKeyFormat,
-                                                   List<Aead, AeadVariant>>(
-                  absl::make_unique<ExampleKeyTypeManager>(), true);
+                                                List<Aead, AeadVariant>>(
+      absl::make_unique<ExampleKeyTypeManager>(), true);
   EXPECT_THAT(status, IsOk());
 
   EcdsaKeyFormat format;
@@ -1952,8 +1922,8 @@
               std::move(delegating_key_manager), true);
   EXPECT_THAT(status, IsOk());
   status = registry_impl.RegisterKeyTypeManager<AesGcmKey, AesGcmKeyFormat,
-                                                   List<Aead, AeadVariant>>(
-                  absl::make_unique<ExampleKeyTypeManager>(), true);
+                                                List<Aead, AeadVariant>>(
+      absl::make_unique<ExampleKeyTypeManager>(), true);
   EXPECT_THAT(status, IsOk());
 
   EcdsaKeyFormat format;
@@ -1979,8 +1949,8 @@
       absl::make_unique<TestPublicKeyTypeManager>().release(), true);
   EXPECT_THAT(status, IsOk());
   status = registry_impl.RegisterKeyTypeManager<AesGcmKey, AesGcmKeyFormat,
-                                                   List<Aead, AeadVariant>>(
-                  absl::make_unique<ExampleKeyTypeManager>(), true);
+                                                List<Aead, AeadVariant>>(
+      absl::make_unique<ExampleKeyTypeManager>(), true);
   EXPECT_THAT(status, IsOk());
 
   EcdsaPrivateKey private_key;
@@ -1995,14 +1965,29 @@
                        HasSubstr("GetPublicKey worked")));
 }
 
-TEST_F(RegistryImplTest, FipsSucceedsOnEmptyRegistry) {
+TEST_F(RegistryImplTest, FipsRestrictionSucceedsOnEmptyRegistry) {
+  RegistryImpl registry_impl;
+  EXPECT_THAT(registry_impl.RestrictToFipsIfEmpty(), IsOk());
+}
+
+TEST_F(RegistryImplTest, FipsRestrictionSucceedsWhenSettingMultipleTimes) {
+  RegistryImpl registry_impl;
+  EXPECT_THAT(registry_impl.RestrictToFipsIfEmpty(), IsOk());
+  EXPECT_THAT(registry_impl.RestrictToFipsIfEmpty(), IsOk());
+  EXPECT_THAT(registry_impl.RestrictToFipsIfEmpty(), IsOk());
+}
+
+TEST_F(RegistryImplTest, FipsRestrictionSucceedsIfBuildInFipsMode) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported when Tink is not built in FIPS mode.";
+  }
   RegistryImpl registry_impl;
   EXPECT_THAT(registry_impl.RestrictToFipsIfEmpty(), IsOk());
 }
 
 TEST_F(RegistryImplTest, FipsFailsIfNotEmpty) {
-  if (!FIPS_mode()) {
-    GTEST_SKIP() << "Not supported when BoringSSL is not built in FIPS-mode.";
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
   auto fips_key_manager = absl::make_unique<ExampleKeyTypeManager>();
@@ -2011,8 +1996,8 @@
 
   RegistryImpl registry_impl;
   auto status = registry_impl.RegisterKeyTypeManager<AesGcmKey, AesGcmKeyFormat,
-                                                   List<Aead, AeadVariant>>(
-                  std::move(fips_key_manager), true);
+                                                     List<Aead, AeadVariant>>(
+      std::move(fips_key_manager), true);
   EXPECT_THAT(status, IsOk());
   EXPECT_THAT(registry_impl.RestrictToFipsIfEmpty(),
               StatusIs(absl::StatusCode::kInternal));
diff --git a/cc/internal/rsa_util.cc b/cc/internal/rsa_util.cc
index 62398e0..45c8c17 100644
--- a/cc/internal/rsa_util.cc
+++ b/cc/internal/rsa_util.cc
@@ -15,18 +15,26 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/rsa_util.h"
 
+#include <stddef.h>
+
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
 #include "openssl/bn.h"
+#include "openssl/rsa.h"
 #include "tink/config/tink_fips.h"
 #include "tink/internal/bn_util.h"
 #include "tink/internal/err_util.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/internal/ssl_util.h"
 #include "tink/util/errors.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
diff --git a/cc/internal/rsa_util.h b/cc/internal/rsa_util.h
index fbf9c30..ae0a7ba 100644
--- a/cc/internal/rsa_util.h
+++ b/cc/internal/rsa_util.h
@@ -16,8 +16,11 @@
 #ifndef TINK_INTERNAL_RSA_UTIL_H_
 #define TINK_INTERNAL_RSA_UTIL_H_
 
+#include <stddef.h>
+
 #include <string>
 
+#include "absl/strings/string_view.h"
 #include "openssl/bn.h"
 #include "openssl/rsa.h"
 #include "tink/internal/ssl_unique_ptr.h"
diff --git a/cc/internal/rsa_util_test.cc b/cc/internal/rsa_util_test.cc
index 6b9b5c8..b86cd92 100644
--- a/cc/internal/rsa_util_test.cc
+++ b/cc/internal/rsa_util_test.cc
@@ -15,19 +15,27 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/internal/rsa_util.h"
 
+#include <stddef.h>
+#include <stdint.h>
+
 #include <algorithm>
+#include <iterator>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/status/status.h"
 #include "absl/strings/escaping.h"
+#include "absl/strings/string_view.h"
 #include "openssl/bn.h"
 #include "openssl/rsa.h"
 #include "tink/internal/bn_util.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/subtle/random.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
diff --git a/cc/internal/serialization.h b/cc/internal/serialization.h
new file mode 100644
index 0000000..b392969
--- /dev/null
+++ b/cc/internal/serialization.h
@@ -0,0 +1,53 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_SERIALIZATION_H_
+#define TINK_INTERNAL_SERIALIZATION_H_
+
+#include "absl/strings/string_view.h"
+
+namespace crypto {
+namespace tink {
+
+// Represents either a serialized `Key` or a serialized `Parameters` object.
+//
+// Serialization objects are used within Tink to serialize keys, keysets, and
+// parameters. For each serialization method (e.g., binary protobuf
+// serialization), one subclass of this interface must be defined.
+//
+// This class should eventually be moved to the Tink Public API, but major
+// changes still might be made until then (i.e., don't assume that this API
+// is completely stable yet).
+class Serialization {
+ public:
+  // Identifies which parsing method to use in the registry.
+  //
+  // When registering a parsing function in the registry, one argument will be
+  // this object identifier. When the registry is asked to parse a
+  // `Serialization`, the registry will then dispatch it to the corresponding
+  // method.
+  //
+  // The returned absl::string_view must remain valid for the lifetime of this
+  // `Serialization` object.
+  virtual absl::string_view ObjectIdentifier() const = 0;
+
+  virtual ~Serialization() = default;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_SERIALIZATION_H_
diff --git a/cc/internal/serialization_registry.cc b/cc/internal/serialization_registry.cc
new file mode 100644
index 0000000..4d34473
--- /dev/null
+++ b/cc/internal/serialization_registry.cc
@@ -0,0 +1,140 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/serialization_registry.h"
+
+#include <memory>
+#include <string>
+#include <typeinfo>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_format.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/parser_index.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serializer_index.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+SerializationRegistry::Builder::Builder(const SerializationRegistry& registry)
+    : Builder(registry.parameters_parsers_, registry.parameters_serializers_,
+              registry.key_parsers_, registry.key_serializers_) {}
+
+util::Status SerializationRegistry::Builder::RegisterParametersParser(
+    ParametersParser* parser) {
+  ParserIndex index = parser->Index();
+  auto it = parameters_parsers_.find(index);
+  if (it != parameters_parsers_.end()) {
+    if (parameters_parsers_[index] != parser) {
+      return util::Status(absl::StatusCode::kAlreadyExists,
+                          "Attempted to update existing parameters parser.");
+    }
+  }
+  parameters_parsers_.insert({parser->Index(), parser});
+  return util::OkStatus();
+}
+
+util::Status SerializationRegistry::Builder::RegisterParametersSerializer(
+    ParametersSerializer* serializer) {
+  SerializerIndex index = serializer->Index();
+  auto it = parameters_serializers_.find(index);
+  if (it != parameters_serializers_.end()) {
+    if (parameters_serializers_[index] != serializer) {
+      return util::Status(
+          absl::StatusCode::kAlreadyExists,
+          "Attempted to update existing parameters serializer.");
+    }
+  }
+  parameters_serializers_.insert({serializer->Index(), serializer});
+  return util::OkStatus();
+}
+
+util::Status SerializationRegistry::Builder::RegisterKeyParser(
+    KeyParser* parser) {
+  ParserIndex index = parser->Index();
+  auto it = key_parsers_.find(index);
+  if (it != key_parsers_.end()) {
+    if (key_parsers_[index] != parser) {
+      return util::Status(absl::StatusCode::kAlreadyExists,
+                          "Attempted to update existing key parser.");
+    }
+  }
+  key_parsers_.insert({parser->Index(), parser});
+  return util::OkStatus();
+}
+
+util::Status SerializationRegistry::Builder::RegisterKeySerializer(
+    KeySerializer* serializer) {
+  SerializerIndex index = serializer->Index();
+  auto it = key_serializers_.find(index);
+  if (it != key_serializers_.end()) {
+    if (key_serializers_[index] != serializer) {
+      return util::Status(absl::StatusCode::kAlreadyExists,
+                          "Attempted to update existing key serializer.");
+    }
+  }
+  key_serializers_.insert({serializer->Index(), serializer});
+  return util::OkStatus();
+}
+
+SerializationRegistry SerializationRegistry::Builder::Build() {
+  return SerializationRegistry(parameters_parsers_, parameters_serializers_,
+                               key_parsers_, key_serializers_);
+}
+
+util::StatusOr<std::unique_ptr<Parameters>>
+SerializationRegistry::ParseParameters(
+    const Serialization& serialization) const {
+  ParserIndex index = ParserIndex::Create(serialization);
+  auto it = parameters_parsers_.find(index);
+  if (it == parameters_parsers_.end()) {
+    return util::Status(
+        absl::StatusCode::kNotFound,
+        absl::StrFormat("No parameters parser found for parameters type %s",
+                        typeid(serialization).name()));
+  }
+
+  return parameters_parsers_.at(index)->ParseParameters(serialization);
+}
+
+util::StatusOr<std::unique_ptr<Key>> SerializationRegistry::ParseKey(
+    const Serialization& serialization,
+    absl::optional<SecretKeyAccessToken> token) const {
+  ParserIndex index = ParserIndex::Create(serialization);
+  auto it = key_parsers_.find(index);
+  if (it == key_parsers_.end()) {
+    return util::Status(
+        absl::StatusCode::kNotFound,
+        absl::StrFormat("No key parser found for serialization type %s",
+                        typeid(serialization).name()));
+  }
+
+  return key_parsers_.at(index)->ParseKey(serialization, token);
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/serialization_registry.h b/cc/internal/serialization_registry.h
new file mode 100644
index 0000000..ac90e75
--- /dev/null
+++ b/cc/internal/serialization_registry.h
@@ -0,0 +1,176 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_SERIALIZATION_REGISTRY_H_
+#define TINK_INTERNAL_SERIALIZATION_REGISTRY_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <typeindex>
+#include <typeinfo>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_format.h"
+#include "absl/types/optional.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/parser_index.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serializer_index.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+class SerializationRegistry {
+ public:
+  class Builder {
+   public:
+    // Neither movable nor copyable.
+    Builder(const Builder& other) = delete;
+    Builder& operator=(const Builder& other) = delete;
+
+    // Creates initially empty serialization registry builder.
+    Builder() = default;
+    // Creates serialization registry builder by initially copying entries from
+    // `registry`.
+    explicit Builder(const SerializationRegistry& registry);
+
+    // Registers parameters `parser`. Returns an error if a different parameters
+    // parser has already been registered.
+    util::Status RegisterParametersParser(ParametersParser* parser);
+
+    // Registers parameters `serializer`. Returns an error if a different
+    // parameters serializer has already been registered.
+    util::Status RegisterParametersSerializer(ParametersSerializer* serializer);
+
+    // Registers key `parser`. Returns an error if a different key parser has
+    // already been registered.
+    util::Status RegisterKeyParser(KeyParser* parser);
+
+    // Registers key `serializer`. Returns an error if a different key
+    // serializer has already been registered.
+    util::Status RegisterKeySerializer(KeySerializer* serializer);
+
+    // Creates serialization registry from this builder.
+    SerializationRegistry Build();
+
+   private:
+    Builder(const absl::flat_hash_map<ParserIndex, ParametersParser*>&
+                parameters_parsers,
+            const absl::flat_hash_map<SerializerIndex, ParametersSerializer*>&
+                parameters_serializers,
+            const absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers,
+            const absl::flat_hash_map<SerializerIndex, KeySerializer*>
+                key_serializers)
+        : parameters_parsers_(parameters_parsers),
+          parameters_serializers_(parameters_serializers),
+          key_parsers_(key_parsers),
+          key_serializers_(key_serializers) {}
+
+    absl::flat_hash_map<ParserIndex, ParametersParser*> parameters_parsers_;
+    absl::flat_hash_map<SerializerIndex, ParametersSerializer*>
+        parameters_serializers_;
+    absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers_;
+    absl::flat_hash_map<SerializerIndex, KeySerializer*> key_serializers_;
+  };
+
+  // Movable and copyable.
+  SerializationRegistry(SerializationRegistry&& other) = default;
+  SerializationRegistry& operator=(SerializationRegistry&& other) = default;
+  SerializationRegistry(const SerializationRegistry& other) = default;
+  SerializationRegistry& operator=(const SerializationRegistry& other) =
+      default;
+
+  // Creates empty serialization registry.
+  SerializationRegistry() = default;
+
+  // Parses `serialization` into a `Parameters` instance.
+  util::StatusOr<std::unique_ptr<Parameters>> ParseParameters(
+      const Serialization& serialization) const;
+
+  // Serializes `parameters` into a `Serialization` instance.
+  template <typename SerializationT>
+  util::StatusOr<std::unique_ptr<Serialization>> SerializeParameters(
+      const Parameters& parameters) const {
+    SerializerIndex index = SerializerIndex::Create<SerializationT>(parameters);
+    auto it = parameters_serializers_.find(index);
+    if (it == parameters_serializers_.end()) {
+      return util::Status(
+          absl::StatusCode::kNotFound,
+          absl::StrFormat(
+              "No parameters serializer found for parameters type %s",
+              typeid(parameters).name()));
+    }
+
+    return parameters_serializers_.at(index)->SerializeParameters(parameters);
+  }
+
+  // Parses `serialization` into a `Key` instance.
+  util::StatusOr<std::unique_ptr<Key>> ParseKey(
+      const Serialization& serialization,
+      absl::optional<SecretKeyAccessToken> token) const;
+
+  // Serializes `parameters` into a `Serialization` instance.
+  template <typename SerializationT>
+  util::StatusOr<std::unique_ptr<Serialization>> SerializeKey(
+      const Key& key, absl::optional<SecretKeyAccessToken> token) const {
+    SerializerIndex index = SerializerIndex::Create<SerializationT>(key);
+    auto it = key_serializers_.find(index);
+    if (it == key_serializers_.end()) {
+      return util::Status(
+          absl::StatusCode::kNotFound,
+          absl::StrFormat("No key serializer found for key type %s",
+                          typeid(key).name()));
+    }
+
+    return key_serializers_.at(index)->SerializeKey(key, token);
+  }
+
+ private:
+  SerializationRegistry(
+      const absl::flat_hash_map<ParserIndex, ParametersParser*>&
+          parameters_parsers,
+      const absl::flat_hash_map<SerializerIndex, ParametersSerializer*>&
+          parameters_serializers,
+      const absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers,
+      const absl::flat_hash_map<SerializerIndex, KeySerializer*>
+          key_serializers)
+      : parameters_parsers_(parameters_parsers),
+        parameters_serializers_(parameters_serializers),
+        key_parsers_(key_parsers),
+        key_serializers_(key_serializers) {}
+
+  absl::flat_hash_map<ParserIndex, ParametersParser*> parameters_parsers_;
+  absl::flat_hash_map<SerializerIndex, ParametersSerializer*>
+      parameters_serializers_;
+  absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers_;
+  absl::flat_hash_map<SerializerIndex, KeySerializer*> key_serializers_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_SERIALIZATION_REGISTRY_H_
diff --git a/cc/internal/serialization_registry_test.cc b/cc/internal/serialization_registry_test.cc
new file mode 100644
index 0000000..0e0872b
--- /dev/null
+++ b/cc/internal/serialization_registry_test.cc
@@ -0,0 +1,415 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/serialization_registry.h"
+
+#include <memory>
+#include <string_view>
+#include <typeindex>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/serialization.h"
+#include "tink/internal/serialization_test_util.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
+TEST(SerializationRegistryTest, ParseParameters) {
+  SerializationRegistry::Builder builder;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser1(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+  ParametersParserImpl<IdParamsSerialization, IdParams> parser2(kIdTypeUrl,
+                                                                ParseIdParams);
+  ASSERT_THAT(builder.RegisterParametersParser(&parser1), IsOk());
+  ASSERT_THAT(builder.RegisterParametersParser(&parser2), IsOk());
+
+  SerializationRegistry registry = builder.Build();
+
+  util::StatusOr<std::unique_ptr<Parameters>> no_id_params =
+      registry.ParseParameters(NoIdSerialization());
+  ASSERT_THAT(no_id_params, IsOk());
+  EXPECT_THAT((*no_id_params)->HasIdRequirement(), IsFalse());
+  EXPECT_THAT(std::type_index(typeid(**no_id_params)),
+              std::type_index(typeid(NoIdParams)));
+
+  util::StatusOr<std::unique_ptr<Parameters>> id_params =
+      registry.ParseParameters(IdParamsSerialization());
+  ASSERT_THAT(id_params, IsOk());
+  EXPECT_THAT((*id_params)->HasIdRequirement(), IsTrue());
+  EXPECT_THAT(std::type_index(typeid(**id_params)),
+              std::type_index(typeid(IdParams)));
+}
+
+TEST(SerializationRegistryTest, ParseParametersWithoutRegistration) {
+  SerializationRegistry::Builder builder;
+  SerializationRegistry registry = builder.Build();
+
+  ASSERT_THAT(registry.ParseParameters(NoIdSerialization()).status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(SerializationRegistryTest, RegisterSameParametersParser) {
+  SerializationRegistry::Builder builder;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser(kNoIdTypeUrl,
+                                                             ParseNoIdParams);
+
+  EXPECT_THAT(builder.RegisterParametersParser(&parser), IsOk());
+  EXPECT_THAT(builder.RegisterParametersParser(&parser), IsOk());
+}
+
+TEST(SerializationRegistryTest,
+     RegisterDifferentParametersParserWithSameIndex) {
+  SerializationRegistry::Builder builder;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser1(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser2(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+
+  EXPECT_THAT(builder.RegisterParametersParser(&parser1), IsOk());
+  EXPECT_THAT(builder.RegisterParametersParser(&parser2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(SerializationRegistryTest, SerializeParameters) {
+  SerializationRegistry::Builder builder;
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer1(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  ParametersSerializerImpl<IdParams, IdParamsSerialization> serializer2(
+      kIdTypeUrl, SerializeIdParams);
+  ASSERT_THAT(builder.RegisterParametersSerializer(&serializer1), IsOk());
+  ASSERT_THAT(builder.RegisterParametersSerializer(&serializer2), IsOk());
+
+  SerializationRegistry registry = builder.Build();
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization1 =
+      registry.SerializeParameters<NoIdSerialization>(NoIdParams());
+  ASSERT_THAT(serialization1, IsOk());
+  EXPECT_THAT((*serialization1)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization2 =
+      registry.SerializeParameters<IdParamsSerialization>(IdParams());
+  ASSERT_THAT(serialization2, IsOk());
+  EXPECT_THAT((*serialization2)->ObjectIdentifier(), Eq(kIdTypeUrl));
+}
+
+TEST(SerializationRegistryTest, SerializeParametersWithoutRegistration) {
+  SerializationRegistry::Builder builder;
+  SerializationRegistry registry = builder.Build();
+
+  ASSERT_THAT(
+      registry.SerializeParameters<NoIdSerialization>(NoIdParams()).status(),
+      StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(SerializationRegistryTest, RegisterSameParametersSerializer) {
+  SerializationRegistry::Builder builder;
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer(
+      kNoIdTypeUrl, SerializeNoIdParams);
+
+  EXPECT_THAT(builder.RegisterParametersSerializer(&serializer), IsOk());
+  EXPECT_THAT(builder.RegisterParametersSerializer(&serializer), IsOk());
+}
+
+TEST(SerializationRegistryTest,
+     RegisterDifferentParametersSerializerWithSameIndex) {
+  SerializationRegistry::Builder builder;
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer1(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer2(
+      kNoIdTypeUrl, SerializeNoIdParams);
+
+  EXPECT_THAT(builder.RegisterParametersSerializer(&serializer1), IsOk());
+  EXPECT_THAT(builder.RegisterParametersSerializer(&serializer2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(SerializationRegistryTest, ParseKey) {
+  SerializationRegistry::Builder builder;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser1(kNoIdTypeUrl, ParseNoIdKey);
+  KeyParserImpl<IdKeySerialization, IdKey> parser2(kIdTypeUrl, ParseIdKey);
+  ASSERT_THAT(builder.RegisterKeyParser(&parser1), IsOk());
+  ASSERT_THAT(builder.RegisterKeyParser(&parser2), IsOk());
+
+  SerializationRegistry registry = builder.Build();
+
+  util::StatusOr<std::unique_ptr<Key>> no_id_key =
+      registry.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(no_id_key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**no_id_key)),
+              std::type_index(typeid(NoIdKey)));
+
+  util::StatusOr<std::unique_ptr<Key>> id_key = registry.ParseKey(
+      IdKeySerialization(/*id=*/123), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(id_key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**id_key)),
+              std::type_index(typeid(IdKey)));
+  EXPECT_THAT((*id_key)->GetIdRequirement(), Eq(123));
+}
+
+TEST(SerializationRegistryTest, ParseKeyNoSecretAccess) {
+  SerializationRegistry::Builder builder;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser(kNoIdTypeUrl, ParseNoIdKey);
+  ASSERT_THAT(builder.RegisterKeyParser(&parser), IsOk());
+
+  SerializationRegistry registry = builder.Build();
+
+  util::StatusOr<std::unique_ptr<Key>> no_id_public_key =
+      registry.ParseKey(NoIdSerialization(), absl::nullopt);
+  ASSERT_THAT(no_id_public_key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**no_id_public_key)),
+              std::type_index(typeid(NoIdKey)));
+}
+
+TEST(SerializationRegistryTest, ParseKeyWithoutRegistration) {
+  SerializationRegistry::Builder builder;
+  SerializationRegistry registry = builder.Build();
+
+  ASSERT_THAT(
+      registry.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get())
+          .status(),
+      StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(SerializationRegistryTest, RegisterSameKeyParser) {
+  SerializationRegistry::Builder builder;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser(kNoIdTypeUrl, ParseNoIdKey);
+
+  EXPECT_THAT(builder.RegisterKeyParser(&parser), IsOk());
+  EXPECT_THAT(builder.RegisterKeyParser(&parser), IsOk());
+}
+
+TEST(SerializationRegistryTest, RegisterDifferentKeyParserWithSameIndex) {
+  SerializationRegistry::Builder builder;
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser1(kNoIdTypeUrl, ParseNoIdKey);
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser2(kNoIdTypeUrl, ParseNoIdKey);
+
+  EXPECT_THAT(builder.RegisterKeyParser(&parser1), IsOk());
+  EXPECT_THAT(builder.RegisterKeyParser(&parser2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(SerializationRegistryTest, SerializeKey) {
+  SerializationRegistry::Builder builder;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer1(SerializeNoIdKey);
+  KeySerializerImpl<IdKey, IdKeySerialization> serializer2(SerializeIdKey);
+  ASSERT_THAT(builder.RegisterKeySerializer(&serializer1), IsOk());
+  ASSERT_THAT(builder.RegisterKeySerializer(&serializer2), IsOk());
+
+  SerializationRegistry registry = builder.Build();
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization1 =
+      registry.SerializeKey<NoIdSerialization>(NoIdKey(),
+                                               InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization1, IsOk());
+  EXPECT_THAT((*serialization1)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization2 =
+      registry.SerializeKey<IdKeySerialization>(IdKey(123),
+                                                InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization2, IsOk());
+  EXPECT_THAT((*serialization2)->ObjectIdentifier(), Eq(kIdTypeUrl));
+}
+
+TEST(SerializationRegistryTest, SerializeKeyNoSecretAccess) {
+  SerializationRegistry::Builder builder;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer(SerializeNoIdKey);
+  ASSERT_THAT(builder.RegisterKeySerializer(&serializer), IsOk());
+
+  SerializationRegistry registry = builder.Build();
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      registry.SerializeKey<NoIdSerialization>(NoIdKey(),
+                                               absl::nullopt);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+TEST(SerializationRegistryTest, SerializeKeyWithoutRegistration) {
+  SerializationRegistry::Builder builder;
+  SerializationRegistry registry = builder.Build();
+
+  ASSERT_THAT(registry
+                  .SerializeKey<NoIdSerialization>(
+                      NoIdKey(), InsecureSecretKeyAccess::Get())
+                  .status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+TEST(SerializationRegistryTest, RegisterSameKeySerializer) {
+  SerializationRegistry::Builder builder;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer(SerializeNoIdKey);
+
+  EXPECT_THAT(builder.RegisterKeySerializer(&serializer), IsOk());
+  EXPECT_THAT(builder.RegisterKeySerializer(&serializer), IsOk());
+}
+
+TEST(SerializationRegistryTest, RegisterDifferentKeySerializerWithSameIndex) {
+  SerializationRegistry::Builder builder;
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer1(SerializeNoIdKey);
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer2(SerializeNoIdKey);
+
+  EXPECT_THAT(builder.RegisterKeySerializer(&serializer1), IsOk());
+  EXPECT_THAT(builder.RegisterKeySerializer(&serializer2),
+              StatusIs(absl::StatusCode::kAlreadyExists));
+}
+
+TEST(SerializationRegistryTest, BuiltFromAnotherRegistry) {
+  SerializationRegistry::Builder builder1;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser1(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer1(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  ASSERT_THAT(builder1.RegisterParametersParser(&parser1), IsOk());
+  ASSERT_THAT(builder1.RegisterParametersSerializer(&serializer1), IsOk());
+
+  SerializationRegistry registry1 = builder1.Build();
+  SerializationRegistry::Builder builder2(registry1);
+
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser2(kNoIdTypeUrl, ParseNoIdKey);
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer2(SerializeNoIdKey);
+  ASSERT_THAT(builder2.RegisterKeyParser(&parser2), IsOk());
+  ASSERT_THAT(builder2.RegisterKeySerializer(&serializer2), IsOk());
+
+  SerializationRegistry registry2 = builder2.Build();
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      registry2.ParseParameters(NoIdSerialization());
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), IsFalse());
+  EXPECT_THAT(std::type_index(typeid(**params)),
+              std::type_index(typeid(NoIdParams)));
+
+  util::StatusOr<std::unique_ptr<Serialization>> params_serialization =
+      registry2.SerializeParameters<NoIdSerialization>(NoIdParams());
+  ASSERT_THAT(params_serialization, IsOk());
+  EXPECT_THAT((*params_serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      registry2.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**key)), std::type_index(typeid(NoIdKey)));
+
+  util::StatusOr<std::unique_ptr<Serialization>> key_serialization =
+      registry2.SerializeKey<NoIdSerialization>(NoIdKey(),
+                                                InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key_serialization, IsOk());
+  EXPECT_THAT((*key_serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+TEST(SerializationRegistryTest, RegistryCopy) {
+  SerializationRegistry::Builder builder;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser1(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer1(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser2(kNoIdTypeUrl, ParseNoIdKey);
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer2(SerializeNoIdKey);
+  ASSERT_THAT(builder.RegisterParametersParser(&parser1), IsOk());
+  ASSERT_THAT(builder.RegisterParametersSerializer(&serializer1), IsOk());
+  ASSERT_THAT(builder.RegisterKeyParser(&parser2), IsOk());
+  ASSERT_THAT(builder.RegisterKeySerializer(&serializer2), IsOk());
+
+  SerializationRegistry registry1 = builder.Build();
+  SerializationRegistry registry2 = registry1;
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      registry2.ParseParameters(NoIdSerialization());
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), IsFalse());
+  EXPECT_THAT(std::type_index(typeid(**params)),
+              std::type_index(typeid(NoIdParams)));
+
+  util::StatusOr<std::unique_ptr<Serialization>> params_serialization =
+      registry2.SerializeParameters<NoIdSerialization>(NoIdParams());
+  ASSERT_THAT(params_serialization, IsOk());
+  EXPECT_THAT((*params_serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      registry2.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**key)), std::type_index(typeid(NoIdKey)));
+
+  util::StatusOr<std::unique_ptr<Serialization>> key_serialization =
+      registry2.SerializeKey<NoIdSerialization>(NoIdKey(),
+                                                InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key_serialization, IsOk());
+  EXPECT_THAT((*key_serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+TEST(SerializationRegistryTest, RegistryMove) {
+  SerializationRegistry::Builder builder;
+  ParametersParserImpl<NoIdSerialization, NoIdParams> parser1(kNoIdTypeUrl,
+                                                              ParseNoIdParams);
+  ParametersSerializerImpl<NoIdParams, NoIdSerialization> serializer1(
+      kNoIdTypeUrl, SerializeNoIdParams);
+  KeyParserImpl<NoIdSerialization, NoIdKey> parser2(kNoIdTypeUrl, ParseNoIdKey);
+  KeySerializerImpl<NoIdKey, NoIdSerialization> serializer2(SerializeNoIdKey);
+  ASSERT_THAT(builder.RegisterParametersParser(&parser1), IsOk());
+  ASSERT_THAT(builder.RegisterParametersSerializer(&serializer1), IsOk());
+  ASSERT_THAT(builder.RegisterKeyParser(&parser2), IsOk());
+  ASSERT_THAT(builder.RegisterKeySerializer(&serializer2), IsOk());
+
+  SerializationRegistry registry1 = builder.Build();
+  SerializationRegistry registry2 = std::move(registry1);
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      registry2.ParseParameters(NoIdSerialization());
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), IsFalse());
+  EXPECT_THAT(std::type_index(typeid(**params)),
+              std::type_index(typeid(NoIdParams)));
+
+  util::StatusOr<std::unique_ptr<Serialization>> params_serialization =
+      registry2.SerializeParameters<NoIdSerialization>(NoIdParams());
+  ASSERT_THAT(params_serialization, IsOk());
+  EXPECT_THAT((*params_serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      registry2.ParseKey(NoIdSerialization(), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT(std::type_index(typeid(**key)), std::type_index(typeid(NoIdKey)));
+
+  util::StatusOr<std::unique_ptr<Serialization>> key_serialization =
+      registry2.SerializeKey<NoIdSerialization>(NoIdKey(),
+                                                InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key_serialization, IsOk());
+  EXPECT_THAT((*key_serialization)->ObjectIdentifier(), Eq(kNoIdTypeUrl));
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/serialization_test_util.h b/cc/internal/serialization_test_util.h
new file mode 100644
index 0000000..b772a0a
--- /dev/null
+++ b/cc/internal/serialization_test_util.h
@@ -0,0 +1,189 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_SERIALIZATION_TEST_UTIL_H_
+#define TINK_INTERNAL_SERIALIZATION_TEST_UTIL_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "tink/internal/serialization.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+constexpr absl::string_view kNoIdTypeUrl = "NoIdTypeUrl";
+constexpr absl::string_view kIdTypeUrl = "IdTypeUrl";
+
+// Generic serialization for keys or parameters.
+class BaseSerialization : public Serialization {
+ public:
+  explicit BaseSerialization(absl::string_view object_identifier)
+      : object_identifier_(object_identifier) {}
+
+  absl::string_view ObjectIdentifier() const override {
+    return object_identifier_;
+  }
+
+  bool operator==(const BaseSerialization& other) const {
+    return object_identifier_ == other.object_identifier_;
+  }
+
+ private:
+  std::string object_identifier_;
+};
+
+// Serialization for keys or parameters without an ID requirement.
+class NoIdSerialization : public BaseSerialization {
+ public:
+  NoIdSerialization() : BaseSerialization(kNoIdTypeUrl) {}
+};
+
+// Serialization for parameters with an ID requirement.
+class IdParamsSerialization : public BaseSerialization {
+ public:
+  IdParamsSerialization() : BaseSerialization(kIdTypeUrl) {}
+};
+
+// Serialization for keys with an ID requirement.
+class IdKeySerialization : public BaseSerialization {
+ public:
+  explicit IdKeySerialization(int id)
+      : BaseSerialization(kIdTypeUrl), id_(id) {}
+
+  int GetKeyId() const { return id_; }
+
+ private:
+  int id_;
+};
+
+// Parameters without an ID requirement.
+class NoIdParams : public Parameters {
+ public:
+  bool HasIdRequirement() const override { return false; }
+
+  bool operator==(const Parameters& other) const override {
+    return !other.HasIdRequirement();
+  }
+};
+
+// Key without an ID requirement.
+class NoIdKey : public Key {
+ public:
+  const Parameters& GetParameters() const override { return params_; }
+
+  absl::optional<int> GetIdRequirement() const override {
+    return absl::nullopt;
+  }
+
+  bool operator==(const Key& other) const override {
+    return params_ == other.GetParameters() &&
+           absl::nullopt == other.GetIdRequirement();
+  }
+
+ private:
+  NoIdParams params_;
+};
+
+// Parameters with an ID requirement.
+class IdParams : public Parameters {
+ public:
+  bool HasIdRequirement() const override { return true; }
+
+  bool operator==(const Parameters& other) const override {
+    return other.HasIdRequirement();
+  }
+};
+
+// Key with an ID requirement.
+class IdKey : public Key {
+ public:
+  explicit IdKey(int id) : id_(id) {}
+
+  const Parameters& GetParameters() const override { return params_; }
+
+  absl::optional<int> GetIdRequirement() const override { return id_; }
+
+  bool operator==(const Key& other) const override {
+    return params_ == other.GetParameters() && id_ == other.GetIdRequirement();
+  }
+
+ private:
+  IdParams params_;
+  int id_;
+};
+
+// Parse `serialization` into parameters without an ID requirement.
+inline util::StatusOr<NoIdParams> ParseNoIdParams(
+    NoIdSerialization serialization) {
+  return NoIdParams();
+}
+
+// Parse `serialization` into parameters with an ID requirement.
+inline util::StatusOr<IdParams> ParseIdParams(
+    IdParamsSerialization serialization) {
+  return IdParams();
+}
+
+// Serialize `parameters` without an ID requirement.
+inline util::StatusOr<NoIdSerialization> SerializeNoIdParams(
+    NoIdParams parameters) {
+  return NoIdSerialization();
+}
+
+// Serialize `parameters` with an ID requirement.
+inline util::StatusOr<IdParamsSerialization> SerializeIdParams(
+    IdParams parameters) {
+  return IdParamsSerialization();
+}
+
+// Parse `serialization` into a key without an ID requirement.
+inline util::StatusOr<NoIdKey> ParseNoIdKey(
+    NoIdSerialization serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  return NoIdKey();
+}
+
+// Parse `serialization` into a key with an ID requirement.
+inline util::StatusOr<IdKey> ParseIdKey(
+    IdKeySerialization serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  return IdKey(serialization.GetKeyId());
+}
+
+// Serialize `key` without an ID requirement.
+inline util::StatusOr<NoIdSerialization> SerializeNoIdKey(
+    NoIdKey key, absl::optional<SecretKeyAccessToken> token) {
+  return NoIdSerialization();
+}
+
+// Serialize `key` with an ID requirement.
+inline util::StatusOr<IdKeySerialization> SerializeIdKey(
+    IdKey key, absl::optional<SecretKeyAccessToken> token) {
+  return IdKeySerialization(key.GetIdRequirement().value());
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_SERIALIZATION_TEST_UTIL_H_
diff --git a/cc/internal/serialization_test_util_test.cc b/cc/internal/serialization_test_util_test.cc
new file mode 100644
index 0000000..1309af8
--- /dev/null
+++ b/cc/internal/serialization_test_util_test.cc
@@ -0,0 +1,116 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/serialization_test_util.h"
+
+#include <string_view>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/parameters.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOkAndHolds;
+using ::testing::Eq;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+using ::testing::Not;
+
+TEST(SerializationTest, Create) {
+  EXPECT_THAT(BaseSerialization("base_type_url").ObjectIdentifier(),
+              Eq("base_type_url"));
+  EXPECT_THAT(NoIdSerialization().ObjectIdentifier(), Eq(kNoIdTypeUrl));
+  EXPECT_THAT(IdParamsSerialization().ObjectIdentifier(), Eq(kIdTypeUrl));
+
+  IdKeySerialization id_key(123);
+  EXPECT_THAT(id_key.ObjectIdentifier(), Eq(kIdTypeUrl));
+  EXPECT_THAT(id_key.GetKeyId(), Eq(123));
+}
+
+TEST(NoIdParamsTest, Create) {
+  NoIdParams params;
+
+  EXPECT_THAT(params.HasIdRequirement(), IsFalse());
+  EXPECT_THAT(params, Eq(NoIdParams()));
+  EXPECT_THAT(params, Not(Eq(IdParams())));
+}
+
+TEST(NoIdParamsTest, ParseAndSerialize) {
+  EXPECT_THAT(ParseNoIdParams(NoIdSerialization()), IsOkAndHolds(NoIdParams()));
+  EXPECT_THAT(SerializeNoIdParams(NoIdParams()),
+              IsOkAndHolds(NoIdSerialization()));
+}
+
+TEST(IdParamsTest, Create) {
+  IdParams params;
+
+  EXPECT_THAT(params.HasIdRequirement(), IsTrue());
+  EXPECT_THAT(params, Eq(IdParams()));
+  EXPECT_THAT(params, Not(Eq(NoIdParams())));
+}
+
+TEST(IdParamsTest, ParseAndSerialize) {
+  EXPECT_THAT(ParseIdParams(IdParamsSerialization()), IsOkAndHolds(IdParams()));
+  EXPECT_THAT(SerializeIdParams(IdParams()),
+              IsOkAndHolds(IdParamsSerialization()));
+}
+
+TEST(NoIdKeyTest, Create) {
+  NoIdKey key;
+
+  EXPECT_THAT(key.GetIdRequirement(), Eq(absl::nullopt));
+  EXPECT_THAT(key.GetParameters(), Eq(NoIdParams()));
+  EXPECT_THAT(key, Eq(NoIdKey()));
+  EXPECT_THAT(key, Not(Eq(IdKey(123))));
+}
+
+TEST(NoIdKeyTest, ParseAndSerialize) {
+  EXPECT_THAT(ParseNoIdKey(NoIdSerialization(), InsecureSecretKeyAccess::Get()),
+              IsOkAndHolds(NoIdKey()));
+  EXPECT_THAT(SerializeNoIdKey(NoIdKey(), InsecureSecretKeyAccess::Get()),
+              IsOkAndHolds(NoIdSerialization()));
+}
+
+TEST(IdKeyTest, Create) {
+  IdKey key(123);
+
+  EXPECT_THAT(key.GetIdRequirement(), Eq(123));
+  EXPECT_THAT(key.GetParameters(), Eq(IdParams()));
+  EXPECT_THAT(key, Eq(IdKey(123)));
+  EXPECT_THAT(key, Not(Eq(IdKey(456))));
+  EXPECT_THAT(key, Not(Eq(NoIdKey())));
+}
+
+TEST(IdKeyTest, ParseAndSerialize) {
+  EXPECT_THAT(ParseIdKey(IdKeySerialization(123),
+                         InsecureSecretKeyAccess::Get()),
+              IsOkAndHolds(IdKey(123)));
+  EXPECT_THAT(SerializeIdKey(IdKey(123), InsecureSecretKeyAccess::Get()),
+              IsOkAndHolds(IdKeySerialization(123)));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/serializer_index.h b/cc/internal/serializer_index.h
new file mode 100644
index 0000000..e0c4e66
--- /dev/null
+++ b/cc/internal/serializer_index.h
@@ -0,0 +1,82 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTERNAL_SERIALIZER_INDEX_H_
+#define TINK_INTERNAL_SERIALIZER_INDEX_H_
+
+#include <string>
+#include <typeindex>
+
+#include "tink/internal/serialization.h"
+#include "tink/key.h"
+#include "tink/parameters.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+class SerializerIndex {
+ public:
+  // Create registry lookup key for the combination of the `KeyOrParameterT` and
+  // `SerializationT` types. Useful for key and parameters serializers.
+  template <typename KeyOrParameterT, typename SerializationT>
+  static SerializerIndex Create() {
+    return SerializerIndex(std::type_index(typeid(KeyOrParameterT)),
+                           std::type_index(typeid(SerializationT)));
+  }
+
+  // Create registry lookup key for `SerializationT` type and `parameters`.
+  // Useful for the serialization registry.
+  template <typename SerializationT>
+  static SerializerIndex Create(const Parameters& parameters) {
+    return SerializerIndex(std::type_index(typeid(parameters)),
+                           std::type_index(typeid(SerializationT)));
+  }
+
+  // Create registry lookup key for `SerializationT` type and `key`. Useful for
+  // the serialization registry.
+  template <typename SerializationT>
+  static SerializerIndex Create(const Key& key) {
+    return SerializerIndex(std::type_index(typeid(key)),
+                           std::type_index(typeid(SerializationT)));
+  }
+
+  // Returns true if key/parameters index and serialization type index match.
+  bool operator==(const SerializerIndex& other) const {
+    return kp_index_ == other.kp_index_ &&
+           serialization_index_ == other.serialization_index_;
+  }
+
+  // Required function to make `SerializerIndex` hashable for Abseil hash maps.
+  template <typename H>
+  friend H AbslHashValue(H h, const SerializerIndex& index) {
+    return H::combine(std::move(h), index.kp_index_,
+                      index.serialization_index_);
+  }
+
+ private:
+  SerializerIndex(std::type_index kp_index, std::type_index serialization_index)
+      : kp_index_(kp_index), serialization_index_(serialization_index) {}
+
+  std::type_index kp_index_;
+  std::type_index serialization_index_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_SERIALIZER_INDEX_H_
diff --git a/cc/internal/serializer_index_test.cc b/cc/internal/serializer_index_test.cc
new file mode 100644
index 0000000..9653ee6
--- /dev/null
+++ b/cc/internal/serializer_index_test.cc
@@ -0,0 +1,91 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/internal/serializer_index.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/internal/serialization_test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::testing::Eq;
+using ::testing::Not;
+
+TEST(SerializerIndex, CreateEquivalentFromParameters) {
+  // Multi-parameter templates require extra surrounding parentheses.
+  ASSERT_THAT((SerializerIndex::Create<NoIdParams, NoIdSerialization>()),
+              Eq((SerializerIndex::Create<NoIdParams, NoIdSerialization>())));
+  ASSERT_THAT((SerializerIndex::Create<NoIdParams, NoIdSerialization>()),
+              Eq((SerializerIndex::Create<NoIdSerialization>(NoIdParams()))));
+  ASSERT_THAT((SerializerIndex::Create<NoIdSerialization>(NoIdParams())),
+              Eq((SerializerIndex::Create<NoIdSerialization>(NoIdParams()))));
+}
+
+TEST(SerializerIndex, CreateFromDifferentParametersType) {
+  // Multi-parameter templates require extra surrounding parentheses.
+  ASSERT_THAT(
+      (SerializerIndex::Create<NoIdParams, NoIdSerialization>()),
+      Not(Eq((SerializerIndex::Create<IdParams, NoIdSerialization>()))));
+  ASSERT_THAT(
+      (SerializerIndex::Create<NoIdSerialization>(NoIdParams())),
+      Not(Eq((SerializerIndex::Create<NoIdSerialization>(IdParams())))));
+}
+
+TEST(SerializerIndex, CreateFromSameParametersTypeWithDifferentSerialization) {
+  // Multi-parameter templates require extra surrounding parentheses.
+  ASSERT_THAT(
+      (SerializerIndex::Create<NoIdParams, NoIdSerialization>()),
+      Not(Eq((SerializerIndex::Create<NoIdParams, IdParamsSerialization>()))));
+  ASSERT_THAT(
+      (SerializerIndex::Create<NoIdSerialization>(NoIdParams())),
+      Not(Eq((SerializerIndex::Create<IdParamsSerialization>(NoIdParams())))));
+}
+
+TEST(SerializerIndex, CreateEquivalentFromKey) {
+  // Multi-parameter templates require extra surrounding parentheses.
+  ASSERT_THAT((SerializerIndex::Create<NoIdKey, NoIdSerialization>()),
+              Eq((SerializerIndex::Create<NoIdKey, NoIdSerialization>())));
+  ASSERT_THAT((SerializerIndex::Create<NoIdKey, NoIdSerialization>()),
+              Eq((SerializerIndex::Create<NoIdSerialization>(NoIdKey()))));
+  ASSERT_THAT((SerializerIndex::Create<NoIdSerialization>(NoIdKey())),
+              Eq((SerializerIndex::Create<NoIdSerialization>(NoIdKey()))));
+}
+
+TEST(SerializerIndex, CreateFromDifferentKeyType) {
+  // Multi-parameter templates require extra surrounding parentheses.
+  ASSERT_THAT((SerializerIndex::Create<NoIdKey, NoIdSerialization>()),
+              Not(Eq((SerializerIndex::Create<IdKey, NoIdSerialization>()))));
+  ASSERT_THAT(
+      (SerializerIndex::Create<NoIdSerialization>(NoIdKey())),
+      Not(Eq((SerializerIndex::Create<NoIdSerialization>(IdKey(/*id=*/1))))));
+}
+
+TEST(SerializerIndex, CreateFromSameKeyTypeWithDifferentSerialization) {
+  // Multi-parameter templates require extra surrounding parentheses.
+  ASSERT_THAT(
+      (SerializerIndex::Create<NoIdKey, NoIdSerialization>()),
+      Not(Eq((SerializerIndex::Create<NoIdKey, IdKeySerialization>()))));
+  ASSERT_THAT(
+      (SerializerIndex::Create<NoIdSerialization>(NoIdKey())),
+      Not(Eq((SerializerIndex::Create<IdKeySerialization>(NoIdKey())))));
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/ssl_unique_ptr.h b/cc/internal/ssl_unique_ptr.h
index d7c0586..7bc6314 100644
--- a/cc/internal/ssl_unique_ptr.h
+++ b/cc/internal/ssl_unique_ptr.h
@@ -17,6 +17,7 @@
 #define TINK_INTERNAL_SSL_UNIQUE_PTR_H_
 
 #include <memory>
+
 // Every header in BoringSSL includes base.h, which in turn defines
 // OPENSSL_IS_BORINGSSL. So we include this common header here to "force" the
 // definition of OPENSSL_IS_BORINGSSL in case BoringSSL is used.
diff --git a/cc/internal/test_file_util.cc b/cc/internal/test_file_util.cc
new file mode 100644
index 0000000..bdb9374
--- /dev/null
+++ b/cc/internal/test_file_util.cc
@@ -0,0 +1,77 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/test_file_util.h"
+
+#include <fstream>
+#include <ios>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/log/check.h"
+#include "absl/status/status.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "tink/subtle/random.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+util::Status CreateTestFile(absl::string_view filename,
+                            absl::string_view file_content) {
+  std::string full_filename = absl::StrCat(test::TmpDir(), "/", filename);
+  std::ofstream output_stream(full_filename, std::ios::binary);
+  if (!output_stream) {
+    return util::Status(absl::StatusCode::kInternal, "Cannot open file");
+  }
+  output_stream.write(file_content.data(), file_content.size());
+  return util::OkStatus();
+}
+
+std::string GetTestFileNamePrefix() {
+  const testing::TestInfo* const test_info =
+      testing::UnitTest::GetInstance()->current_test_info();
+  CHECK(test_info != nullptr);
+  std::string random_string = subtle::Random::GetRandomBytes(/*length=*/16);
+  std::string test_suite_name = test_info->test_suite_name();
+  std::string test_name = test_info->name();
+  // Parametrized tests return test_suite_name of the form <Prefix>/<Test Suite>
+  // and name of the form <Test Name>/<Suffix>.
+  // In this case, get only the prefix and test name. Keeping all of these may
+  // result in a file name that is too long.
+  if (test_info->value_param() != nullptr) {
+    std::vector<std::string> test_suite_parts =
+        absl::StrSplit(test_info->test_suite_name(), '/');
+    CHECK_GE(test_suite_parts.size(), 1);
+    test_suite_name = test_suite_parts[0];
+    std::vector<std::string> test_name_parts =
+        absl::StrSplit(test_info->name(), '/');
+    CHECK_GE(test_name_parts.size(), 1);
+    test_name = test_name_parts[0];
+  }
+  return absl::StrCat(test_suite_name, "_", test_name, "_",
+                      absl::BytesToHexString(random_string));
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/test_file_util.h b/cc/internal/test_file_util.h
index 9f37a56..f3e37aa 100644
--- a/cc/internal/test_file_util.h
+++ b/cc/internal/test_file_util.h
@@ -20,6 +20,7 @@
 #include <string>
 
 #include "absl/strings/string_view.h"
+#include "tink/util/status.h"
 
 namespace crypto {
 namespace tink {
@@ -33,6 +34,13 @@
 // Returns the path of the specified file in the runfiles directory.
 std::string RunfilesPath(absl::string_view path);
 
+crypto::tink::util::Status CreateTestFile(absl::string_view filename,
+                                          absl::string_view file_content);
+
+// Returns the prefix to use for files to use in tests. The result will be of
+// the form: <test name>_<testcase name>_<hex encoded random 32 bytes string>.
+std::string GetTestFileNamePrefix();
+
 }  // namespace internal
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/internal/test_random_access_stream.cc b/cc/internal/test_random_access_stream.cc
new file mode 100644
index 0000000..53918d8
--- /dev/null
+++ b/cc/internal/test_random_access_stream.cc
@@ -0,0 +1,97 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/test_random_access_stream.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "tink/random_access_stream.h"
+#include "tink/util/buffer.h"
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+util::Status TestRandomAccessStream::PRead(int64_t position, int count,
+                                           util::Buffer* dest_buffer) {
+  if (dest_buffer == nullptr) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "dest_buffer must be non-null");
+  }
+  if (count <= 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "count must be positive");
+  }
+  if (count > dest_buffer->allocated_size()) {
+    return util::Status(absl::StatusCode::kInvalidArgument, "buffer too small");
+  }
+  if (position < 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "position cannot be negative");
+  }
+  if (position >= content_.size()) {
+    dest_buffer->set_size(0).IgnoreError();
+    return util::Status(absl::StatusCode::kOutOfRange, "EOF");
+  }
+  util::Status status = dest_buffer->set_size(count);
+  if (!status.ok()) {
+    return status;
+  }
+  int read_count =
+      std::min(count, static_cast<int>(content_.size() - position));
+  std::copy(content_.begin() + position,
+            content_.begin() + position + read_count,
+            dest_buffer->get_mem_block());
+  status = dest_buffer->set_size(read_count);
+  if (!status.ok()) {
+    return status;
+  }
+  if (position + read_count == content_.size()) {
+    // We reached EOF.
+    return util::Status(absl::StatusCode::kOutOfRange, "EOF");
+  }
+  return util::OkStatus();
+}
+
+util::Status ReadAllFromRandomAccessStream(
+    RandomAccessStream* random_access_stream, std::string& contents,
+    int chunk_size) {
+  if (chunk_size < 1) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "chunk_size must be greater than zero");
+  }
+  contents.clear();
+  std::unique_ptr<util::Buffer> buffer =
+      *std::move(util::Buffer::New(chunk_size));
+  int64_t position = 0;
+  auto status = util::OkStatus();
+  while (status.ok()) {
+    status = random_access_stream->PRead(position, chunk_size, buffer.get());
+    contents.append(buffer->get_mem_block(), buffer->size());
+    position = contents.size();
+  }
+  return status;
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/test_random_access_stream.h b/cc/internal/test_random_access_stream.h
new file mode 100644
index 0000000..5e2e8fa
--- /dev/null
+++ b/cc/internal/test_random_access_stream.h
@@ -0,0 +1,67 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_INTERNAL_TEST_RANDOM_ACCESS_STREAM_H_
+#define TINK_INTERNAL_TEST_RANDOM_ACCESS_STREAM_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "tink/random_access_stream.h"
+#include "tink/util/buffer.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// A simple test-only RandomAccessStream implementation that reads from a
+// std::string.
+class TestRandomAccessStream : public RandomAccessStream {
+ public:
+  explicit TestRandomAccessStream(std::string content)
+      : content_(std::move(content)) {}
+  // Move only.
+  TestRandomAccessStream(TestRandomAccessStream&& other) = default;
+  TestRandomAccessStream& operator=(TestRandomAccessStream&& other) = default;
+  TestRandomAccessStream(const TestRandomAccessStream&) = delete;
+  TestRandomAccessStream& operator=(const TestRandomAccessStream&) = delete;
+
+  util::Status PRead(int64_t position, int count,
+                     util::Buffer* dest_buffer) override;
+
+  util::StatusOr<int64_t> size() override { return content_.size(); }
+
+ private:
+  std::string content_;
+};
+
+// Reads the entire `random_access_stream` using a buffer of size `chunk_size`
+// until no more bytes can be read, and puts the read bytes into `contents`.
+// Returns the status of the last call to random_access_stream->PRead().
+util::Status ReadAllFromRandomAccessStream(
+    RandomAccessStream* random_access_stream, std::string& contents,
+    int chunk_size = 42);
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTERNAL_TEST_RANDOM_ACCESS_STREAM_H_
diff --git a/cc/internal/test_random_access_stream_test.cc b/cc/internal/test_random_access_stream_test.cc
new file mode 100644
index 0000000..28ddcaa
--- /dev/null
+++ b/cc/internal/test_random_access_stream_test.cc
@@ -0,0 +1,161 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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 "tink/internal/test_random_access_stream.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/subtle/random.h"
+#include "tink/util/buffer.h"
+#include "tink/util/status.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::StatusIs;
+
+TEST(TestRandomAccessStreamTest, ReadAllSucceeds) {
+  const int buffer_size = 4 * 1024;
+  const int stream_size = 100 * 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+  auto rand_access_stream =
+      std::make_unique<TestRandomAccessStream>(stream_content);
+  auto buffer = *std::move(util::Buffer::New(buffer_size));
+  util::Status pread_status = util::OkStatus();
+  std::string result;
+  do {
+    pread_status =
+        rand_access_stream->PRead(result.size(), buffer_size, buffer.get());
+    result.append(buffer->get_mem_block(), buffer->size());
+  } while (pread_status.ok());
+  EXPECT_THAT(pread_status, StatusIs(absl::StatusCode::kOutOfRange));
+  EXPECT_EQ(result, stream_content);
+}
+
+TEST(TestRandomAccessStreamTest, PreadAllInOnePread) {
+  const int stream_size = 8 * 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+  auto rand_access_stream =
+      std::make_unique<TestRandomAccessStream>(stream_content);
+  auto buffer = *std::move(util::Buffer::New(stream_size));
+  ASSERT_THAT(
+      rand_access_stream->PRead(/*position=*/0, stream_size, buffer.get()),
+      StatusIs(absl::StatusCode::kOutOfRange));
+  EXPECT_EQ(std::string(buffer->get_mem_block(), buffer->size()),
+            stream_content);
+}
+
+TEST(TestRandomAccessStreamTest, PreadCountLargerThanBufferFails) {
+  const int buffer_size = 4 * 1024;
+  const int stream_size = 100 * 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+  auto rand_access_stream =
+      std::make_unique<TestRandomAccessStream>(stream_content);
+  auto buffer = *std::move(util::Buffer::New(buffer_size));
+  EXPECT_THAT(
+      rand_access_stream->PRead(/*position=*/0, buffer_size + 1, buffer.get()),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(TestRandomAccessStreamTest, InvalidPosition) {
+  const int buffer_size = 4 * 1024;
+  const int stream_size = 100 * 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+  auto rand_access_stream =
+      std::make_unique<TestRandomAccessStream>(stream_content);
+  auto buffer = *std::move(util::Buffer::New(buffer_size));
+  EXPECT_THAT(rand_access_stream->PRead(-1, buffer_size, buffer.get()),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(TestRandomAccessStreamTest, PreadWithNullBufferFails) {
+  const int stream_size = 100 * 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+  auto rand_access_stream =
+      std::make_unique<TestRandomAccessStream>(stream_content);
+  EXPECT_THAT(rand_access_stream->PRead(/*position=*/0, stream_size,
+                                        /*dest_buffer=*/nullptr),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(TestRandomAccessStreamTest, PreadWithEmptyStreamEof) {
+  const int buffer_size = 4 * 1024;
+  std::string stream_content;  // Empty string.
+  auto rand_access_stream =
+      std::make_unique<TestRandomAccessStream>(stream_content);
+  auto buffer = *std::move(util::Buffer::New(buffer_size));
+  EXPECT_THAT(
+      rand_access_stream->PRead(/*position=*/0, buffer_size, buffer.get()),
+      StatusIs(absl::StatusCode::kOutOfRange));
+}
+
+// Pread of the last partial block populates the buffer with the remaining
+// bytes and returns an EOF status.
+TEST(TestRandomAccessStreamTest, PreadTheLastPartialBlockReturnsEof) {
+  const int buffer_size = 4 * 1024;
+  const int stream_size = 100 * 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+  auto rand_access_stream =
+      std::make_unique<TestRandomAccessStream>(stream_content);
+  auto buffer = *std::move(util::Buffer::New(buffer_size));
+  // Read at a postion so that only buffer_size - 1 bytes are left.
+  EXPECT_THAT(rand_access_stream->PRead(stream_size - buffer_size + 1,
+                                        buffer_size, buffer.get()),
+              StatusIs(absl::StatusCode::kOutOfRange));
+  EXPECT_EQ(buffer->size(), buffer_size - 1);
+  EXPECT_EQ(std::string(buffer->get_mem_block(), buffer->size()),
+            stream_content.substr(stream_size - buffer_size + 1));
+}
+
+TEST(TestRandomAccessStreamTest, ReadAllFromRandomAccessStreamSucceeds) {
+  std::string content_to_read = subtle::Random::GetRandomBytes(4 * 1024);
+  auto test_random_access_stream =
+      std::make_unique<TestRandomAccessStream>(content_to_read);
+  std::string read_content;
+  EXPECT_THAT(ReadAllFromRandomAccessStream(test_random_access_stream.get(),
+                                            read_content,
+                                            /*chunk_size=*/128),
+              StatusIs(absl::StatusCode::kOutOfRange));
+  EXPECT_EQ(content_to_read, read_content);
+}
+
+TEST(TestRandomAccessStreamTest,
+     ReadAllFromRandomAccessStreamFailsWhenChunkIsLessThanOne) {
+  std::string content_to_read = subtle::Random::GetRandomBytes(4 * 1024);
+  auto test_random_access_stream =
+      std::make_unique<TestRandomAccessStream>(content_to_read);
+  std::string read_content;
+  EXPECT_THAT(ReadAllFromRandomAccessStream(test_random_access_stream.get(),
+                                            read_content,
+                                            /*chunk_size=*/0),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(ReadAllFromRandomAccessStream(test_random_access_stream.get(),
+                                            read_content,
+                                            /*chunk_size=*/-10),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/internal/util.cc b/cc/internal/util.cc
index 25c0aa0..c569d80 100644
--- a/cc/internal/util.cc
+++ b/cc/internal/util.cc
@@ -18,6 +18,8 @@
 #include <iterator>
 #include <functional>
 
+#include "absl/strings/ascii.h"
+#include "absl/log/log.h"
 #include "absl/strings/string_view.h"
 
 namespace crypto {
@@ -57,6 +59,19 @@
              std::prev(first.end()), std::prev(second.end()));
 }
 
+bool IsPrintableAscii(absl::string_view input) {
+  for (char c : input) {
+    if (!absl::ascii_isprint(c) || absl::ascii_isspace(c)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void LogFatal(absl::string_view msg) {
+  LOG(FATAL) <<  msg;
+}
+
 }  // namespace internal
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/internal/util.h b/cc/internal/util.h
index 247e499..6973189 100644
--- a/cc/internal/util.h
+++ b/cc/internal/util.h
@@ -16,6 +16,7 @@
 #ifndef TINK_INTERNAL_UTIL_H_
 #define TINK_INTERNAL_UTIL_H_
 
+#include "absl/base/attributes.h"
 #include "absl/strings/string_view.h"
 
 namespace crypto {
@@ -31,6 +32,23 @@
 // Returns true if `first` fully overlaps with `second`.
 bool BuffersAreIdentical(absl::string_view first, absl::string_view second);
 
+// Returns true if `input` only contains printable ASCII characters (whitespace
+// is not allowed).
+bool IsPrintableAscii(absl::string_view input);
+
+// Returns true if built on Windows; false otherwise.
+inline bool IsWindows() {
+#if defined(_WIN32)
+  return true;
+#else
+  return false;
+#endif
+}
+
+// Wraps Abseil's LOG(FATAL) macro and sets the [noreturn] attribute, which is
+// useful for avoiding false positive [-Werror=return-type] compiler errors.
+ABSL_ATTRIBUTE_NORETURN void LogFatal(absl::string_view msg);
+
 }  // namespace internal
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/internal/util_test.cc b/cc/internal/util_test.cc
index 1746993..fe470a8 100644
--- a/cc/internal/util_test.cc
+++ b/cc/internal/util_test.cc
@@ -26,6 +26,9 @@
 namespace internal {
 namespace {
 
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
 constexpr absl::string_view kLongString =
     "a long buffer with \n several \n newlines";
 
@@ -97,6 +100,22 @@
   EXPECT_FALSE(BuffersAreIdentical(buffer.substr(10, 5), buffer.substr(0, 10)));
 }
 
+TEST(UtilTest, IsPrintableAscii) {
+  const std::string input =
+      "!\"#$%&'()*+,-./"
+      "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
+      "abcdefghijklmnopqrstuvwxyz{|}~";
+  EXPECT_THAT(IsPrintableAscii(input), IsTrue());
+}
+
+TEST(UtilTest, IsNotPrintableAscii) {
+  EXPECT_THAT(IsPrintableAscii("\n"), IsFalse());
+  EXPECT_THAT(IsPrintableAscii("\t"), IsFalse());
+  EXPECT_THAT(IsPrintableAscii(" "), IsFalse());
+  EXPECT_THAT(IsPrintableAscii(std::string("\x7f", 1)), IsFalse());
+  EXPECT_THAT(IsPrintableAscii("ö"), IsFalse());
+}
+
 }  // namespace
 }  // namespace internal
 }  // namespace tink
diff --git a/cc/json_keyset_reader.h b/cc/json_keyset_reader.h
index 350d7a8..8bcb0cc 100644
--- a/cc/json_keyset_reader.h
+++ b/cc/json_keyset_reader.h
@@ -18,6 +18,7 @@
 #define TINK_JSON_KEYSET_READER_H_
 
 #include <istream>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/json_keyset_writer.h b/cc/json_keyset_writer.h
index 81dd488..d7fbb65 100644
--- a/cc/json_keyset_writer.h
+++ b/cc/json_keyset_writer.h
@@ -17,6 +17,7 @@
 #ifndef TINK_JSON_KEYSET_WRITER_H_
 #define TINK_JSON_KEYSET_WRITER_H_
 
+#include <memory>
 #include <ostream>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_ecdsa_sign_key_manager.cc b/cc/jwt/internal/jwt_ecdsa_sign_key_manager.cc
index 009057e..b264e40 100644
--- a/cc/jwt/internal/jwt_ecdsa_sign_key_manager.cc
+++ b/cc/jwt/internal/jwt_ecdsa_sign_key_manager.cc
@@ -15,6 +15,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/jwt/internal/jwt_ecdsa_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_ecdsa_sign_key_manager.h b/cc/jwt/internal/jwt_ecdsa_sign_key_manager.h
index 0259a26..4c7e3e2 100644
--- a/cc/jwt/internal/jwt_ecdsa_sign_key_manager.h
+++ b/cc/jwt/internal/jwt_ecdsa_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_ECDSA_SIGN_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_JWT_ECDSA_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/jwt_ecdsa_sign_verify_key_manager_test.cc b/cc/jwt/internal/jwt_ecdsa_sign_verify_key_manager_test.cc
index 8172973..2bd3dca 100644
--- a/cc/jwt/internal/jwt_ecdsa_sign_verify_key_manager_test.cc
+++ b/cc/jwt/internal/jwt_ecdsa_sign_verify_key_manager_test.cc
@@ -14,8 +14,10 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/jwt/internal/jwt_ecdsa_verify_key_manager.cc b/cc/jwt/internal/jwt_ecdsa_verify_key_manager.cc
index e6a51d5..68d8505 100644
--- a/cc/jwt/internal/jwt_ecdsa_verify_key_manager.cc
+++ b/cc/jwt/internal/jwt_ecdsa_verify_key_manager.cc
@@ -15,6 +15,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/jwt/internal/jwt_ecdsa_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_ecdsa_verify_key_manager.h b/cc/jwt/internal/jwt_ecdsa_verify_key_manager.h
index 5e23d58..530d2ba 100644
--- a/cc/jwt/internal/jwt_ecdsa_verify_key_manager.h
+++ b/cc/jwt/internal/jwt_ecdsa_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_ECDSA_VERIFY_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_JWT_ECDSA_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/jwt_format_test.cc b/cc/jwt/internal/jwt_format_test.cc
index f25652e..4d963c6 100644
--- a/cc/jwt/internal/jwt_format_test.cc
+++ b/cc/jwt/internal/jwt_format_test.cc
@@ -17,6 +17,7 @@
 #include "tink/jwt/internal/jwt_format.h"
 
 #include <string>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/jwt/internal/jwt_hmac_key_manager.h b/cc/jwt/internal/jwt_hmac_key_manager.h
index 03228dd..02f544d 100644
--- a/cc/jwt/internal/jwt_hmac_key_manager.h
+++ b/cc/jwt/internal/jwt_hmac_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_HMAC_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_JWT_HMAC_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_hmac_key_manager_test.cc b/cc/jwt/internal/jwt_hmac_key_manager_test.cc
index 00be35c..d4285bf 100644
--- a/cc/jwt/internal/jwt_hmac_key_manager_test.cc
+++ b/cc/jwt/internal/jwt_hmac_key_manager_test.cc
@@ -16,7 +16,10 @@
 
 #include "tink/jwt/internal/jwt_hmac_key_manager.h"
 
+#include <memory>
+#include <sstream>
 #include <string>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/jwt/internal/jwt_mac_impl.cc b/cc/jwt/internal/jwt_mac_impl.cc
index 5b24c1b..aa2123d 100644
--- a/cc/jwt/internal/jwt_mac_impl.cc
+++ b/cc/jwt/internal/jwt_mac_impl.cc
@@ -18,6 +18,7 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/status/status.h"
 #include "absl/strings/escaping.h"
diff --git a/cc/jwt/internal/jwt_mac_impl.h b/cc/jwt/internal/jwt_mac_impl.h
index a5cc64b..f557cd3 100644
--- a/cc/jwt/internal/jwt_mac_impl.h
+++ b/cc/jwt/internal/jwt_mac_impl.h
@@ -17,6 +17,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_MAC_IMPL_H_
 #define TINK_JWT_INTERNAL_JWT_MAC_IMPL_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_mac_impl_test.cc b/cc/jwt/internal/jwt_mac_impl_test.cc
index fb9a7df..077e3ed 100644
--- a/cc/jwt/internal/jwt_mac_impl_test.cc
+++ b/cc/jwt/internal/jwt_mac_impl_test.cc
@@ -16,8 +16,10 @@
 
 #include "tink/jwt/internal/jwt_mac_impl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/jwt/internal/jwt_mac_internal.h b/cc/jwt/internal/jwt_mac_internal.h
index 0b83f02..d7f3ec7 100644
--- a/cc/jwt/internal/jwt_mac_internal.h
+++ b/cc/jwt/internal/jwt_mac_internal.h
@@ -65,7 +65,7 @@
       absl::string_view compact, const JwtValidator& validator,
       absl::optional<absl::string_view> kid) const = 0;
 
-  virtual ~JwtMacInternal() {}
+  virtual ~JwtMacInternal() = default;
 };
 
 }  // namespace jwt_internal
diff --git a/cc/jwt/internal/jwt_mac_wrapper.cc b/cc/jwt/internal/jwt_mac_wrapper.cc
index d53fb2d..73ee43f 100644
--- a/cc/jwt/internal/jwt_mac_wrapper.cc
+++ b/cc/jwt/internal/jwt_mac_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/jwt_mac_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -48,7 +49,7 @@
       absl::string_view compact,
       const crypto::tink::JwtValidator& validator) const override;
 
-  ~JwtMacSetWrapper() override {}
+  ~JwtMacSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<JwtMacInternal>> jwt_mac_set_;
diff --git a/cc/jwt/internal/jwt_mac_wrapper.h b/cc/jwt/internal/jwt_mac_wrapper.h
index e764c6c..3a93664 100644
--- a/cc/jwt/internal/jwt_mac_wrapper.h
+++ b/cc/jwt/internal/jwt_mac_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_JWT_INTERNAL_JWT_MAC_WRAPPER_H_
 #define TINK_JWT_INTERNAL_JWT_MAC_WRAPPER_H_
 
+#include <memory>
+
 #include "tink/jwt/internal/jwt_mac_internal.h"
 #include "tink/jwt/jwt_mac.h"
 #include "tink/primitive_set.h"
diff --git a/cc/jwt/internal/jwt_mac_wrapper_test.cc b/cc/jwt/internal/jwt_mac_wrapper_test.cc
index f7e66ed..0d6062b 100644
--- a/cc/jwt/internal/jwt_mac_wrapper_test.cc
+++ b/cc/jwt/internal/jwt_mac_wrapper_test.cc
@@ -16,8 +16,10 @@
 
 #include "tink/jwt/internal/jwt_mac_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/strings/str_split.h"
diff --git a/cc/jwt/internal/jwt_public_key_sign_impl.h b/cc/jwt/internal/jwt_public_key_sign_impl.h
index 7f83103..7f665bd 100644
--- a/cc/jwt/internal/jwt_public_key_sign_impl.h
+++ b/cc/jwt/internal/jwt_public_key_sign_impl.h
@@ -17,6 +17,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_SIGN_IMPL_H_
 #define TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_SIGN_IMPL_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_public_key_sign_internal.h b/cc/jwt/internal/jwt_public_key_sign_internal.h
index 28a095b..f7522e6 100644
--- a/cc/jwt/internal/jwt_public_key_sign_internal.h
+++ b/cc/jwt/internal/jwt_public_key_sign_internal.h
@@ -41,7 +41,7 @@
   virtual crypto::tink::util::StatusOr<std::string> SignAndEncodeWithKid(
       const RawJwt& token, absl::optional<absl::string_view> kid) const = 0;
 
-  virtual ~JwtPublicKeySignInternal() {}
+  virtual ~JwtPublicKeySignInternal() = default;
 };
 
 }  // namespace tink
diff --git a/cc/jwt/internal/jwt_public_key_sign_verify_impl_test.cc b/cc/jwt/internal/jwt_public_key_sign_verify_impl_test.cc
index c69907a..14cb895 100644
--- a/cc/jwt/internal/jwt_public_key_sign_verify_impl_test.cc
+++ b/cc/jwt/internal/jwt_public_key_sign_verify_impl_test.cc
@@ -14,8 +14,10 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/jwt/internal/jwt_public_key_sign_wrapper.cc b/cc/jwt/internal/jwt_public_key_sign_wrapper.cc
index 316e3ec..5ad41da 100644
--- a/cc/jwt/internal/jwt_public_key_sign_wrapper.cc
+++ b/cc/jwt/internal/jwt_public_key_sign_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/jwt_public_key_sign_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -44,7 +45,7 @@
   crypto::tink::util::StatusOr<std::string> SignAndEncode(
       const crypto::tink::RawJwt& token) const override;
 
-  ~JwtPublicKeySignSetWrapper() override {}
+  ~JwtPublicKeySignSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<JwtPublicKeySignInternal>> jwt_sign_set_;
diff --git a/cc/jwt/internal/jwt_public_key_sign_wrapper.h b/cc/jwt/internal/jwt_public_key_sign_wrapper.h
index 477ccd9..d1e951a 100644
--- a/cc/jwt/internal/jwt_public_key_sign_wrapper.h
+++ b/cc/jwt/internal/jwt_public_key_sign_wrapper.h
@@ -16,6 +16,8 @@
 #ifndef TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_SIGN_WRAPPER_H_
 #define TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_SIGN_WRAPPER_H_
 
+#include <memory>
+
 #include "tink/jwt/internal/jwt_public_key_sign_internal.h"
 #include "tink/jwt/jwt_public_key_sign.h"
 #include "tink/primitive_set.h"
diff --git a/cc/jwt/internal/jwt_public_key_verify_impl.cc b/cc/jwt/internal/jwt_public_key_verify_impl.cc
index 37e02b7..8ea3a6a 100644
--- a/cc/jwt/internal/jwt_public_key_verify_impl.cc
+++ b/cc/jwt/internal/jwt_public_key_verify_impl.cc
@@ -18,6 +18,7 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/status/status.h"
 #include "absl/strings/escaping.h"
diff --git a/cc/jwt/internal/jwt_public_key_verify_impl.h b/cc/jwt/internal/jwt_public_key_verify_impl.h
index b6adba1..99c0af3 100644
--- a/cc/jwt/internal/jwt_public_key_verify_impl.h
+++ b/cc/jwt/internal/jwt_public_key_verify_impl.h
@@ -17,6 +17,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_VERIFY_IMPL_H_
 #define TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_VERIFY_IMPL_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_public_key_verify_internal.h b/cc/jwt/internal/jwt_public_key_verify_internal.h
index 9ec5aa2..392dc3c 100644
--- a/cc/jwt/internal/jwt_public_key_verify_internal.h
+++ b/cc/jwt/internal/jwt_public_key_verify_internal.h
@@ -54,7 +54,7 @@
       absl::string_view compact, const JwtValidator& validator,
       absl::optional<absl::string_view> kid) const = 0;
 
-  virtual ~JwtPublicKeyVerifyInternal() {}
+  virtual ~JwtPublicKeyVerifyInternal() = default;
 };
 
 }  // namespace tink
diff --git a/cc/jwt/internal/jwt_public_key_verify_wrapper.cc b/cc/jwt/internal/jwt_public_key_verify_wrapper.cc
index 179de6f..6d4159f 100644
--- a/cc/jwt/internal/jwt_public_key_verify_wrapper.cc
+++ b/cc/jwt/internal/jwt_public_key_verify_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/jwt_public_key_verify_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -45,7 +46,7 @@
       absl::string_view compact,
       const crypto::tink::JwtValidator& validator) const override;
 
-  ~JwtPublicKeyVerifySetWrapper() override {}
+  ~JwtPublicKeyVerifySetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<JwtPublicKeyVerifyInternal>> jwt_verify_set_;
diff --git a/cc/jwt/internal/jwt_public_key_verify_wrapper.h b/cc/jwt/internal/jwt_public_key_verify_wrapper.h
index ac0b807..cc59461 100644
--- a/cc/jwt/internal/jwt_public_key_verify_wrapper.h
+++ b/cc/jwt/internal/jwt_public_key_verify_wrapper.h
@@ -16,6 +16,8 @@
 #ifndef TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_VERIFY_WRAPPER_H_
 #define TINK_JWT_INTERNAL_JWT_PUBLIC_KEY_VERIFY_WRAPPER_H_
 
+#include <memory>
+
 #include "tink/jwt/internal/jwt_public_key_verify_internal.h"
 #include "tink/jwt/jwt_public_key_verify.h"
 #include "tink/primitive_set.h"
diff --git a/cc/jwt/internal/jwt_public_key_wrappers_test.cc b/cc/jwt/internal/jwt_public_key_wrappers_test.cc
index 285aa6e..ce5c8ca 100644
--- a/cc/jwt/internal/jwt_public_key_wrappers_test.cc
+++ b/cc/jwt/internal/jwt_public_key_wrappers_test.cc
@@ -14,8 +14,10 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/strings/str_split.h"
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.cc b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.cc
index 8cd220f..31bd8ad 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.cc
+++ b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.cc
@@ -15,8 +15,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.h"
 
-#include <utility>
+#include <memory>
 #include <string>
+#include <utility>
 
 #include "tink/jwt/internal/jwt_public_key_sign_impl.h"
 #include "tink/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.h"
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.h b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.h
index 5e799e5..0cd598a 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.h
+++ b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_JWT_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_verify_key_manager_test.cc b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_verify_key_manager_test.cc
index 14f00dc..6741929 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_verify_key_manager_test.cc
+++ b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_sign_verify_key_manager_test.cc
@@ -14,8 +14,10 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.cc b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.cc
index 2d75ae4..3c4432c 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.cc
+++ b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.cc
@@ -15,6 +15,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.h b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.h
index be85648..f977489 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.h
+++ b/cc/jwt/internal/jwt_rsa_ssa_pkcs1_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_RSA_SSA_PKCS1_VERIFY_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_JWT_RSA_SSA_PKCS1_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.cc b/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.cc
index 470be79..183ebb6 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.cc
+++ b/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.cc
@@ -15,6 +15,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.h b/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.h
index 1ac226b..fd8891f 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.h
+++ b/cc/jwt/internal/jwt_rsa_ssa_pss_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_JWT_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pss_sign_verify_key_manager_test.cc b/cc/jwt/internal/jwt_rsa_ssa_pss_sign_verify_key_manager_test.cc
index 5fe31e6..ddc7c0b 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pss_sign_verify_key_manager_test.cc
+++ b/cc/jwt/internal/jwt_rsa_ssa_pss_sign_verify_key_manager_test.cc
@@ -14,8 +14,10 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.cc b/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.cc
index c6813b0..4826e99 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.cc
+++ b/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.cc
@@ -15,6 +15,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.h b/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.h
index dd6d5f8..c8ba629 100644
--- a/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.h
+++ b/cc/jwt/internal/jwt_rsa_ssa_pss_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_JWT_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_JWT_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.cc b/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.cc
index 2d917a0..144bf8b 100644
--- a/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.cc
+++ b/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_ecdsa_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.h b/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.h
index d90660e..99e8911 100644
--- a/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.h
+++ b/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_RAW_JWT_ECDSA_SIGN_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_RAW_JWT_ECDSA_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager_test.cc b/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager_test.cc
index 6147e88..552b6aa 100644
--- a/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager_test.cc
+++ b/cc/jwt/internal/raw_jwt_ecdsa_sign_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_ecdsa_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
diff --git a/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.cc b/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.cc
index b9e9675..956790a 100644
--- a/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.cc
+++ b/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_ecdsa_verify_key_manager.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
diff --git a/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.h b/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.h
index ecfafe1..8179d48 100644
--- a/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.h
+++ b/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_RAW_JWT_ECDSA_VERIFY_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_RAW_JWT_ECDSA_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager_test.cc b/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager_test.cc
index 8a4bc8a..84b7dec 100644
--- a/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager_test.cc
+++ b/cc/jwt/internal/raw_jwt_ecdsa_verify_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_ecdsa_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
diff --git a/cc/jwt/internal/raw_jwt_hmac_key_manager.cc b/cc/jwt/internal/raw_jwt_hmac_key_manager.cc
index 0169931..72b4335 100644
--- a/cc/jwt/internal/raw_jwt_hmac_key_manager.cc
+++ b/cc/jwt/internal/raw_jwt_hmac_key_manager.cc
@@ -46,8 +46,6 @@
 
 namespace {
 
-constexpr int kMinKeySizeInBytes = 32;
-
 StatusOr<int> MinimumKeySize(const JwtHmacAlgorithm& algorithm) {
   switch (algorithm) {
     case JwtHmacAlgorithm::HS256:
diff --git a/cc/jwt/internal/raw_jwt_hmac_key_manager.h b/cc/jwt/internal/raw_jwt_hmac_key_manager.h
index 62d88a9..7e6b1e3 100644
--- a/cc/jwt/internal/raw_jwt_hmac_key_manager.h
+++ b/cc/jwt/internal/raw_jwt_hmac_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_RAW_JWT_HMAC_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_RAW_JWT_HMAC_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_hmac_key_manager_test.cc b/cc/jwt/internal/raw_jwt_hmac_key_manager_test.cc
index 4235952..88428ee 100644
--- a/cc/jwt/internal/raw_jwt_hmac_key_manager_test.cc
+++ b/cc/jwt/internal/raw_jwt_hmac_key_manager_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/jwt/internal/raw_jwt_hmac_key_manager.h"
 
+#include <memory>
+#include <sstream>
 #include <utility>
 
 #include "gmock/gmock.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.cc
index d0839d7..a4450f3 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.h b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.h
index 22b3f83..efe5f32 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.h
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_RAW_JWT_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_RAW_JWT_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager_test.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager_test.cc
index e1eaf2d..32395ae 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager_test.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pkcs1_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.cc
index 10dae49..9e8a415 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.h b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.h
index 7cbb449..aca8285 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.h
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.h
@@ -18,6 +18,7 @@
 #define TINK_JWT_INTERNAL_RAW_JWT_RSA_SSA_PKCS1_VERIFY_KEY_MANAGER_H_
 
 #include <algorithm>
+#include <memory>
 #include <string>
 #include <vector>
 
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager_test.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager_test.cc
index be19b50..3bd1629 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager_test.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pkcs1_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.cc
index 9fd230c..9b4509b 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.h b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.h
index 12f55e2..bf06792 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.h
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_RAW_JWT_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_RAW_JWT_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager_test.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager_test.cc
index f6adfcf..56e627b 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager_test.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pss_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.cc
index baabdf9..d5e83b1 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.h b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.h
index 5750194..0e9cc8f 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.h
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_JWT_INTERNAL_RAW_JWT_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
 #define TINK_JWT_INTERNAL_RAW_JWT_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager_test.cc b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager_test.cc
index 39ec7ec..6cc9688 100644
--- a/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager_test.cc
+++ b/cc/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/internal/raw_jwt_rsa_ssa_pss_verify_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "gmock/gmock.h"
diff --git a/cc/jwt/jwk_set_converter.cc b/cc/jwt/jwk_set_converter.cc
index 4d8a7af..cf26d38 100644
--- a/cc/jwt/jwk_set_converter.cc
+++ b/cc/jwt/jwk_set_converter.cc
@@ -16,6 +16,9 @@
 
 #include "tink/jwt/jwk_set_converter.h"
 
+#include <memory>
+#include <ostream>
+#include <sstream>
 #include <string>
 
 #include "absl/strings/escaping.h"
diff --git a/cc/jwt/jwk_set_converter.h b/cc/jwt/jwk_set_converter.h
index 6595a23..1d3914d 100644
--- a/cc/jwt/jwk_set_converter.h
+++ b/cc/jwt/jwk_set_converter.h
@@ -17,6 +17,7 @@
 #ifndef TINK_JWT_JWK_SET_CONVERTER_H_
 #define TINK_JWT_JWK_SET_CONVERTER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
diff --git a/cc/jwt/jwk_set_converter_test.cc b/cc/jwt/jwk_set_converter_test.cc
index 743518c..494cdf5 100644
--- a/cc/jwt/jwk_set_converter_test.cc
+++ b/cc/jwt/jwk_set_converter_test.cc
@@ -16,7 +16,9 @@
 
 #include "tink/jwt/jwk_set_converter.h"
 
+#include <memory>
 #include <string>
+#include <tuple>
 #include <utility>
 
 #include "google/protobuf/util/message_differencer.h"
diff --git a/cc/jwt/jwt_key_templates_test.cc b/cc/jwt/jwt_key_templates_test.cc
index f52d2ff..d85c450 100644
--- a/cc/jwt/jwt_key_templates_test.cc
+++ b/cc/jwt/jwt_key_templates_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/jwt/jwt_key_templates.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/jwt/jwt_mac.h b/cc/jwt/jwt_mac.h
index 3589e00..7b4374b 100644
--- a/cc/jwt/jwt_mac.h
+++ b/cc/jwt/jwt_mac.h
@@ -56,7 +56,7 @@
   virtual crypto::tink::util::StatusOr<VerifiedJwt> VerifyMacAndDecode(
       absl::string_view compact, const JwtValidator& validator) const = 0;
 
-  virtual ~JwtMac() {}
+  virtual ~JwtMac() = default;
 };
 
 }  // namespace tink
diff --git a/cc/jwt/jwt_public_key_sign.h b/cc/jwt/jwt_public_key_sign.h
index 840182a..a20f1d1 100644
--- a/cc/jwt/jwt_public_key_sign.h
+++ b/cc/jwt/jwt_public_key_sign.h
@@ -38,7 +38,7 @@
   virtual crypto::tink::util::StatusOr<std::string> SignAndEncode(
       const RawJwt& token) const = 0;
 
-  virtual ~JwtPublicKeySign() {}
+  virtual ~JwtPublicKeySign() = default;
 };
 
 }  // namespace tink
diff --git a/cc/jwt/jwt_public_key_verify.h b/cc/jwt/jwt_public_key_verify.h
index 1e6eb3a..decdc2f 100644
--- a/cc/jwt/jwt_public_key_verify.h
+++ b/cc/jwt/jwt_public_key_verify.h
@@ -48,7 +48,7 @@
   virtual crypto::tink::util::StatusOr<VerifiedJwt> VerifyAndDecode(
       absl::string_view compact, const JwtValidator& validator) const = 0;
 
-  virtual ~JwtPublicKeyVerify() {}
+  virtual ~JwtPublicKeyVerify() = default;
 };
 
 }  // namespace tink
diff --git a/cc/jwt/jwt_validator.cc b/cc/jwt/jwt_validator.cc
index c56a399..881d9e1 100644
--- a/cc/jwt/jwt_validator.cc
+++ b/cc/jwt/jwt_validator.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <string>
+#include <vector>
 
 #include "absl/status/status.h"
 
diff --git a/cc/jwt/raw_jwt.cc b/cc/jwt/raw_jwt.cc
index e1fae19..23ba60d 100644
--- a/cc/jwt/raw_jwt.cc
+++ b/cc/jwt/raw_jwt.cc
@@ -18,6 +18,7 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/status/status.h"
 #include "absl/strings/numbers.h"
@@ -185,7 +186,7 @@
   return jwt_internal::ProtoStructToJsonString(json_proto_);
 }
 
-RawJwt::RawJwt() {}
+RawJwt::RawJwt() = default;
 
 RawJwt::RawJwt(absl::optional<std::string> type_header,
                google::protobuf::Struct json_proto) {
diff --git a/cc/jwt/raw_jwt.h b/cc/jwt/raw_jwt.h
index 36f40de..2a83d23 100644
--- a/cc/jwt/raw_jwt.h
+++ b/cc/jwt/raw_jwt.h
@@ -18,6 +18,7 @@
 #define TINK_JWT_RAW_JWT_H_
 
 #include <string>
+#include <vector>
 
 #include "google/protobuf/struct.pb.h"
 #include "absl/strings/string_view.h"
diff --git a/cc/jwt/raw_jwt_test.cc b/cc/jwt/raw_jwt_test.cc
index e7dc361..a191d56 100644
--- a/cc/jwt/raw_jwt_test.cc
+++ b/cc/jwt/raw_jwt_test.cc
@@ -17,6 +17,7 @@
 #include "tink/jwt/raw_jwt.h"
 
 #include <string>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/strings/escaping.h"
@@ -473,7 +474,15 @@
       RawJwtBuilder().SetIssuer("issuer").WithoutExpiration().Build();
   ASSERT_THAT(jwt, IsOk());
 
-  ASSERT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"iss":"issuer"})"));
+  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"iss":"issuer"})"));
+}
+
+TEST(RawJwt, IntegerIsEncodedAsInteger) {
+  util::StatusOr<RawJwt> jwt =
+      RawJwtBuilder().AddNumberClaim("num", 1).WithoutExpiration().Build();
+  ASSERT_THAT(jwt, IsOk());
+
+  EXPECT_THAT(jwt->GetJsonPayload(), IsOkAndHolds(R"({"num":1})"));
 }
 
 TEST(RawJwt, GetExpirationJsonPayload) {
diff --git a/cc/jwt/verified_jwt.cc b/cc/jwt/verified_jwt.cc
index 4374044..c1359f5 100644
--- a/cc/jwt/verified_jwt.cc
+++ b/cc/jwt/verified_jwt.cc
@@ -17,6 +17,7 @@
 #include "tink/jwt/verified_jwt.h"
 
 #include <string>
+#include <vector>
 
 #include "absl/strings/numbers.h"
 #include "absl/strings/str_format.h"
@@ -26,7 +27,7 @@
 namespace crypto {
 namespace tink {
 
-VerifiedJwt::VerifiedJwt() {}
+VerifiedJwt::VerifiedJwt() = default;
 
 VerifiedJwt::VerifiedJwt(const RawJwt& raw_jwt) {
   raw_jwt_ = raw_jwt;
diff --git a/cc/jwt/verified_jwt.h b/cc/jwt/verified_jwt.h
index faa5ba3..e4d4c85 100644
--- a/cc/jwt/verified_jwt.h
+++ b/cc/jwt/verified_jwt.h
@@ -18,6 +18,7 @@
 #define TINK_JWT_VERIFIED_JWT_H_
 
 #include <string>
+#include <vector>
 
 #include "google/protobuf/struct.pb.h"
 #include "absl/strings/string_view.h"
diff --git a/cc/jwt/verified_jwt_test.cc b/cc/jwt/verified_jwt_test.cc
index ff1ba54..ed0c7e3 100644
--- a/cc/jwt/verified_jwt_test.cc
+++ b/cc/jwt/verified_jwt_test.cc
@@ -16,8 +16,10 @@
 
 #include "tink/jwt/verified_jwt.h"
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
@@ -326,7 +328,7 @@
   VerifiedJwt jwt2 = std::move(jwt1);
   // We want that a VerifiedJwt object remains a valid object, even after
   // std::moved has been called.
-  EXPECT_TRUE(jwt1.HasIssuer());
+  EXPECT_TRUE(jwt1.HasIssuer());  // NOLINT(bugprone-use-after-move)
   EXPECT_THAT(jwt1.GetIssuer(), IsOkAndHolds("issuer"));
   EXPECT_TRUE(jwt2.HasIssuer());
   EXPECT_THAT(jwt2.GetIssuer(), IsOkAndHolds("issuer"));
diff --git a/cc/key_access.h b/cc/key_access.h
index 51b7e32..77465a6 100644
--- a/cc/key_access.h
+++ b/cc/key_access.h
@@ -14,8 +14,8 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-#ifndef THIRD_PARTY_TINK_KEY_ACCESS_H_
-#define THIRD_PARTY_TINK_KEY_ACCESS_H_
+#ifndef TINK_KEY_ACCESS_H_
+#define TINK_KEY_ACCESS_H_
 
 namespace crypto {
 namespace tink {
@@ -27,7 +27,7 @@
     return token;
   }
 
-  const bool CanAccessSecret() { return can_access_secret_; }
+  bool CanAccessSecret() { return can_access_secret_; }
 
   // KeyAccess objects are copiable and movable.
   KeyAccess(const KeyAccess&) = default;
@@ -46,4 +46,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // THIRD_PARTY_TINK_KEY_ACCESS_H_
+#endif  // TINK_KEY_ACCESS_H_
diff --git a/cc/key_gen_configuration.h b/cc/key_gen_configuration.h
new file mode 100644
index 0000000..f8e3e09
--- /dev/null
+++ b/cc/key_gen_configuration.h
@@ -0,0 +1,52 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEY_GEN_CONFIGURATION_H_
+#define TINK_KEY_GEN_CONFIGURATION_H_
+
+#include "tink/internal/key_type_info_store.h"
+
+namespace crypto {
+namespace tink {
+
+namespace internal {
+class KeyGenConfigurationImpl;
+}
+
+// KeyGenConfiguration used to generate keys using stored key type managers.
+class KeyGenConfiguration {
+ public:
+  KeyGenConfiguration() = default;
+
+  // Not copyable or movable.
+  KeyGenConfiguration(const KeyGenConfiguration&) = delete;
+  KeyGenConfiguration& operator=(const KeyGenConfiguration&) = delete;
+
+ private:
+  friend class internal::KeyGenConfigurationImpl;
+
+  // When true, KeyGenConfiguration is in global registry mode. For
+  // `some_fn(config)` with a `config` parameter, this indicates to `some_fn` to
+  // use crypto::tink::Registry directly.
+  bool global_registry_mode_ = false;
+
+  crypto::tink::internal::KeyTypeInfoStore key_type_info_store_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEY_GEN_CONFIGURATION_H_
diff --git a/cc/key_manager.h b/cc/key_manager.h
index 871acee..58d2744 100644
--- a/cc/key_manager.h
+++ b/cc/key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_KEY_MANAGER_H_
 #define TINK_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
@@ -56,7 +57,7 @@
       std::unique_ptr<google::crypto::tink::KeyData>>
   NewKeyData(absl::string_view serialized_key_format) const = 0;
 
-  virtual ~KeyFactory() {}
+  virtual ~KeyFactory() = default;
 };
 
 class PrivateKeyFactory : public virtual KeyFactory {
@@ -66,7 +67,7 @@
       std::unique_ptr<google::crypto::tink::KeyData>>
   GetPublicKeyData(absl::string_view serialized_private_key) const = 0;
 
-  virtual ~PrivateKeyFactory() {}
+  ~PrivateKeyFactory() override = default;
 };
 
 /**
@@ -94,7 +95,7 @@
     return (key_type == get_key_type());
   }
 
-  virtual ~KeyManagerBase() {}
+  virtual ~KeyManagerBase() = default;
 };
 
 template <class P>
diff --git a/cc/key_status.h b/cc/key_status.h
new file mode 100644
index 0000000..8d37cb5
--- /dev/null
+++ b/cc/key_status.h
@@ -0,0 +1,36 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEY_STATUS_H_
+#define TINK_KEY_STATUS_H_
+
+namespace crypto {
+namespace tink {
+
+// Enum representation of KeyStatusType in tink/proto/tink.proto. Using an
+// enum class prevents unintentional implicit conversions.
+enum class KeyStatus : int {
+  kEnabled = 1,    // Can be used for cryptographic operations.
+  kDisabled = 2,   // Cannot be used (but can become kEnabled again).
+  kDestroyed = 3,  // Key data does not exist in this Keyset any more.
+  // Added to guard from failures that may be caused by future expansions.
+  kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEY_STATUS_H_
diff --git a/cc/keyderivation/BUILD.bazel b/cc/keyderivation/BUILD.bazel
new file mode 100644
index 0000000..449698a
--- /dev/null
+++ b/cc/keyderivation/BUILD.bazel
@@ -0,0 +1,115 @@
+package(
+    default_visibility = ["//:__subpackages__"],
+)
+
+licenses(["notice"])
+
+cc_library(
+    name = "key_derivation_config",
+    srcs = ["key_derivation_config.cc"],
+    hdrs = ["key_derivation_config.h"],
+    include_prefix = "tink/keyderivation",
+    visibility = ["//visibility:public"],
+    deps = [
+        ":keyset_deriver_wrapper",
+        "//config:tink_fips",
+        "//keyderivation/internal:prf_based_deriver_key_manager",
+        "//prf:hkdf_prf_key_manager",
+        "//util:status",
+    ],
+)
+
+cc_test(
+    name = "key_derivation_config_test",
+    srcs = ["key_derivation_config_test.cc"],
+    deps = [
+        ":key_derivation_config",
+        ":key_derivation_key_templates",
+        ":keyset_deriver",
+        "//:registry",
+        "//aead:aead_config",
+        "//aead:aead_key_templates",
+        "//aead:aes_gcm_key_manager",
+        "//prf:prf_key_templates",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "key_derivation_key_templates",
+    srcs = ["key_derivation_key_templates.cc"],
+    hdrs = ["key_derivation_key_templates.h"],
+    include_prefix = "tink/keyderivation",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//keyderivation/internal:prf_based_deriver_key_manager",
+        "//proto:tink_cc_proto",
+        "//subtle:random",
+        "//util:statusor",
+    ],
+)
+
+cc_test(
+    name = "key_derivation_key_templates_test",
+    srcs = ["key_derivation_key_templates_test.cc"],
+    deps = [
+        ":key_derivation_key_templates",
+        ":keyset_deriver_wrapper",
+        "//:registry",
+        "//aead:aead_key_templates",
+        "//aead:aes_gcm_key_manager",
+        "//keyderivation/internal:prf_based_deriver_key_manager",
+        "//prf:hkdf_prf_key_manager",
+        "//prf:prf_key_templates",
+        "//proto:prf_based_deriver_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "keyset_deriver",
+    hdrs = ["keyset_deriver.h"],
+    include_prefix = "tink/keyderivation",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//:keyset_handle",
+        "//util:statusor",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "keyset_deriver_wrapper",
+    srcs = ["keyset_deriver_wrapper.cc"],
+    hdrs = ["keyset_deriver_wrapper.h"],
+    include_prefix = "tink/keyderivation",
+    deps = [
+        ":keyset_deriver",
+        "//:cleartext_keyset_handle",
+        "//:primitive_set",
+        "//:primitive_wrapper",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/status",
+    ],
+)
+
+cc_test(
+    name = "keyset_deriver_wrapper_test",
+    srcs = ["keyset_deriver_wrapper_test.cc"],
+    deps = [
+        ":keyset_deriver",
+        ":keyset_deriver_wrapper",
+        "//:cleartext_keyset_handle",
+        "//:primitive_set",
+        "//proto:tink_cc_proto",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/keyderivation/CMakeLists.txt b/cc/keyderivation/CMakeLists.txt
new file mode 100644
index 0000000..fa3d7e8
--- /dev/null
+++ b/cc/keyderivation/CMakeLists.txt
@@ -0,0 +1,109 @@
+tink_module(keyderivation)
+
+add_subdirectory(internal)
+
+tink_cc_library(
+  NAME key_derivation_config
+  SRCS
+    key_derivation_config.cc
+    key_derivation_config.h
+  DEPS
+    tink::keyderivation::keyset_deriver_wrapper
+    tink::config::tink_fips
+    tink::keyderivation::internal::prf_based_deriver_key_manager
+    tink::prf::hkdf_prf_key_manager
+    tink::util::status
+  PUBLIC
+)
+
+tink_cc_test(
+  NAME key_derivation_config_test
+  SRCS
+    key_derivation_config_test.cc
+  DEPS
+    tink::keyderivation::key_derivation_config
+    tink::keyderivation::key_derivation_key_templates
+    tink::keyderivation::keyset_deriver
+    gmock
+    tink::core::registry
+    tink::aead::aead_config
+    tink::aead::aead_key_templates
+    tink::aead::aes_gcm_key_manager
+    tink::prf::prf_key_templates
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_library(
+  NAME key_derivation_key_templates
+  SRCS
+    key_derivation_key_templates.cc
+    key_derivation_key_templates.h
+  DEPS
+    tink::keyderivation::internal::prf_based_deriver_key_manager
+    tink::subtle::random
+    tink::util::statusor
+    tink::proto::tink_cc_proto
+  PUBLIC
+)
+
+tink_cc_test(
+  NAME key_derivation_key_templates_test
+  SRCS
+    key_derivation_key_templates_test.cc
+  DEPS
+    tink::keyderivation::key_derivation_key_templates
+    tink::keyderivation::keyset_deriver_wrapper
+    gmock
+    absl::status
+    tink::core::registry
+    tink::aead::aead_key_templates
+    tink::aead::aes_gcm_key_manager
+    tink::keyderivation::internal::prf_based_deriver_key_manager
+    tink::prf::hkdf_prf_key_manager
+    tink::prf::prf_key_templates
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::proto::prf_based_deriver_cc_proto
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME keyset_deriver
+  SRCS
+    keyset_deriver.h
+  DEPS
+    absl::strings
+    tink::core::keyset_handle
+    tink::util::statusor
+  PUBLIC
+)
+
+tink_cc_library(
+  NAME keyset_deriver_wrapper
+  SRCS
+    keyset_deriver_wrapper.cc
+    keyset_deriver_wrapper.h
+  DEPS
+    tink::keyderivation::keyset_deriver
+    absl::status
+    tink::core::cleartext_keyset_handle
+    tink::core::primitive_set
+    tink::core::primitive_wrapper
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME keyset_deriver_wrapper_test
+  SRCS
+    keyset_deriver_wrapper_test.cc
+  DEPS
+    tink::keyderivation::keyset_deriver
+    tink::keyderivation::keyset_deriver_wrapper
+    gmock
+    absl::status
+    tink::core::cleartext_keyset_handle
+    tink::core::primitive_set
+    tink::util::test_matchers
+    tink::proto::tink_cc_proto
+)
diff --git a/cc/keyderivation/internal/BUILD.bazel b/cc/keyderivation/internal/BUILD.bazel
new file mode 100644
index 0000000..09aa90b
--- /dev/null
+++ b/cc/keyderivation/internal/BUILD.bazel
@@ -0,0 +1,73 @@
+package(default_visibility = ["//:__subpackages__"])
+
+licenses(["notice"])
+
+cc_library(
+    name = "prf_based_deriver",
+    srcs = ["prf_based_deriver.cc"],
+    hdrs = ["prf_based_deriver.h"],
+    include_prefix = "tink/keyderivation/internal",
+    deps = [
+        "//:cleartext_keyset_handle",
+        "//:keyset_handle",
+        "//:registry",
+        "//keyderivation:keyset_deriver",
+        "//proto:tink_cc_proto",
+        "//subtle/prf:streaming_prf",
+    ],
+)
+
+cc_test(
+    name = "prf_based_deriver_test",
+    srcs = ["prf_based_deriver_test.cc"],
+    deps = [
+        ":prf_based_deriver",
+        "//:cleartext_keyset_handle",
+        "//aead:aead_key_templates",
+        "//aead:aes_gcm_key_manager",
+        "//prf:hkdf_prf_key_manager",
+        "//proto:aes_gcm_cc_proto",
+        "//util:test_matchers",
+        "//util:test_util",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "prf_based_deriver_key_manager",
+    hdrs = ["prf_based_deriver_key_manager.h"],
+    include_prefix = "tink/keyderivation/internal",
+    deps = [
+        ":prf_based_deriver",
+        "//keyderivation:keyset_deriver",
+        "//proto:prf_based_deriver_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "prf_based_deriver_key_manager_test",
+    srcs = ["prf_based_deriver_key_manager_test.cc"],
+    deps = [
+        ":prf_based_deriver_key_manager",
+        "//:cleartext_keyset_handle",
+        "//aead:aead_key_templates",
+        "//aead:aes_gcm_key_manager",
+        "//keyderivation:keyset_deriver",
+        "//prf:hkdf_prf_key_manager",
+        "//proto:aes_gcm_cc_proto",
+        "//proto:hkdf_prf_cc_proto",
+        "//proto:prf_based_deriver_cc_proto",
+        "//proto:tink_cc_proto",
+        "//subtle",
+        "//util:statusor",
+        "//util:test_matchers",
+        "//util:test_util",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/keyderivation/internal/CMakeLists.txt b/cc/keyderivation/internal/CMakeLists.txt
new file mode 100644
index 0000000..2fd4c7e
--- /dev/null
+++ b/cc/keyderivation/internal/CMakeLists.txt
@@ -0,0 +1,69 @@
+tink_module(keyderivation::internal)
+
+tink_cc_library(
+  NAME prf_based_deriver
+  SRCS
+    prf_based_deriver.cc
+    prf_based_deriver.h
+  DEPS
+    tink::core::cleartext_keyset_handle
+    tink::core::keyset_handle
+    tink::core::registry
+    tink::keyderivation::keyset_deriver
+    tink::subtle::prf::streaming_prf
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME prf_based_deriver_test
+  SRCS
+    prf_based_deriver_test.cc
+  DEPS
+    tink::keyderivation::internal::prf_based_deriver
+    gmock
+    absl::status
+    tink::core::cleartext_keyset_handle
+    tink::aead::aead_key_templates
+    tink::aead::aes_gcm_key_manager
+    tink::prf::hkdf_prf_key_manager
+    tink::util::test_matchers
+    tink::util::test_util
+    tink::proto::aes_gcm_cc_proto
+)
+
+tink_cc_library(
+  NAME prf_based_deriver_key_manager
+  SRCS
+    prf_based_deriver_key_manager.h
+  DEPS
+    tink::keyderivation::internal::prf_based_deriver
+    absl::memory
+    absl::status
+    absl::strings
+    tink::keyderivation::keyset_deriver
+    tink::proto::prf_based_deriver_cc_proto
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME prf_based_deriver_key_manager_test
+  SRCS
+    prf_based_deriver_key_manager_test.cc
+  DEPS
+    tink::keyderivation::internal::prf_based_deriver_key_manager
+    gmock
+    absl::status
+    tink::core::cleartext_keyset_handle
+    tink::aead::aead_key_templates
+    tink::aead::aes_gcm_key_manager
+    tink::keyderivation::keyset_deriver
+    tink::prf::hkdf_prf_key_manager
+    tink::subtle::subtle
+    tink::util::statusor
+    tink::util::test_matchers
+    tink::util::test_util
+    tink::proto::aes_gcm_cc_proto
+    tink::proto::hkdf_prf_cc_proto
+    tink::proto::prf_based_deriver_cc_proto
+    tink::proto::tink_cc_proto
+)
diff --git a/cc/keyderivation/internal/prf_based_deriver.cc b/cc/keyderivation/internal/prf_based_deriver.cc
new file mode 100644
index 0000000..3aa7c72
--- /dev/null
+++ b/cc/keyderivation/internal/prf_based_deriver.cc
@@ -0,0 +1,90 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/internal/prf_based_deriver.h"
+
+#include <memory>
+#include <utility>
+
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/keyset_handle.h"
+#include "tink/registry.h"
+#include "tink/subtle/prf/streaming_prf.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+
+util::StatusOr<std::unique_ptr<KeysetDeriver>> PrfBasedDeriver::New(
+    const KeyData& prf_key, const KeyTemplate& key_template) {
+  // Validate `prf_key`.
+  util::StatusOr<std::unique_ptr<StreamingPrf>> streaming_prf =
+      Registry::GetPrimitive<StreamingPrf>(prf_key);
+  if (!streaming_prf.ok()) {
+    return streaming_prf.status();
+  }
+
+  // Validate `key_template`.
+  std::unique_ptr<InputStream> randomness = (*streaming_prf)->ComputePrf("s");
+  util::StatusOr<KeyData> key_data =
+      internal::RegistryImpl::GlobalInstance().DeriveKey(key_template,
+                                                         randomness.get());
+  if (!key_data.ok()) {
+    return key_data.status();
+  }
+
+  return {absl::WrapUnique<PrfBasedDeriver>(
+      new PrfBasedDeriver(*std::move(streaming_prf), key_template))};
+}
+
+util::StatusOr<std::unique_ptr<KeysetHandle>> PrfBasedDeriver::DeriveKeyset(
+    absl::string_view salt) const {
+  std::unique_ptr<InputStream> randomness = streaming_prf_->ComputePrf(salt);
+
+  util::StatusOr<KeyData> key_data =
+      crypto::tink::internal::RegistryImpl::GlobalInstance().DeriveKey(
+          key_template_, randomness.get());
+  if (!key_data.ok()) {
+    return key_data.status();
+  }
+
+  // Fill in placeholder values for key ID, status, and output prefix type.
+  // These will be populated with the correct values in the keyset deriver
+  // factory. This is acceptable because the keyset as-is will never leave Tink,
+  // and the user only interacts via the keyset deriver factory.
+  Keyset::Key key;
+  *key.mutable_key_data() = *key_data;
+  key.set_status(KeyStatusType::UNKNOWN_STATUS);
+  key.set_key_id(0);
+  key.set_output_prefix_type(OutputPrefixType::UNKNOWN_PREFIX);
+
+  Keyset keyset;
+  *keyset.add_key() = key;
+  keyset.set_primary_key_id(0);
+
+  return CleartextKeysetHandle::GetKeysetHandle(keyset);
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/internal/prf_based_deriver.h b/cc/keyderivation/internal/prf_based_deriver.h
new file mode 100644
index 0000000..0b07d3f
--- /dev/null
+++ b/cc/keyderivation/internal/prf_based_deriver.h
@@ -0,0 +1,55 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEYDERIVATION_INTERNAL_PRF_BASED_DERIVER_H_
+#define TINK_KEYDERIVATION_INTERNAL_PRF_BASED_DERIVER_H_
+
+#include <memory>
+#include <utility>
+
+#include "tink/keyderivation/keyset_deriver.h"
+#include "tink/keyset_handle.h"
+#include "tink/subtle/prf/streaming_prf.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// The PrfBasedDeriver first uses a PRF to get some randomness, then gives this
+// to the Tink registry to derive a key.
+class PrfBasedDeriver : public KeysetDeriver {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetDeriver>> New(
+      const ::google::crypto::tink::KeyData& prf_key,
+      const ::google::crypto::tink::KeyTemplate& key_template);
+
+  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> DeriveKeyset(
+      absl::string_view salt) const override;
+
+ private:
+  PrfBasedDeriver(std::unique_ptr<StreamingPrf> streaming_prf,
+                  const ::google::crypto::tink::KeyTemplate& key_template)
+      : streaming_prf_(std::move(streaming_prf)), key_template_(key_template) {}
+
+  const ::std::unique_ptr<StreamingPrf> streaming_prf_;
+  const ::google::crypto::tink::KeyTemplate key_template_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEYDERIVATION_INTERNAL_PRF_BASED_DERIVER_H_
diff --git a/cc/keyderivation/internal/prf_based_deriver_key_manager.h b/cc/keyderivation/internal/prf_based_deriver_key_manager.h
new file mode 100644
index 0000000..f5725d3
--- /dev/null
+++ b/cc/keyderivation/internal/prf_based_deriver_key_manager.h
@@ -0,0 +1,132 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEYDERIVATION_INTERNAL_PRF_BASED_DERIVER_KEY_MANAGER_H_
+#define TINK_KEYDERIVATION_INTERNAL_PRF_BASED_DERIVER_KEY_MANAGER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "tink/keyderivation/internal/prf_based_deriver.h"
+#include "tink/keyderivation/keyset_deriver.h"
+#include "proto/prf_based_deriver.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+class PrfBasedDeriverKeyManager
+    : public KeyTypeManager<google::crypto::tink::PrfBasedDeriverKey,
+                            google::crypto::tink::PrfBasedDeriverKeyFormat,
+                            List<KeysetDeriver>> {
+ public:
+  class KeysetDeriverFactory : public PrimitiveFactory<KeysetDeriver> {
+    crypto::tink::util::StatusOr<std::unique_ptr<KeysetDeriver>> Create(
+        const google::crypto::tink::PrfBasedDeriverKey& key) const override {
+      return internal::PrfBasedDeriver::New(
+          key.prf_key(), key.params().derived_key_template());
+    }
+  };
+
+  PrfBasedDeriverKeyManager()
+      : KeyTypeManager(absl::make_unique<
+                       PrfBasedDeriverKeyManager::KeysetDeriverFactory>()) {}
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override { return 0; }
+
+  google::crypto::tink::KeyData::KeyMaterialType key_material_type()
+      const override {
+    return google::crypto::tink::KeyData::SYMMETRIC;
+  }
+
+  const std::string& get_key_type() const override { return key_type_; }
+
+  crypto::tink::util::Status ValidateKey(
+      const google::crypto::tink::PrfBasedDeriverKey& key) const override {
+    crypto::tink::util::Status status =
+        ValidateVersion(key.version(), get_version());
+    if (!status.ok()) return status;
+    if (!key.has_prf_key()) {
+      return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
+                                        "key.prf_key() must be set");
+    }
+    if (!key.params().has_derived_key_template()) {
+      return crypto::tink::util::Status(
+          absl::StatusCode::kInvalidArgument,
+          "key.params().derived_key_template() must be set");
+    }
+    return util::OkStatus();
+  }
+
+  crypto::tink::util::Status ValidateKeyFormat(
+      const google::crypto::tink::PrfBasedDeriverKeyFormat& key_format)
+      const override {
+    if (!key_format.has_prf_key_template()) {
+      return crypto::tink::util::Status(absl::StatusCode::kInvalidArgument,
+                                        "key.prf_key_template() must be set");
+    }
+    if (!key_format.params().has_derived_key_template()) {
+      return crypto::tink::util::Status(
+          absl::StatusCode::kInvalidArgument,
+          "key_format.params().derived_key_template() must be set");
+    }
+    return util::OkStatus();
+  }
+
+  crypto::tink::util::StatusOr<google::crypto::tink::PrfBasedDeriverKey>
+  CreateKey(const google::crypto::tink::PrfBasedDeriverKeyFormat& key_format)
+      const override {
+    crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+        prf_key = CreateKeyData(key_format.prf_key_template());
+    if (!prf_key.ok()) return prf_key.status();
+
+    // Java and Go implementations perform additional verification by getting a
+    // StreamingPrf primitive from the registry and trying to derive
+    // `key_format.params().derived_key_template()` with a fake salt. This is
+    // currently not possible in C++.
+
+    google::crypto::tink::PrfBasedDeriverKey key;
+    key.set_version(get_version());
+    *key.mutable_params()->mutable_derived_key_template() =
+        key_format.params().derived_key_template();
+    *key.mutable_prf_key() = **std::move(prf_key);
+    return key;
+  }
+
+ protected:
+  virtual crypto::tink::util::StatusOr<
+      std::unique_ptr<google::crypto::tink::KeyData>>
+  CreateKeyData(const google::crypto::tink::KeyTemplate& key_template) const {
+    return Registry::NewKeyData(key_template);
+  }
+
+ private:
+  const std::string key_type_ =
+      absl::StrCat(kTypeGoogleapisCom,
+                   google::crypto::tink::PrfBasedDeriverKey().GetTypeName());
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEYDERIVATION_INTERNAL_PRF_BASED_DERIVER_KEY_MANAGER_H_
diff --git a/cc/keyderivation/internal/prf_based_deriver_key_manager_test.cc b/cc/keyderivation/internal/prf_based_deriver_key_manager_test.cc
new file mode 100644
index 0000000..62bb1f1
--- /dev/null
+++ b/cc/keyderivation/internal/prf_based_deriver_key_manager_test.cc
@@ -0,0 +1,287 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/internal/prf_based_deriver_key_manager.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/keyderivation/keyset_deriver.h"
+#include "tink/prf/hkdf_prf_key_manager.h"
+#include "tink/subtle/random.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_gcm.pb.h"
+#include "proto/hkdf_prf.pb.h"
+#include "proto/prf_based_deriver.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::crypto::tink::util::StatusOr;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::HkdfPrfKey;
+using ::google::crypto::tink::HkdfPrfKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::PrfBasedDeriverKey;
+using ::google::crypto::tink::PrfBasedDeriverKeyFormat;
+using ::testing::Eq;
+using ::testing::SizeIs;
+
+TEST(PrfBasedDeriverKeyManagerTest, Basics) {
+  EXPECT_THAT(PrfBasedDeriverKeyManager().get_version(), Eq(0));
+  EXPECT_THAT(PrfBasedDeriverKeyManager().get_key_type(),
+              Eq("type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey"));
+  EXPECT_THAT(PrfBasedDeriverKeyManager().key_material_type(),
+              Eq(KeyData::SYMMETRIC));
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, ValidateKeyEmpty) {
+  EXPECT_THAT(PrfBasedDeriverKeyManager().ValidateKey(PrfBasedDeriverKey()),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, ValidateKey) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.set_key_value("0123456789abcdef");
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+
+  PrfBasedDeriverKey key;
+  key.set_version(0);
+  *key.mutable_prf_key() = test::AsKeyData(prf_key, KeyData::SYMMETRIC);
+  *key.mutable_params()->mutable_derived_key_template() =
+      AeadKeyTemplates::Aes256Gcm();
+
+  EXPECT_THAT(PrfBasedDeriverKeyManager().ValidateKey(key), IsOk());
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, ValidateKeyWithWrongVersion) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.set_key_value("0123456789abcdef");
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+
+  PrfBasedDeriverKey key;
+  key.set_version(1);
+  *key.mutable_prf_key() = test::AsKeyData(prf_key, KeyData::SYMMETRIC);
+  *key.mutable_params()->mutable_derived_key_template() =
+      AeadKeyTemplates::Aes256Gcm();
+
+  EXPECT_THAT(PrfBasedDeriverKeyManager().ValidateKey(key),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, ValidateKeyFormat) {
+  HkdfPrfKeyFormat prf_key_format;
+  prf_key_format.set_key_size(16);
+  prf_key_format.mutable_params()->set_hash(HashType::SHA256);
+
+  PrfBasedDeriverKeyFormat key_format;
+  key_format.mutable_prf_key_template()->set_type_url(
+      HkdfPrfKeyManager().get_key_type());
+  key_format.mutable_prf_key_template()->set_value(
+      prf_key_format.SerializeAsString());
+  *key_format.mutable_params()->mutable_derived_key_template() =
+      AeadKeyTemplates::Aes256Gcm();
+
+  EXPECT_THAT(PrfBasedDeriverKeyManager().ValidateKeyFormat(key_format),
+              IsOk());
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, ValidateKeyFormatEmpty) {
+  EXPECT_THAT(
+      PrfBasedDeriverKeyManager().ValidateKeyFormat(PrfBasedDeriverKeyFormat()),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, CreateKey) {
+  Registry::Reset();
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<HkdfPrfKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<HkdfPrfKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), true),
+              IsOk());
+
+  HkdfPrfKeyFormat prf_key_format;
+  prf_key_format.set_key_size(32);
+  prf_key_format.mutable_params()->set_hash(HashType::SHA256);
+
+  PrfBasedDeriverKeyFormat key_format;
+  key_format.mutable_prf_key_template()->set_type_url(
+      HkdfPrfKeyManager().get_key_type());
+  key_format.mutable_prf_key_template()->set_value(
+      prf_key_format.SerializeAsString());
+  *key_format.mutable_params()->mutable_derived_key_template() =
+      AeadKeyTemplates::Aes256Gcm();
+
+  util::StatusOr<PrfBasedDeriverKey> key =
+      PrfBasedDeriverKeyManager().CreateKey(key_format);
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT((*key).version(), Eq(0));
+  EXPECT_THAT((*key).prf_key().type_url(),
+              Eq(HkdfPrfKeyManager().get_key_type()));
+  EXPECT_THAT((*key).prf_key().key_material_type(), Eq(KeyData::SYMMETRIC));
+
+  HkdfPrfKey prf_key;
+  ASSERT_TRUE(prf_key.ParseFromString((*key).prf_key().value()));
+  EXPECT_THAT(prf_key.key_value().size(), Eq(32));
+
+  EXPECT_THAT((*key).params().derived_key_template().type_url(),
+              Eq(key_format.params().derived_key_template().type_url()));
+  EXPECT_THAT((*key).params().derived_key_template().value(),
+              Eq(key_format.params().derived_key_template().value()));
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, CreateKeyWithInvalidPrfKey) {
+  Registry::Reset();
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<HkdfPrfKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<HkdfPrfKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), true),
+              IsOk());
+
+  HkdfPrfKeyFormat prf_key_format;
+  prf_key_format.set_key_size(32);
+  prf_key_format.mutable_params()->set_hash(HashType::UNKNOWN_HASH);
+
+  PrfBasedDeriverKeyFormat key_format;
+  key_format.mutable_prf_key_template()->set_type_url(
+      HkdfPrfKeyManager().get_key_type());
+  key_format.mutable_prf_key_template()->set_value(
+      prf_key_format.SerializeAsString());
+  *key_format.mutable_params()->mutable_derived_key_template() =
+      AeadKeyTemplates::Aes256Gcm();
+
+  EXPECT_THAT(PrfBasedDeriverKeyManager().CreateKey(key_format).status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, CreateKeyWithInvalidDerivedKeyTemplate) {
+  Registry::Reset();
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<HkdfPrfKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<HkdfPrfKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), true),
+              IsOk());
+
+  HkdfPrfKeyFormat prf_key_format;
+  prf_key_format.set_key_size(32);
+  prf_key_format.mutable_params()->set_hash(HashType::SHA256);
+  KeyTemplate derived_template;
+  derived_template.set_type_url("nonexistent.type.url");
+
+  PrfBasedDeriverKeyFormat key_format;
+  key_format.mutable_prf_key_template()->set_type_url(
+      HkdfPrfKeyManager().get_key_type());
+  key_format.mutable_prf_key_template()->set_value(
+      prf_key_format.SerializeAsString());
+  *key_format.mutable_params()->mutable_derived_key_template() =
+      derived_template;
+
+  // See comment in PrfBasedDeriverKeyManager::CreateKey().
+  EXPECT_THAT(PrfBasedDeriverKeyManager().CreateKey(key_format).status(),
+              IsOk());
+}
+
+TEST(PrfBasedDeriverKeyManagerTest, GetPrimitive) {
+  Registry::Reset();
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<PrfBasedDeriverKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<HkdfPrfKeyManager>(), true),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), true),
+              IsOk());
+
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+  prf_key.mutable_params()->set_salt(subtle::Random::GetRandomBytes(15));
+  prf_key.set_key_value(subtle::Random::GetRandomBytes(33));
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+  PrfBasedDeriverKey key;
+  key.set_version(0);
+  *key.mutable_prf_key() = test::AsKeyData(prf_key, KeyData::SYMMETRIC);
+  *key.mutable_params()->mutable_derived_key_template() =
+      AeadKeyTemplates::Aes256Gcm();
+
+  StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      PrfBasedDeriverKeyManager().GetPrimitive<KeysetDeriver>(key);
+  ASSERT_THAT(deriver, IsOk());
+
+  std::string salt = subtle::Random::GetRandomBytes(23);
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      (*deriver)->DeriveKeyset(salt);
+  ASSERT_THAT(handle, IsOk());
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+
+  StatusOr<std::unique_ptr<KeysetDeriver>> direct_deriver =
+      internal::PrfBasedDeriver::New(key.prf_key(),
+                                     key.params().derived_key_template());
+  ASSERT_THAT(direct_deriver, IsOk());
+  util::StatusOr<std::unique_ptr<KeysetHandle>> direct_handle =
+      (*direct_deriver)->DeriveKeyset(salt);
+  ASSERT_THAT(direct_handle, IsOk());
+  Keyset direct_keyset = CleartextKeysetHandle::GetKeyset(**direct_handle);
+
+  ASSERT_THAT(keyset.key(), SizeIs(1));
+  ASSERT_THAT(direct_keyset.key(), SizeIs(1));
+
+  ASSERT_THAT(keyset.key(0).key_data().type_url(),
+              Eq(keyset.key(0).key_data().type_url()));
+
+  AesGcmKey derived_key;
+  ASSERT_TRUE(derived_key.ParseFromString(keyset.key(0).key_data().value()));
+  AesGcmKey direct_derived_key;
+  ASSERT_TRUE(direct_derived_key.ParseFromString(
+      direct_keyset.key(0).key_data().value()));
+  EXPECT_THAT(derived_key.key_value(), Eq(direct_derived_key.key_value()));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/internal/prf_based_deriver_test.cc b/cc/keyderivation/internal/prf_based_deriver_test.cc
new file mode 100644
index 0000000..c46e572
--- /dev/null
+++ b/cc/keyderivation/internal/prf_based_deriver_test.cc
@@ -0,0 +1,342 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/internal/prf_based_deriver.h"
+
+#include <memory>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/prf/hkdf_prf_key_manager.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_gcm.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::HkdfPrfKey;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::Ne;
+using ::testing::SizeIs;
+
+class PrfBasedDeriverTest : public ::testing::Test {
+ public:
+  static void SetUpTestSuite() {
+    ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                   absl::make_unique<AesGcmKeyManager>(), true),
+               IsOk());
+    ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                   absl::make_unique<HkdfPrfKeyManager>(), true),
+               IsOk());
+  }
+};
+
+TEST_F(PrfBasedDeriverTest, New) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+  prf_key.mutable_params()->set_salt("");
+  prf_key.set_key_value("0123456789abcdef0123456789abcdef");
+
+  EXPECT_THAT(PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                                   AeadKeyTemplates::Aes128Gcm()),
+              IsOk());
+}
+
+TEST_F(PrfBasedDeriverTest, NewWithInvalidPrfKey) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::UNKNOWN_HASH);
+  prf_key.mutable_params()->set_salt("");
+  prf_key.set_key_value("0123456789abcdef0123456789abcdef");
+
+  EXPECT_THAT(PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                                   AeadKeyTemplates::Aes128Gcm())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(PrfBasedDeriverTest, NewWithInvalidDerivedKeyTemplate) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+  prf_key.mutable_params()->set_salt("");
+  prf_key.set_key_value("0123456789abcdef0123456789abcdef");
+
+  KeyTemplate derived_template;
+  derived_template.set_type_url("some non-existent type url");
+  EXPECT_THAT(PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                                   derived_template)
+                  .status(),
+              StatusIs(absl::StatusCode::kNotFound));
+}
+
+// Test vector from https://tools.ietf.org/html/rfc5869#appendix-A.2.
+TEST_F(PrfBasedDeriverTest, DeriveKeyset) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+  prf_key.mutable_params()->set_salt(
+      test::HexDecodeOrDie("606162636465666768696a6b6c6d6e6f"
+                           "707172737475767778797a7b7c7d7e7f"
+                           "808182838485868788898a8b8c8d8e8f"
+                           "909192939495969798999a9b9c9d9e9f"
+                           "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"));
+  prf_key.set_key_value(
+      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"
+                           "101112131415161718191a1b1c1d1e1f"
+                           "202122232425262728292a2b2c2d2e2f"
+                           "303132333435363738393a3b3c3d3e3f"
+                           "404142434445464748494a4b4c4d4e4f"));
+
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                           AeadKeyTemplates::Aes256Gcm());
+  ASSERT_THAT(deriver, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      (*deriver)->DeriveKeyset(
+          test::HexDecodeOrDie("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+                               "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+                               "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+                               "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+                               "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"));
+  ASSERT_THAT(handle, IsOk());
+
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+  ASSERT_THAT(keyset.key(), SizeIs(1));
+  EXPECT_THAT(keyset.key(0).key_data().type_url(),
+              Eq(AesGcmKeyManager().get_key_type()));
+  EXPECT_THAT(keyset.key(0).key_data().key_material_type(),
+              Eq(AesGcmKeyManager().key_material_type()));
+
+  AesGcmKey derived_key;
+  ASSERT_TRUE(derived_key.ParseFromString(keyset.key(0).key_data().value()));
+  EXPECT_THAT(derived_key.version(), Eq(AesGcmKeyManager().get_version()));
+  // The derived key value is the first 32 bytes of the test vector's OKM field.
+  EXPECT_THAT(test::HexEncode(derived_key.key_value()),
+              Eq("b11e398dc80327a1c8e7f78c596a4934"
+                 "4f012eda2d4efad8a050cc4c19afa97c"));
+}
+
+TEST_F(PrfBasedDeriverTest, DeriveKeysetHoldsPlaceholderValues) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA256);
+  prf_key.mutable_params()->set_salt("");
+  prf_key.set_key_value("0123456789abcdef0123456789abcdef");
+
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                           AeadKeyTemplates::Aes128Gcm());
+  ASSERT_THAT(deriver, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      (*deriver)->DeriveKeyset("salt");
+  ASSERT_THAT(handle, IsOk());
+
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+  EXPECT_THAT(keyset.primary_key_id(), Eq(0));
+  ASSERT_THAT(keyset.key(), SizeIs(1));
+  EXPECT_THAT(keyset.key(0).status(), Eq(KeyStatusType::UNKNOWN_STATUS));
+  EXPECT_THAT(keyset.key(0).key_id(), Eq(0));
+  EXPECT_THAT(keyset.key(0).output_prefix_type(),
+              Eq(OutputPrefixType::UNKNOWN_PREFIX));
+}
+
+TEST_F(PrfBasedDeriverTest, DeriveKeysetWithDifferentPrfKeys) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA512);
+  prf_key.mutable_params()->set_salt("");
+  prf_key.set_key_value(subtle::Random::GetRandomBytes(32));
+
+  AesGcmKey derived_key_0;
+  AesGcmKey derived_key_1;
+  {
+    util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+        PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                             AeadKeyTemplates::Aes128Gcm());
+    ASSERT_THAT(deriver, IsOk());
+
+    util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+        (*deriver)->DeriveKeyset("salt");
+    ASSERT_THAT(handle, IsOk());
+
+    Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+    ASSERT_TRUE(
+        derived_key_0.ParseFromString(keyset.key(0).key_data().value()));
+  }
+  {
+    prf_key.set_key_value(prf_key.key_value() + '\0');
+    util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+        PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                             AeadKeyTemplates::Aes128Gcm());
+    ASSERT_THAT(deriver, IsOk());
+
+    util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+        (*deriver)->DeriveKeyset("salt");
+    ASSERT_THAT(handle, IsOk());
+
+    Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+    ASSERT_TRUE(
+        derived_key_1.ParseFromString(keyset.key(0).key_data().value()));
+  }
+  EXPECT_THAT(derived_key_0.key_value(), SizeIs(16));
+  EXPECT_THAT(derived_key_1.key_value(), SizeIs(16));
+  EXPECT_THAT(derived_key_0.key_value(), Ne(derived_key_1.key_value()));
+}
+
+TEST_F(PrfBasedDeriverTest, DeriveKeysetWithDifferentSalts) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA512);
+  prf_key.set_key_value(subtle::Random::GetRandomBytes(32));
+
+  AesGcmKey derived_key_0;
+  AesGcmKey derived_key_1;
+  {
+    util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+        PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                             AeadKeyTemplates::Aes128Gcm());
+    ASSERT_THAT(deriver, IsOk());
+
+    util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+        (*deriver)->DeriveKeyset(std::string(10, '\0'));
+    ASSERT_THAT(handle, IsOk());
+
+    Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+    ASSERT_TRUE(
+        derived_key_0.ParseFromString(keyset.key(0).key_data().value()));
+  }
+  {
+    util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+        PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                             AeadKeyTemplates::Aes128Gcm());
+    ASSERT_THAT(deriver, IsOk());
+
+    util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+        (*deriver)->DeriveKeyset(std::string(11, '\0'));
+    ASSERT_THAT(handle, IsOk());
+
+    Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+    ASSERT_TRUE(
+        derived_key_1.ParseFromString(keyset.key(0).key_data().value()));
+  }
+  EXPECT_THAT(derived_key_0.key_value(), SizeIs(16));
+  EXPECT_THAT(derived_key_1.key_value(), SizeIs(16));
+  EXPECT_THAT(derived_key_0.key_value(), Ne(derived_key_1.key_value()));
+}
+
+// Test vector generated with Java implementation.
+TEST_F(PrfBasedDeriverTest, DeriveKeysetWithTestVector0) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA512);
+  prf_key.set_key_value(test::HexDecodeOrDie(
+      "a1a2a3a4a5a6a7a8a9aaabacadaeafb1b2b3b4b5b6b7b8b9babbbcbdbebf"
+      "c1c2c3c4c5c6c7c8c9cacbcccdcecfc1c2c3c4c5c6c7c8c9cacbcccdcecf"
+      "00"));
+
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                           AeadKeyTemplates::Aes128Gcm());
+  ASSERT_THAT(deriver, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      (*deriver)->DeriveKeyset(test::HexDecodeOrDie("1122334455"));
+  ASSERT_THAT(handle, IsOk());
+
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+  AesGcmKey derived_key;
+  ASSERT_TRUE(derived_key.ParseFromString(keyset.key(0).key_data().value()));
+  EXPECT_THAT(derived_key.version(), Eq(AesGcmKeyManager().get_version()));
+  EXPECT_THAT(test::HexEncode(derived_key.key_value()),
+              Eq("31c449af66b669b9963ef2df30dfe5f9"));
+}
+
+// Test vector generated with Java implementation.
+TEST_F(PrfBasedDeriverTest, DeriveKeysetWithTestVector1) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA512);
+  prf_key.set_key_value(test::HexDecodeOrDie(
+      "c1c2c3c4c5c6c7c8c9cacbcccdcecfc1c2c3c4c5c6c7c8c9cacbcccdcecf"
+      "a1a2a3a4a5a6a7a8a9aaabacadaeafb1b2b3b4b5b6b7b8b9babbbcbdbebf"));
+
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                           AeadKeyTemplates::Aes128Gcm());
+  ASSERT_THAT(deriver, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      (*deriver)->DeriveKeyset(test::HexDecodeOrDie("00"));
+  ASSERT_THAT(handle, IsOk());
+
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+  AesGcmKey derived_key;
+  ASSERT_TRUE(derived_key.ParseFromString(keyset.key(0).key_data().value()));
+  EXPECT_THAT(derived_key.version(), Eq(AesGcmKeyManager().get_version()));
+  EXPECT_THAT(test::HexEncode(derived_key.key_value()),
+              Eq("887af0808c1855eba1594bf540adb957"));
+}
+
+// Test vector generated with Java implementation.
+TEST_F(PrfBasedDeriverTest, DeriveKeysetWithEmptySaltTestVector) {
+  HkdfPrfKey prf_key;
+  prf_key.set_version(0);
+  prf_key.mutable_params()->set_hash(HashType::SHA512);
+  prf_key.set_key_value(test::HexDecodeOrDie(
+      "c1c2c3c4c5c6c7c8c9cacbcccdcecfc1c2c3c4c5c6c7c8c9cacbcccdcecf"
+      "a1a2a3a4a5a6a7a8a9aaabacadaeafb1b2b3b4b5b6b7b8b9babbbcbdbebf"));
+
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      PrfBasedDeriver::New(test::AsKeyData(prf_key, KeyData::SYMMETRIC),
+                           AeadKeyTemplates::Aes128Gcm());
+  ASSERT_THAT(deriver, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      (*deriver)->DeriveKeyset("");
+  ASSERT_THAT(handle, IsOk());
+
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(**handle);
+  AesGcmKey derived_key;
+  ASSERT_TRUE(derived_key.ParseFromString(keyset.key(0).key_data().value()));
+  EXPECT_THAT(derived_key.version(), Eq(AesGcmKeyManager().get_version()));
+  EXPECT_THAT(test::HexEncode(derived_key.key_value()),
+              Eq("fb2b448c2595caf75129e282af758bf1"));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/key_derivation_config.cc b/cc/keyderivation/key_derivation_config.cc
new file mode 100644
index 0000000..2194e33
--- /dev/null
+++ b/cc/keyderivation/key_derivation_config.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/key_derivation_config.h"
+
+#include "tink/config/tink_fips.h"
+#include "tink/keyderivation/internal/prf_based_deriver_key_manager.h"
+#include "tink/keyderivation/keyset_deriver_wrapper.h"
+#include "tink/prf/hkdf_prf_key_manager.h"
+
+namespace crypto {
+namespace tink {
+
+// static
+util::Status KeyDerivationConfig::Register() {
+  // Register primitive wrappers.
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<KeysetDeriverWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+
+  // Currently, no KeysetDeriver key managers only use FIPS-validated
+  // implementations, so none are registered in FIPS-only mode.
+  if (IsFipsModeEnabled()) {
+    return util::OkStatus();
+  }
+
+  // Register required key manager for PrfBasedDeriverKeyManager.
+  status = Registry::RegisterKeyTypeManager(
+      absl::make_unique<HkdfPrfKeyManager>(), true);
+  if (!status.ok()) {
+    return status;
+  }
+
+  // Register key managers.
+  return Registry::RegisterKeyTypeManager(
+      absl::make_unique<internal::PrfBasedDeriverKeyManager>(), true);
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/key_derivation_config.h b/cc/keyderivation/key_derivation_config.h
new file mode 100644
index 0000000..5575024
--- /dev/null
+++ b/cc/keyderivation/key_derivation_config.h
@@ -0,0 +1,46 @@
+// Copyright 2020 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEYDERIVATION_KEY_DERIVATION_CONFIG_H_
+#define TINK_KEYDERIVATION_KEY_DERIVATION_CONFIG_H_
+
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+///////////////////////////////////////////////////////////////////////////////
+// Static methods and constants for registering to the Registry all
+// KeysetDeriver key types supported in a particular release of Tink.
+//
+// To register all KeysetDeriver key types, one can do:
+//
+//   crypto::tink::util::Status status = KeyDerivationConfig::Register();
+//
+class KeyDerivationConfig {
+ public:
+  // Registers KeysetDeriver primitive wrapper and key managers for all
+  // KeyDerivation key types from the current Tink release.
+  static crypto::tink::util::Status Register();
+
+ private:
+  KeyDerivationConfig() {}
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEYDERIVATION_KEY_DERIVATION_CONFIG_H_
diff --git a/cc/keyderivation/key_derivation_config_test.cc b/cc/keyderivation/key_derivation_config_test.cc
new file mode 100644
index 0000000..5540d26
--- /dev/null
+++ b/cc/keyderivation/key_derivation_config_test.cc
@@ -0,0 +1,83 @@
+// Copyright 2020 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/key_derivation_config.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aead_config.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/keyderivation/key_derivation_key_templates.h"
+#include "tink/keyderivation/keyset_deriver.h"
+#include "tink/prf/prf_key_templates.h"
+#include "tink/registry.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::testing::Not;
+
+TEST(KeyDerivationConfigTest, Register) {
+  Registry::Reset();
+
+  EXPECT_THAT(KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+                  PrfKeyTemplates::HkdfSha256(), AeadKeyTemplates::Aes256Gcm()),
+              Not(IsOk()));
+
+  ASSERT_THAT(KeyDerivationConfig::Register(), IsOk());
+  ASSERT_THAT(AeadConfig::Register(), IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<AesGcmKeyManager>(), true),
+              IsOk());
+
+  util::StatusOr<::google::crypto::tink::KeyTemplate> templ =
+      KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+          PrfKeyTemplates::HkdfSha256(), AeadKeyTemplates::Aes256Gcm());
+  ASSERT_THAT(templ, IsOk());
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      KeysetHandle::GenerateNew(*templ);
+  ASSERT_THAT(handle, IsOk());
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      (*handle)->GetPrimitive<KeysetDeriver>();
+  ASSERT_THAT(deriver, IsOk());
+  util::StatusOr<std::unique_ptr<KeysetHandle>> derived_handle =
+      (*deriver)->DeriveKeyset("salty");
+  ASSERT_THAT(derived_handle, IsOk());
+
+  util::StatusOr<std::unique_ptr<Aead>> aead =
+      (*derived_handle)->GetPrimitive<Aead>();
+  ASSERT_THAT(aead, IsOk());
+  std::string plaintext = "plaintext";
+  std::string ad = "ad";
+  util::StatusOr<std::string> ciphertext = (*aead)->Encrypt(plaintext, ad);
+  ASSERT_THAT(ciphertext, IsOk());
+  util::StatusOr<std::string> got = (*aead)->Decrypt(*ciphertext, ad);
+  ASSERT_THAT(got, IsOk());
+  EXPECT_EQ(plaintext, *got);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/key_derivation_key_templates.cc b/cc/keyderivation/key_derivation_key_templates.cc
new file mode 100644
index 0000000..8b0e66f
--- /dev/null
+++ b/cc/keyderivation/key_derivation_key_templates.cc
@@ -0,0 +1,62 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/key_derivation_key_templates.h"
+
+#include <memory>
+
+#include "tink/keyderivation/internal/prf_based_deriver_key_manager.h"
+#include "tink/subtle/random.h"
+
+namespace crypto {
+namespace tink {
+
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::PrfBasedDeriverKeyFormat;
+
+util::StatusOr<KeyTemplate>
+KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+    const KeyTemplate& prf_key_template,
+    const KeyTemplate& derived_key_template) {
+  KeyTemplate key_template;
+  key_template.set_type_url(
+      internal::PrfBasedDeriverKeyManager().get_key_type());
+  key_template.set_output_prefix_type(
+      derived_key_template.output_prefix_type());
+
+  PrfBasedDeriverKeyFormat format;
+  *format.mutable_prf_key_template() = prf_key_template;
+  *format.mutable_params()->mutable_derived_key_template() =
+      derived_key_template;
+  format.SerializeToString(key_template.mutable_value());
+
+  // Verify `key_template` is derivable.
+  util::StatusOr<std::unique_ptr<KeysetHandle>> handle =
+      KeysetHandle::GenerateNew(key_template);
+  if (!handle.ok()) {
+    return handle.status();
+  }
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> deriver =
+      (*handle)->GetPrimitive<KeysetDeriver>();
+  if (!deriver.ok()) {
+    return deriver.status();
+  }
+
+  return key_template;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/key_derivation_key_templates.h b/cc/keyderivation/key_derivation_key_templates.h
new file mode 100644
index 0000000..50af285
--- /dev/null
+++ b/cc/keyderivation/key_derivation_key_templates.h
@@ -0,0 +1,52 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEYDERIVATION_KEY_DERIVATION_KEY_TEMPLATES_H_
+#define TINK_KEYDERIVATION_KEY_DERIVATION_KEY_TEMPLATES_H_
+
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+///////////////////////////////////////////////////////////////////////////////
+// Methods to generate KeyTemplates for key derivation.
+class KeyDerivationKeyTemplates {
+ public:
+  // Creates a key template for key derivation that uses a PRF to derive a key
+  // that adheres to `derived_key_template`. The following must be true:
+  //   (1) `prf_key_template` is a PRF key template, i.e.
+  //         `keyset_handle->GetPrimitive<StreamingPrf>()` works.
+  //   (2) `derived_key_template` describes a key type that supports derivation.
+  //
+  // The output prefix type of the derived key will match the output prefix type
+  // of `derived_key_template`.
+  //
+  // This function verifies the newly created key template by creating a
+  // KeysetDeriver primitive from it. This requires both the `prf_key_template`
+  // and `derived_key_template` key types to be in the registry. It also
+  // attempts to derive a key, returning an error on failure.
+  static util::StatusOr<google::crypto::tink::KeyTemplate>
+  CreatePrfBasedKeyTemplate(
+      const google::crypto::tink::KeyTemplate& prf_key_template,
+      const google::crypto::tink::KeyTemplate& derived_key_template);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEYDERIVATION_KEY_DERIVATION_KEY_TEMPLATES_H_
diff --git a/cc/keyderivation/key_derivation_key_templates_test.cc b/cc/keyderivation/key_derivation_key_templates_test.cc
new file mode 100644
index 0000000..a6e01b3
--- /dev/null
+++ b/cc/keyderivation/key_derivation_key_templates_test.cc
@@ -0,0 +1,206 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/key_derivation_key_templates.h"
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/keyderivation/internal/prf_based_deriver_key_manager.h"
+#include "tink/keyderivation/keyset_deriver_wrapper.h"
+#include "tink/prf/hkdf_prf_key_manager.h"
+#include "tink/prf/prf_key_templates.h"
+#include "tink/registry.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+#include "proto/prf_based_deriver.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyTemplate;
+using ::google::crypto::tink::OutputPrefixType;
+using ::google::crypto::tink::PrfBasedDeriverKeyFormat;
+using ::testing::Eq;
+using ::testing::Not;
+
+class KeyDerivationKeyTemplatesTest : public ::testing::Test {
+ protected:
+  void TearDown() override { Registry::Reset(); }
+};
+
+TEST_F(KeyDerivationKeyTemplatesTest, CreatePrfBasedKeyTemplate) {
+  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
+                  absl::make_unique<KeysetDeriverWrapper>()),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<internal::PrfBasedDeriverKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<HkdfPrfKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+
+  std::vector<OutputPrefixType> output_prefix_types = {
+      OutputPrefixType::RAW, OutputPrefixType::TINK, OutputPrefixType::LEGACY};
+  for (OutputPrefixType output_prefix_type : output_prefix_types) {
+    KeyTemplate derived_key_template = AeadKeyTemplates::Aes256Gcm();
+    derived_key_template.set_output_prefix_type(output_prefix_type);
+    util::StatusOr<KeyTemplate> key_template =
+        KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+            PrfKeyTemplates::HkdfSha256(), derived_key_template);
+
+    ASSERT_THAT(key_template, IsOk());
+    EXPECT_THAT(
+        key_template->type_url(),
+        Eq("type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey"));
+    EXPECT_THAT(key_template->type_url(),
+                Eq(internal::PrfBasedDeriverKeyManager().get_key_type()));
+    EXPECT_THAT(key_template->output_prefix_type(), Eq(output_prefix_type));
+
+    PrfBasedDeriverKeyFormat key_format;
+    EXPECT_TRUE(key_format.ParseFromString(key_template->value()));
+    EXPECT_THAT(
+        internal::PrfBasedDeriverKeyManager().ValidateKeyFormat(key_format),
+        IsOk());
+  }
+}
+
+TEST_F(KeyDerivationKeyTemplatesTest, CreatePrfBasedKeyTemplateInvalidPrfKey) {
+  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
+                  absl::make_unique<KeysetDeriverWrapper>()),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<internal::PrfBasedDeriverKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<HkdfPrfKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+
+  EXPECT_THAT(KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+                  AeadKeyTemplates::Aes256Gcm(), AeadKeyTemplates::Aes256Gcm())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(KeyDerivationKeyTemplatesTest,
+       CreatePrfBasedKeyTemplateInvalidDerivedKeyTemplate) {
+  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
+                  absl::make_unique<KeysetDeriverWrapper>()),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<internal::PrfBasedDeriverKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<HkdfPrfKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+
+  util::StatusOr<KeyTemplate> derived_key_template =
+      KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+          PrfKeyTemplates::HkdfSha256(), AeadKeyTemplates::Aes256Gcm());
+  EXPECT_THAT(KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+                  PrfKeyTemplates::HkdfSha256(), *derived_key_template)
+                  .status(),
+              StatusIs(absl::StatusCode::kUnimplemented));
+}
+
+TEST_F(KeyDerivationKeyTemplatesTest,
+       CreatePrfBasedKeyTemplateNoPrfBasedDeriverKeyManager) {
+  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
+                  absl::make_unique<KeysetDeriverWrapper>()),
+              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<HkdfPrfKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+
+  EXPECT_THAT(KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+                  PrfKeyTemplates::HkdfSha256(), AeadKeyTemplates::Aes256Gcm()),
+              Not(IsOk()));
+}
+
+TEST_F(KeyDerivationKeyTemplatesTest,
+       CreatePrfBasedKeyTemplateNoHkdfPrfKeyManager) {
+  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
+                  absl::make_unique<KeysetDeriverWrapper>()),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<internal::PrfBasedDeriverKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<AesGcmKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+
+  EXPECT_THAT(KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+                  PrfKeyTemplates::HkdfSha256(), AeadKeyTemplates::Aes256Gcm()),
+              Not(IsOk()));
+}
+
+TEST_F(KeyDerivationKeyTemplatesTest,
+       CreatePrfBasedKeyTemplateNoAesGcmKeyManager) {
+  ASSERT_THAT(Registry::RegisterPrimitiveWrapper(
+                  absl::make_unique<KeysetDeriverWrapper>()),
+              IsOk());
+  ASSERT_THAT(Registry::RegisterKeyTypeManager(
+                  absl::make_unique<internal::PrfBasedDeriverKeyManager>(),
+                  /*new_key_allowed=*/true),
+              IsOk());
+  ASSERT_THAT(
+      Registry::RegisterKeyTypeManager(absl::make_unique<HkdfPrfKeyManager>(),
+                                       /*new_key_allowed=*/true),
+      IsOk());
+
+  EXPECT_THAT(KeyDerivationKeyTemplates::CreatePrfBasedKeyTemplate(
+                  PrfKeyTemplates::HkdfSha256(), AeadKeyTemplates::Aes256Gcm()),
+              Not(IsOk()));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/keyset_deriver.h b/cc/keyderivation/keyset_deriver.h
new file mode 100644
index 0000000..f6f11bd
--- /dev/null
+++ b/cc/keyderivation/keyset_deriver.h
@@ -0,0 +1,46 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEYDERIVATION_KEYSET_DERIVER_H_
+#define TINK_KEYDERIVATION_KEYSET_DERIVER_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// KeysetDeriver is the interface used to derive new keysets based on an
+// additional input, the salt.
+//
+// The salt is used to create the keyset using a pseudorandom function.
+// Implementations must be indistinguishable from ideal KeysetDerivers, which,
+// for every salt, generates a new random keyset and caches it.
+class KeysetDeriver {
+ public:
+  virtual crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
+  DeriveKeyset(absl::string_view salt) const = 0;
+
+  virtual ~KeysetDeriver() = default;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEYDERIVATION_KEYSET_DERIVER_H_
diff --git a/cc/keyderivation/keyset_deriver_wrapper.cc b/cc/keyderivation/keyset_deriver_wrapper.cc
new file mode 100644
index 0000000..cf74a11
--- /dev/null
+++ b/cc/keyderivation/keyset_deriver_wrapper.cc
@@ -0,0 +1,104 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/keyset_deriver_wrapper.h"
+
+#include <memory>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+
+util::Status Validate(PrimitiveSet<KeysetDeriver>* deriver_set) {
+  if (deriver_set == nullptr) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "deriver_set must be non-NULL");
+  }
+  if (deriver_set->get_primary() == nullptr) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "deriver_set has no primary");
+  }
+  return util::OkStatus();
+}
+
+class KeysetDeriverSetWrapper : public KeysetDeriver {
+ public:
+  explicit KeysetDeriverSetWrapper(
+      std::unique_ptr<PrimitiveSet<KeysetDeriver>> deriver_set)
+      : deriver_set_(std::move(deriver_set)) {}
+
+  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> DeriveKeyset(
+      absl::string_view salt) const override;
+
+  ~KeysetDeriverSetWrapper() override = default;
+
+ private:
+  std::unique_ptr<PrimitiveSet<KeysetDeriver>> deriver_set_;
+};
+
+crypto::tink::util::StatusOr<KeyData> DeriveAndGetKeyData(
+    absl::string_view salt, const KeysetDeriver& deriver) {
+  auto keyset_handle_or = deriver.DeriveKeyset(salt);
+  if (!keyset_handle_or.ok()) return keyset_handle_or.status();
+  const Keyset& keyset =
+      CleartextKeysetHandle::GetKeyset(*keyset_handle_or.value());
+  if (keyset.key_size() != 1) {
+    return util::Status(
+        absl::StatusCode::kInternal,
+        "Wrapper Deriver must create a keyset with exactly one KeyData");
+  }
+  return keyset.key(0).key_data();
+}
+
+crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
+KeysetDeriverSetWrapper::DeriveKeyset(absl::string_view salt) const {
+  Keyset keyset;
+  for (const auto* entry : deriver_set_->get_all_in_keyset_order()) {
+    Keyset::Key* key = keyset.add_key();
+
+    crypto::tink::util::StatusOr<KeyData> key_data_or =
+        DeriveAndGetKeyData(salt, entry->get_primitive());
+    if (!key_data_or.ok()) return key_data_or.status();
+    *key->mutable_key_data() = key_data_or.value();
+    key->set_status(entry->get_status());
+    key->set_output_prefix_type(entry->get_output_prefix_type());
+    key->set_key_id(entry->get_key_id());
+  }
+  keyset.set_primary_key_id(deriver_set_->get_primary()->get_key_id());
+  return CleartextKeysetHandle::GetKeysetHandle(keyset);
+}
+
+}  // namespace
+
+crypto::tink::util::StatusOr<std::unique_ptr<KeysetDeriver>>
+KeysetDeriverWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<KeysetDeriver>> deriver_set) const {
+  util::Status status = Validate(deriver_set.get());
+  if (!status.ok()) return status;
+  return {absl::make_unique<KeysetDeriverSetWrapper>(std::move(deriver_set))};
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/keyset_deriver_wrapper.h b/cc/keyderivation/keyset_deriver_wrapper.h
new file mode 100644
index 0000000..256440a
--- /dev/null
+++ b/cc/keyderivation/keyset_deriver_wrapper.h
@@ -0,0 +1,44 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEYDERIVATION_KEYSET_DERIVER_WRAPPER_H_
+#define TINK_KEYDERIVATION_KEYSET_DERIVER_WRAPPER_H_
+
+#include <memory>
+
+#include "tink/keyderivation/keyset_deriver.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+
+namespace crypto {
+namespace tink {
+
+// A KeysetDeriverWrapper wraps the KeysetDeriver primitive.
+//
+// The wrapper derives a key from each key in a keyset. It returns the resulting
+// keys in a new keyset. Each of the derived keys inherits key_id, status, and
+// output_prefix_type from the key from which it was derived.
+class KeysetDeriverWrapper
+    : public PrimitiveWrapper<KeysetDeriver, KeysetDeriver> {
+ public:
+  crypto::tink::util::StatusOr<std::unique_ptr<KeysetDeriver>> Wrap(
+      std::unique_ptr<PrimitiveSet<KeysetDeriver>> deriver_set) const override;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEYDERIVATION_KEYSET_DERIVER_WRAPPER_H_
diff --git a/cc/keyderivation/keyset_deriver_wrapper_test.cc b/cc/keyderivation/keyset_deriver_wrapper_test.cc
new file mode 100644
index 0000000..dde29fb
--- /dev/null
+++ b/cc/keyderivation/keyset_deriver_wrapper_test.cc
@@ -0,0 +1,197 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+//     http://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 "tink/keyderivation/keyset_deriver_wrapper.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/keyderivation/keyset_deriver.h"
+#include "tink/primitive_set.h"
+#include "tink/util/test_matchers.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeysetInfo;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+// TODO(b/255828521): Move this to a shared location once KeysetDeriver is in
+// the public API.
+class DummyDeriver : public KeysetDeriver {
+ public:
+  explicit DummyDeriver(absl::string_view name) : name_(name) {}
+  util::StatusOr<std::unique_ptr<KeysetHandle>> DeriveKeyset(
+      absl::string_view salt) const override {
+    Keyset::Key key;
+    key.mutable_key_data()->set_type_url(
+        absl::StrCat(name_.size(), ":", name_, salt));
+    key.set_status(KeyStatusType::UNKNOWN_STATUS);
+    key.set_key_id(0);
+    key.set_output_prefix_type(OutputPrefixType::UNKNOWN_PREFIX);
+
+    Keyset keyset;
+    *keyset.add_key() = key;
+    keyset.set_primary_key_id(0);
+    return CleartextKeysetHandle::GetKeysetHandle(keyset);
+  }
+
+ private:
+  std::string name_;
+};
+
+TEST(KeysetDeriverWrapperTest, WrapNullptr) {
+  EXPECT_THAT(KeysetDeriverWrapper().Wrap(nullptr).status(),
+              StatusIs(absl::StatusCode::kInternal, HasSubstr("non-NULL")));
+}
+
+TEST(KeysetDeriverWrapperTest, WrapEmpty) {
+  EXPECT_THAT(
+      KeysetDeriverWrapper()
+          .Wrap(absl::make_unique<PrimitiveSet<KeysetDeriver>>())
+          .status(),
+      StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("no primary")));
+}
+
+TEST(KeysetDeriverWrapperTest, WrapNoPrimary) {
+  auto deriver_set = absl::make_unique<PrimitiveSet<KeysetDeriver>>();
+  KeysetInfo::KeyInfo key_info;
+  key_info.set_key_id(1234);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::TINK);
+
+  EXPECT_THAT(
+      deriver_set->AddPrimitive(absl::make_unique<DummyDeriver>(""), key_info)
+          .status(),
+      IsOk());
+
+  EXPECT_THAT(
+      KeysetDeriverWrapper().Wrap(std::move(deriver_set)).status(),
+      StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("no primary")));
+}
+
+TEST(KeysetDeriverWrapperTest, WrapSingle) {
+  auto deriver_set = absl::make_unique<PrimitiveSet<KeysetDeriver>>();
+  KeysetInfo::KeyInfo key_info;
+  key_info.set_key_id(1234);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::TINK);
+  key_info.set_type_url(
+      "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey");
+
+  auto entry_or = deriver_set->AddPrimitive(
+      absl::make_unique<DummyDeriver>("wrap_single_key"), key_info);
+  ASSERT_THAT(entry_or, IsOk());
+  EXPECT_THAT(deriver_set->set_primary(entry_or.value()), IsOk());
+
+  auto wrapper_deriver_or = KeysetDeriverWrapper().Wrap(std::move(deriver_set));
+
+  ASSERT_THAT(wrapper_deriver_or, IsOk());
+
+  auto derived_keyset_or =
+      wrapper_deriver_or.value()->DeriveKeyset("wrap_single_salt");
+
+  ASSERT_THAT(derived_keyset_or, IsOk());
+
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(*derived_keyset_or.value());
+
+  EXPECT_THAT(keyset.primary_key_id(), Eq(1234));
+  ASSERT_THAT(keyset.key_size(), Eq(1));
+  EXPECT_THAT(keyset.key(0).key_data().type_url(),
+              Eq("15:wrap_single_keywrap_single_salt"));
+  EXPECT_THAT(keyset.key(0).status(), Eq(KeyStatusType::ENABLED));
+  EXPECT_THAT(keyset.key(0).key_id(), Eq(1234));
+  EXPECT_THAT(keyset.key(0).output_prefix_type(), Eq(OutputPrefixType::TINK));
+}
+
+TEST(KeysetDeriverWrapperTest, WrapMultiple) {
+  auto pset = absl::make_unique<PrimitiveSet<KeysetDeriver>>();
+  std::vector<KeysetInfo::KeyInfo> key_infos;
+
+  KeysetInfo::KeyInfo key_info;
+  key_info.set_key_id(1010101);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::RAW);
+  key_info.set_type_url(
+      "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey");
+  ASSERT_THAT(
+      pset->AddPrimitive(absl::make_unique<DummyDeriver>("k1"), key_info)
+          .status(),
+      IsOk());
+  key_infos.push_back(key_info);
+
+  key_info.set_key_id(2020202);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::LEGACY);
+  key_info.set_type_url(
+      "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey");
+  util::StatusOr<PrimitiveSet<KeysetDeriver>::Entry<KeysetDeriver>*> entry =
+      pset->AddPrimitive(absl::make_unique<DummyDeriver>("k2"), key_info);
+  ASSERT_THAT(entry, IsOk());
+  ASSERT_THAT(pset->set_primary(*entry), IsOk());
+  key_infos.push_back(key_info);
+
+  key_info.set_key_id(3030303);
+  key_info.set_status(KeyStatusType::ENABLED);
+  key_info.set_output_prefix_type(OutputPrefixType::TINK);
+  key_info.set_type_url(
+      "type.googleapis.com/google.crypto.tink.PrfBasedDeriverKey");
+  ASSERT_THAT(
+      pset->AddPrimitive(absl::make_unique<DummyDeriver>("k3"), key_info),
+      IsOk());
+  key_infos.push_back(key_info);
+
+  util::StatusOr<std::unique_ptr<KeysetDeriver>> wrapper_deriver =
+      KeysetDeriverWrapper().Wrap(std::move(pset));
+  ASSERT_THAT(wrapper_deriver, IsOk());
+
+  util::StatusOr<std::unique_ptr<KeysetHandle>> derived_keyset =
+      (*wrapper_deriver)->DeriveKeyset("salt");
+  ASSERT_THAT(derived_keyset, IsOk());
+  Keyset keyset = CleartextKeysetHandle::GetKeyset(**derived_keyset);
+
+  EXPECT_THAT(keyset.primary_key_id(), Eq(2020202));
+  ASSERT_THAT(keyset.key_size(), Eq(3));
+
+  for (int i = 0; i < keyset.key().size(); i++) {
+    std::string type_url = absl::StrCat("2:k", i + 1, "salt");
+    EXPECT_THAT(keyset.key(i).key_data().type_url(), Eq(type_url));
+
+    Keyset::Key key = keyset.key(i);
+    key_info = key_infos[i];
+    EXPECT_THAT(key.status(), Eq(key_info.status()));
+    EXPECT_THAT(key.key_id(), Eq(key_info.key_id()));
+    EXPECT_THAT(key.output_prefix_type(), Eq(key_info.output_prefix_type()));
+  }
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/keyderivation/subtle/BUILD.bazel b/cc/keyderivation/subtle/BUILD.bazel
new file mode 100644
index 0000000..5b01f6e
--- /dev/null
+++ b/cc/keyderivation/subtle/BUILD.bazel
@@ -0,0 +1 @@
+licenses(["notice"])
diff --git a/cc/keyderivation/subtle/CMakeLists.txt b/cc/keyderivation/subtle/CMakeLists.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cc/keyderivation/subtle/CMakeLists.txt
diff --git a/cc/keyset_handle.h b/cc/keyset_handle.h
index 19c25bd..8d594e9 100644
--- a/cc/keyset_handle.h
+++ b/cc/keyset_handle.h
@@ -12,20 +12,29 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-///////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
 
 #ifndef TINK_KEYSET_HANDLE_H_
 #define TINK_KEYSET_HANDLE_H_
 
+#include <cstdint>
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/base/attributes.h"
 #include "absl/container/flat_hash_map.h"
+#include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "tink/aead.h"
+#include "tink/configuration.h"
+#include "tink/internal/configuration_impl.h"
 #include "tink/internal/key_info.h"
+#include "tink/key.h"
+#include "tink/key_gen_configuration.h"
 #include "tink/key_manager.h"
+#include "tink/key_status.h"
 #include "tink/keyset_reader.h"
 #include "tink/keyset_writer.h"
 #include "tink/primitive_set.h"
@@ -40,6 +49,60 @@
 // key material.
 class KeysetHandle {
  public:
+  // Represents a single entry in a `KeysetHandle`. Some current behavior will
+  // be changed in the future.
+  class Entry {
+   public:
+    // May return an internal class in case there is no implementation of the
+    // corresponding key class yet.  Returned value only valid for lifetime
+    // of entry object.
+    std::shared_ptr<const Key> GetKey() const { return key_; }
+
+    // Status indicates whether or not a key should still be used.
+    KeyStatus GetStatus() const { return status_; }
+
+    // ID should be unique (though currently Tink still accepts keysets with
+    // repeated IDs).
+    int GetId() const { return id_; }
+
+    // Should return true for exactly one entry (though currently Tink still
+    // accepts keysets which have no entry marked as primary).
+    bool IsPrimary() const { return is_primary_; }
+
+   private:
+    friend class KeysetHandle;
+    friend class KeysetHandleBuilder;
+
+    Entry(std::shared_ptr<const Key> key, KeyStatus status, int id,
+          bool is_primary)
+        : key_(std::move(key)),
+          status_(status),
+          id_(id),
+          is_primary_(is_primary) {}
+
+    std::shared_ptr<const Key> key_;
+    KeyStatus status_;
+    int id_;
+    bool is_primary_;
+  };
+
+  // Returns the number of entries in this keyset.
+  int size() const { return keyset_.key_size(); }
+  // Validates single `KeysetHandle::Entry` at `index` by making sure that the
+  // key entry's type URL is printable and that it has a valid key status.
+  crypto::tink::util::Status ValidateAt(int index) const;
+  // Validates each individual `KeysetHandle::Entry` in keyset handle by calling
+  // `ValidateAt()`.  Also, checks that there is a single enabled primary key.
+  crypto::tink::util::Status Validate() const;
+  // Returns entry for primary key in this keyset. Crashes if `Validate()`
+  // does not return an OK status.  Call `Validate()` prior to calling this
+  // method to avoid potentially crashing your program.
+  Entry GetPrimary() const;
+  // Returns the `KeysetHandle::Entry` at `index`.  Crashes if
+  // `ValidateAt(index)` does not return an OK status.  Call `ValidateAt(index)`
+  // prior to calling this method to avoid potentially crashing your program.
+  Entry operator[](int index) const;
+
   // Creates a KeysetHandle from an encrypted keyset obtained via `reader`
   // using `master_key_aead` to decrypt the keyset, with monitoring annotations
   // `monitoring_annotations`; by default, `monitoring_annotations` is empty.
@@ -69,10 +132,19 @@
                const absl::flat_hash_map<std::string, std::string>&
                    monitoring_annotations = {});
 
-  // Returns a KeysetHandle for a new keyset that contains a single fresh key
-  // generated according to `key_template`. The keyset is annotated for
-  // monitoring with `monitoring_annotations`; by default,
-  // `monitoring_annotations` is empty.
+  // Returns a KeysetHandle containing a single new key generated according to
+  // `key_template` and using `config`. The keyset is annotated for monitoring
+  // with `monitoring_annotations`, which is empty by default.
+  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
+  GenerateNew(const google::crypto::tink::KeyTemplate& key_template,
+              const crypto::tink::KeyGenConfiguration& config,
+              const absl::flat_hash_map<std::string, std::string>&
+                  monitoring_annotations = {});
+
+  // TODO(b/265865177): Deprecate.
+  // Returns a KeysetHandle containing a single new key generated according to
+  // `key_template`. The keyset is annotated for monitoring with
+  // `monitoring_annotations`, which is empty by default.
   static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
   GenerateNew(const google::crypto::tink::KeyTemplate& key_template,
               const absl::flat_hash_map<std::string, std::string>&
@@ -108,10 +180,14 @@
   crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
   GetPublicKeysetHandle() const;
 
-  // Creates a wrapped primitive corresponding to this keyset or fails with
-  // a non-ok status. Uses the KeyManager and PrimitiveWrapper objects in the
-  // global registry to create the primitive. This function is the most common
-  // way of creating a primitive.
+  // Creates a wrapped primitive using this keyset handle and config, which
+  // stores necessary primitive wrappers and key type managers.
+  template <class P>
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
+      const Configuration& config) const;
+
+  // Creates a wrapped primitive using this keyset handle and the global
+  // registry, which stores necessary primitive wrappers and key type managers.
   template <class P>
   crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive() const;
 
@@ -128,15 +204,27 @@
   // The classes below need access to get_keyset();
   friend class CleartextKeysetHandle;
   friend class KeysetManager;
-  friend class RegistryImpl;
 
   // TestKeysetHandle::GetKeyset() provides access to get_keyset().
   friend class TestKeysetHandle;
 
+  // KeysetHandleBuilder::Build() needs access to KeysetHandle(Keyset).
+  friend class KeysetHandleBuilder;
+
   // Creates a handle that contains the given keyset.
-  explicit KeysetHandle(google::crypto::tink::Keyset keyset);
-  // Creates a handle that contains the given keyset.
-  explicit KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset);
+  explicit KeysetHandle(google::crypto::tink::Keyset keyset)
+      : keyset_(std::move(keyset)) {}
+  explicit KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset)
+      : keyset_(std::move(*keyset)) {}
+  // Creates a handle that contains the given `keyset` and `entries`.
+  explicit KeysetHandle(
+      google::crypto::tink::Keyset keyset,
+      const std::vector<std::shared_ptr<const Entry>>& entries)
+      : keyset_(std::move(keyset)), entries_(entries) {}
+  explicit KeysetHandle(
+      std::unique_ptr<google::crypto::tink::Keyset> keyset,
+      const std::vector<std::shared_ptr<const Entry>>& entries)
+      : keyset_(std::move(*keyset)), entries_(entries) {}
   // Creates a handle that contains the given `keyset` and
   // `monitoring_annotations`.
   KeysetHandle(google::crypto::tink::Keyset keyset,
@@ -149,16 +237,45 @@
                    monitoring_annotations)
       : keyset_(std::move(*keyset)),
         monitoring_annotations_(monitoring_annotations) {}
+  // Creates a handle that contains the given `keyset`, `entries`, and
+  // `monitoring_annotations`.
+  KeysetHandle(google::crypto::tink::Keyset keyset,
+               const std::vector<std::shared_ptr<const Entry>>& entries,
+               const absl::flat_hash_map<std::string, std::string>&
+                   monitoring_annotations)
+      : keyset_(std::move(keyset)),
+        entries_(entries),
+        monitoring_annotations_(monitoring_annotations) {}
+  KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset,
+               const std::vector<std::shared_ptr<const Entry>>& entries,
+               const absl::flat_hash_map<std::string, std::string>&
+                   monitoring_annotations)
+      : keyset_(std::move(*keyset)),
+        entries_(entries),
+        monitoring_annotations_(monitoring_annotations) {}
 
-  // Helper function which generates a key from a template, then adds it
-  // to the keyset. TODO(tholenst): Change this to a proper member operating
-  // on the internal keyset.
+  // Generates a key from `key_template` and adds it `keyset`.
   static crypto::tink::util::StatusOr<uint32_t> AddToKeyset(
       const google::crypto::tink::KeyTemplate& key_template, bool as_primary,
+      const crypto::tink::KeyGenConfiguration& config,
       google::crypto::tink::Keyset* keyset);
 
+  // Creates list of KeysetHandle::Entry entries derived from `keyset` in order.
+  static crypto::tink::util::StatusOr<std::vector<std::shared_ptr<const Entry>>>
+  GetEntriesFromKeyset(const google::crypto::tink::Keyset& keyset);
+
+  // Creates KeysetHandle::Entry for `key`, which will be set to primary if
+  // its key id equals `primary_key_id`.
+  static util::StatusOr<Entry> CreateEntry(
+      const google::crypto::tink::Keyset::Key& key, uint32_t primary_key_id);
+
+  // Generates a key from `key_template` and adds it to the keyset handle.
+  crypto::tink::util::StatusOr<uint32_t> AddKey(
+      const google::crypto::tink::KeyTemplate& key_template, bool as_primary,
+      const crypto::tink::KeyGenConfiguration& config);
+
   // Returns keyset held by this handle.
-  const google::crypto::tink::Keyset& get_keyset() const;
+  const google::crypto::tink::Keyset& get_keyset() const { return keyset_; }
 
   // Creates a set of primitives corresponding to the keys with
   // (status == ENABLED) in the keyset given in 'keyset_handle',
@@ -171,7 +288,17 @@
   crypto::tink::util::StatusOr<std::unique_ptr<PrimitiveSet<P>>> GetPrimitives(
       const KeyManager<P>* custom_manager) const;
 
+  // Creates KeysetHandle::Entry from `keyset_` at `index`.
+  Entry CreateEntryAt(int index) const;
+
   google::crypto::tink::Keyset keyset_;
+  // If this keyset handle has been created with a constructor that does not
+  // accept an entries argument, then `entries` will be empty and operator[]
+  // will fall back to creating the key entry on demand from `keyset_`.
+  //
+  // If `entries_` is not empty, then it should contain exactly one key entry
+  // for each key proto in `keyset_`.
+  std::vector<std::shared_ptr<const Entry>> entries_;
   absl::flat_hash_map<std::string, std::string> monitoring_annotations_;
 };
 
@@ -183,8 +310,8 @@
 KeysetHandle::GetPrimitives(const KeyManager<P>* custom_manager) const {
   crypto::tink::util::Status status = ValidateKeyset(get_keyset());
   if (!status.ok()) return status;
-  std::unique_ptr<PrimitiveSet<P>> primitives(
-      new PrimitiveSet<P>(monitoring_annotations_));
+  typename PrimitiveSet<P>::Builder primitives_builder;
+  primitives_builder.AddAnnotations(monitoring_annotations_);
   for (const google::crypto::tink::Keyset::Key& key : get_keyset().key()) {
     if (key.status() == google::crypto::tink::KeyStatusType::ENABLED) {
       std::unique_ptr<P> primitive;
@@ -198,23 +325,57 @@
         if (!primitive_result.ok()) return primitive_result.status();
         primitive = std::move(primitive_result.value());
       }
-      auto entry_result =
-          primitives->AddPrimitive(std::move(primitive), KeyInfoFromKey(key));
-      if (!entry_result.ok()) return entry_result.status();
       if (key.key_id() == get_keyset().primary_key_id()) {
-        auto primary_result = primitives->set_primary(entry_result.value());
-        if (!primary_result.ok()) return primary_result;
+        primitives_builder.AddPrimaryPrimitive(std::move(primitive),
+                                               KeyInfoFromKey(key));
+      } else {
+        primitives_builder.AddPrimitive(std::move(primitive),
+                                        KeyInfoFromKey(key));
       }
     }
   }
-  return std::move(primitives);
+  auto primitives = std::move(primitives_builder).Build();
+  if (!primitives.ok()) return primitives.status();
+  return absl::make_unique<PrimitiveSet<P>>(*std::move(primitives));
 }
 
 template <class P>
+crypto::tink::util::StatusOr<std::unique_ptr<P>> KeysetHandle::GetPrimitive(
+    const Configuration& config) const {
+  if (crypto::tink::internal::ConfigurationImpl::GetGlobalRegistryMode(
+          config)) {
+    return crypto::tink::internal::RegistryImpl::GlobalInstance().WrapKeyset<P>(
+        keyset_, monitoring_annotations_);
+  }
+
+  crypto::tink::util::StatusOr<
+      const crypto::tink::internal::KeysetWrapperStore*>
+      wrapper_store =
+          crypto::tink::internal::ConfigurationImpl::GetKeysetWrapperStore(
+              config);
+  if (!wrapper_store.ok()) {
+    return wrapper_store.status();
+  }
+  crypto::tink::util::StatusOr<const crypto::tink::internal::KeysetWrapper<P>*>
+      wrapper = (*wrapper_store)->Get<P>();
+  if (!wrapper.ok()) {
+    return wrapper.status();
+  }
+  return (*wrapper)->Wrap(keyset_, monitoring_annotations_);
+}
+
+// TODO(b/265865177): Deprecate.
+template <class P>
 crypto::tink::util::StatusOr<std::unique_ptr<P>> KeysetHandle::GetPrimitive()
     const {
-  return internal::RegistryImpl::GlobalInstance().WrapKeyset<P>(
-      keyset_, monitoring_annotations_);
+  // TODO(b/265705174): Replace with ConfigGlobalRegistry instance.
+  crypto::tink::Configuration config;
+  crypto::tink::util::Status status =
+      crypto::tink::internal::ConfigurationImpl::SetGlobalRegistryMode(config);
+  if (!status.ok()) {
+    return status;
+  }
+  return GetPrimitive<P>(config);
 }
 
 template <class P>
diff --git a/cc/keyset_handle_builder.h b/cc/keyset_handle_builder.h
new file mode 100644
index 0000000..72d3af8
--- /dev/null
+++ b/cc/keyset_handle_builder.h
@@ -0,0 +1,169 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_KEYSET_HANDLE_BUILDER_H_
+#define TINK_KEYSET_HANDLE_BUILDER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "tink/internal/keyset_handle_builder_entry.h"
+#include "tink/key.h"
+#include "tink/key_status.h"
+#include "tink/keyset_handle.h"
+#include "tink/parameters.h"
+
+namespace crypto {
+namespace tink {
+
+// Creates new `KeysetHandle` objects.
+class KeysetHandleBuilder {
+ public:
+  // Movable, but not copyable.
+  KeysetHandleBuilder(KeysetHandleBuilder&& other) = default;
+  KeysetHandleBuilder& operator=(KeysetHandleBuilder&& other) = default;
+  KeysetHandleBuilder(const KeysetHandleBuilder& other) = delete;
+  KeysetHandleBuilder& operator=(const KeysetHandleBuilder& other) = delete;
+
+  // Creates initially empty keyset handle builder.
+  KeysetHandleBuilder() = default;
+  // Creates keyset handle builder by initially moving keys from `handle`.
+  explicit KeysetHandleBuilder(const KeysetHandle& handle);
+
+  // Represents a single entry in a `KeysetHandleBuilder`.
+  class Entry {
+   public:
+    // Movable, but not copyable.
+    Entry(Entry&& other) = default;
+    Entry& operator=(Entry&& other) = default;
+    Entry(const Entry& other) = delete;
+    Entry& operator=(const Entry& other) = delete;
+
+    // Creates new KeysetHandleBuilder::Entry from a given `key`. Also, sets
+    // key `status` and whether or not the key `is_primary`.
+    static Entry CreateFromKey(std::shared_ptr<const Key> key, KeyStatus status,
+                               bool is_primary);
+
+    template <typename CopyableKey>
+    inline static Entry CreateFromCopyableKey(CopyableKey key, KeyStatus status,
+                                              bool is_primary) {
+      auto copyable_key = absl::make_unique<CopyableKey>(std::move(key));
+      return CreateFromKey(std::move(copyable_key), status, is_primary);
+    }
+
+    // Creates new KeysetHandleBuilder::Entry from given `parameters`. Also,
+    // sets key `status` and whether or not the key `is_primary`. If `id`
+    // does not have a value, then the key will be assigned a random id.
+    static Entry CreateFromParams(std::shared_ptr<const Parameters> parameters,
+                                  KeyStatus status, bool is_primary,
+                                  absl::optional<int> id = absl::nullopt);
+
+    template <typename CopyableParameters>
+    inline static Entry CreateFromCopyableParams(
+        CopyableParameters parameters, KeyStatus status, bool is_primary,
+        absl::optional<int> id = absl::nullopt) {
+      auto copyable_params =
+          absl::make_unique<CopyableParameters>(std::move(parameters));
+      return CreateFromParams(std::move(copyable_params), status, is_primary,
+                              id);
+    }
+
+    // Sets the key status of this entry.
+    void SetStatus(KeyStatus status) { entry_->SetStatus(status); }
+    // Returns key status of this entry.
+    KeyStatus GetStatus() const { return entry_->GetStatus(); }
+
+    // Assigns a fixed id when this keyset is built.
+    void SetFixedId(int id) { entry_->SetFixedId(id); }
+    // Assigns an unused random id when this keyset is built.
+    void SetRandomId() { entry_->SetRandomId(); }
+
+    // Sets this entry as the primary key.
+    void SetPrimary() { entry_->SetPrimary(); }
+    // Unsets this entry as the primary key.
+    void UnsetPrimary() { entry_->UnsetPrimary(); }
+    // Returns whether or not this entry has been marked as a primary.
+    bool IsPrimary() const { return entry_->IsPrimary(); }
+
+   private:
+    friend class KeysetHandleBuilder;
+
+    explicit Entry(std::unique_ptr<internal::KeysetHandleBuilderEntry> entry)
+        : entry_(std::move(entry)) {}
+
+    // Returns whether or not this entry has a randomly assigned id.
+    bool HasRandomId() {
+      return entry_->GetKeyIdStrategyEnum() ==
+             internal::KeyIdStrategyEnum::kRandomId;
+    }
+
+    internal::KeyIdStrategy GetKeyIdStrategy() {
+      return entry_->GetKeyIdStrategy();
+    }
+
+    crypto::tink::util::StatusOr<google::crypto::tink::Keyset::Key>
+    CreateKeysetKey(int id) {
+      return entry_->CreateKeysetKey(id);
+    }
+
+    std::unique_ptr<internal::KeysetHandleBuilderEntry> entry_;
+    bool added_to_builder_ = false;
+  };
+
+  // Adds an `entry` to the keyset builder. Crashes if `entry` has already been
+  // added to a keyset handle builder.
+  KeysetHandleBuilder& AddEntry(KeysetHandleBuilder::Entry entry);
+  // Removes an entry at `index` from keyset builder.
+  KeysetHandleBuilder& RemoveEntry(int index);
+
+  // Returns the number of Entry objects in this keyset builder.
+  int size() const { return entries_.size(); }
+
+  // Returns entry from keyset builder at `index`.
+  KeysetHandleBuilder::Entry& operator[](int index) { return entries_[index]; }
+
+  // Creates a new `KeysetHandle` object.
+  //
+  // Note: Since KeysetHandleBuilder::Entry objects might have randomly
+  // generated IDs, Build() can only be called once on a single
+  // KeysetHandleBuilder object.  Otherwise, the KeysetHandleBuilder::Entry
+  // IDs would randomly change for each call to Build(), which would result
+  // in incompatible keysets.
+  crypto::tink::util::StatusOr<KeysetHandle> Build();
+
+ private:
+  // Select the next key id based on the given strategy.
+  crypto::tink::util::StatusOr<int> NextIdFromKeyIdStrategy(
+      internal::KeyIdStrategy strategy, const std::set<int>& ids_so_far);
+
+  // Unset primary flag on all entries.
+  void ClearPrimary();
+
+  // Verify that entries with fixed IDs do not follow entries with random IDs.
+  crypto::tink::util::Status CheckIdAssignments();
+
+  std::vector<KeysetHandleBuilder::Entry> entries_;
+
+  bool build_called_ = false;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_KEYSET_HANDLE_BUILDER_H_
diff --git a/cc/keyset_manager.h b/cc/keyset_manager.h
index d3bfcff..645fc83 100644
--- a/cc/keyset_manager.h
+++ b/cc/keyset_manager.h
@@ -16,6 +16,8 @@
 #ifndef TINK_KEYSET_MANAGER_H_
 #define TINK_KEYSET_MANAGER_H_
 
+#include <memory>
+
 #include "absl/base/thread_annotations.h"
 #include "absl/synchronization/mutex.h"
 #include "tink/util/status.h"
@@ -34,7 +36,7 @@
 class KeysetManager {
  public:
   // Constructs a KeysetManager with an empty Keyset.
-  KeysetManager() {}
+  KeysetManager() = default;
 
   // Creates a new KeysetManager that contains a Keyset with a single key
   // generated freshly according the specification in 'key_template'.
diff --git a/cc/keyset_reader.h b/cc/keyset_reader.h
index 037697b..02e2659 100644
--- a/cc/keyset_reader.h
+++ b/cc/keyset_reader.h
@@ -17,6 +17,8 @@
 #ifndef TINK_KEYSET_READER_H_
 #define TINK_KEYSET_READER_H_
 
+#include <memory>
+
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
 
@@ -37,7 +39,7 @@
     std::unique_ptr<google::crypto::tink::EncryptedKeyset>>
   ReadEncrypted() = 0;
 
-  virtual ~KeysetReader() {}
+  virtual ~KeysetReader() = default;
 };
 
 }  // namespace tink
diff --git a/cc/keyset_writer.h b/cc/keyset_writer.h
index 3c30bd1..96df7db 100644
--- a/cc/keyset_writer.h
+++ b/cc/keyset_writer.h
@@ -35,7 +35,7 @@
   virtual crypto::tink::util::Status
       Write(const google::crypto::tink::EncryptedKeyset& encrypted_keyset) = 0;
 
-  virtual ~KeysetWriter() {}
+  virtual ~KeysetWriter() = default;
 };
 
 }  // namespace tink
diff --git a/cc/kms_client.h b/cc/kms_client.h
index dc7a59d..6753804 100644
--- a/cc/kms_client.h
+++ b/cc/kms_client.h
@@ -38,7 +38,7 @@
   virtual crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
   GetAead(absl::string_view key_uri) const = 0;
 
-  virtual ~KmsClient() {}
+  virtual ~KmsClient() = default;
 };
 
 }  // namespace tink
diff --git a/cc/kms_clients.h b/cc/kms_clients.h
index 3b2ac13..772360b 100644
--- a/cc/kms_clients.h
+++ b/cc/kms_clients.h
@@ -17,6 +17,7 @@
 #ifndef TINK_KMS_CLIENTS_H_
 #define TINK_KMS_CLIENTS_H_
 
+#include <memory>
 #include <utility>
 #include <vector>
 
diff --git a/cc/mac.h b/cc/mac.h
index 6bc02b8..a50adc9 100644
--- a/cc/mac.h
+++ b/cc/mac.h
@@ -42,7 +42,7 @@
       absl::string_view mac_value,
       absl::string_view data) const = 0;
 
-  virtual ~Mac() {}
+  virtual ~Mac() = default;
 };
 
 }  // namespace tink
diff --git a/cc/mac/BUILD.bazel b/cc/mac/BUILD.bazel
index b681672..77f5534 100644
--- a/cc/mac/BUILD.bazel
+++ b/cc/mac/BUILD.bazel
@@ -32,7 +32,9 @@
     visibility = ["//visibility:public"],
     deps = [
         ":aes_cmac_key_manager",
+        ":aes_cmac_proto_serialization",
         ":hmac_key_manager",
+        ":hmac_proto_serialization",
         ":mac_wrapper",
         "//:registry",
         "//config:config_util",
@@ -172,8 +174,121 @@
     deps = [
         ":mac_parameters",
         "//:crypto_format",
+        "//internal:util",
         "//util:status",
         "//util:statusor",
+        "@com_google_absl//absl/log",
+    ],
+)
+
+cc_library(
+    name = "aes_cmac_key",
+    srcs = ["aes_cmac_key.cc"],
+    hdrs = ["aes_cmac_key.h"],
+    include_prefix = "tink/mac",
+    deps = [
+        ":aes_cmac_parameters",
+        ":mac_key",
+        "//:partial_key_access_token",
+        "//:restricted_data",
+        "//subtle:subtle_util",
+        "//util:status",
+        "//util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_library(
+    name = "aes_cmac_proto_serialization",
+    srcs = ["aes_cmac_proto_serialization.cc"],
+    hdrs = ["aes_cmac_proto_serialization.h"],
+    include_prefix = "tink/mac",
+    deps = [
+        ":aes_cmac_key",
+        ":aes_cmac_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//internal:key_parser",
+        "//internal:key_serializer",
+        "//internal:mutable_serialization_registry",
+        "//internal:parameters_parser",
+        "//internal:parameters_serializer",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:aes_cmac_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_library(
+    name = "hmac_parameters",
+    srcs = ["hmac_parameters.cc"],
+    hdrs = ["hmac_parameters.h"],
+    include_prefix = "tink/mac",
+    deps = [
+        ":mac_parameters",
+        "//:crypto_format",
+        "//internal:util",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/log",
+    ],
+)
+
+cc_library(
+    name = "hmac_key",
+    srcs = ["hmac_key.cc"],
+    hdrs = ["hmac_key.h"],
+    include_prefix = "tink/mac",
+    deps = [
+        ":hmac_parameters",
+        ":mac_key",
+        "//:partial_key_access_token",
+        "//:restricted_data",
+        "//subtle:subtle_util",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/types:optional",
+    ],
+)
+
+cc_library(
+    name = "hmac_proto_serialization",
+    srcs = ["hmac_proto_serialization.cc"],
+    hdrs = ["hmac_proto_serialization.h"],
+    include_prefix = "tink/mac",
+    deps = [
+        ":hmac_key",
+        ":hmac_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//:secret_key_access_token",
+        "//internal:key_parser",
+        "//internal:key_serializer",
+        "//internal:mutable_serialization_registry",
+        "//internal:parameters_parser",
+        "//internal:parameters_serializer",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:common_cc_proto",
+        "//proto:hmac_cc_proto",
+        "//proto:tink_cc_proto",
+        "//util:status",
+        "//util:statusor",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/types:optional",
     ],
 )
 
@@ -207,16 +322,26 @@
     srcs = ["mac_config_test.cc"],
     tags = ["fips"],
     deps = [
+        ":aes_cmac_key",
         ":aes_cmac_key_manager",
+        ":aes_cmac_parameters",
+        ":hmac_key",
         ":hmac_key_manager",
+        ":hmac_parameters",
         ":mac_config",
         ":mac_key_templates",
         "//:chunked_mac",
-        "//:config",
+        "//:insecure_secret_key_access",
         "//:keyset_handle",
         "//:mac",
+        "//:partial_key_access",
         "//:registry",
-        "//config:tink_fips",
+        "//internal:fips_utils",
+        "//internal:mutable_serialization_registry",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:common_cc_proto",
+        "//proto:tink_cc_proto",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
@@ -324,3 +449,90 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "aes_cmac_key_test",
+    size = "small",
+    srcs = ["aes_cmac_key_test.cc"],
+    deps = [
+        ":aes_cmac_key",
+        ":aes_cmac_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "aes_cmac_proto_serialization_test",
+    size = "small",
+    srcs = ["aes_cmac_proto_serialization_test.cc"],
+    deps = [
+        ":aes_cmac_key",
+        ":aes_cmac_parameters",
+        ":aes_cmac_proto_serialization",
+        "//:insecure_secret_key_access",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//internal:mutable_serialization_registry",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:aes_cmac_cc_proto",
+        "//proto:tink_cc_proto",
+        "//subtle:random",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "hmac_parameters_test",
+    size = "small",
+    srcs = ["hmac_parameters_test.cc"],
+    deps = [
+        ":hmac_parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "hmac_key_test",
+    srcs = ["hmac_key_test.cc"],
+    deps = [
+        ":hmac_key",
+        ":hmac_parameters",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "hmac_proto_serialization_test",
+    srcs = ["hmac_proto_serialization_test.cc"],
+    deps = [
+        ":hmac_key",
+        ":hmac_parameters",
+        ":hmac_proto_serialization",
+        "//:insecure_secret_key_access",
+        "//:partial_key_access",
+        "//:restricted_data",
+        "//internal:mutable_serialization_registry",
+        "//internal:proto_key_serialization",
+        "//internal:proto_parameters_serialization",
+        "//proto:common_cc_proto",
+        "//proto:hmac_cc_proto",
+        "//proto:tink_cc_proto",
+        "//subtle:random",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/mac/BUILD.gn b/cc/mac/BUILD.gn
index 6129f46..601f2fc 100644
--- a/cc/mac/BUILD.gn
+++ b/cc/mac/BUILD.gn
@@ -41,7 +41,9 @@
   ]
   public_deps = [
     ":aes_cmac_key_manager",
+    ":aes_cmac_proto_serialization",
     ":hmac_key_manager",
+    ":hmac_proto_serialization",
     ":mac_wrapper",
     "//third_party/abseil-cpp/absl/base:core_headers",
     "//third_party/abseil-cpp/absl/memory:memory",
@@ -117,3 +119,175 @@
   ]
   public_configs = [ "//third_party/tink:tink_config" ]
 }
+
+# CC Library : mac_parameters
+source_set("mac_parameters") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "mac_parameters.h" ]
+  public_deps = [ "//third_party/tink/cc:parameters" ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : mac_key
+source_set("mac_key") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [ "mac_key.h" ]
+  public_deps = [
+    ":mac_parameters",
+    "//third_party/tink/cc:key",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : aes_cmac_parameters
+source_set("aes_cmac_parameters") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "aes_cmac_parameters.cc",
+    "aes_cmac_parameters.h",
+  ]
+  public_deps = [
+    ":mac_parameters",
+    "//third_party/abseil-cpp/absl/log:log",
+    "//third_party/tink/cc:crypto_format",
+    "//third_party/tink/cc/internal:util",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : aes_cmac_key
+source_set("aes_cmac_key") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "aes_cmac_key.cc",
+    "aes_cmac_key.h",
+  ]
+  public_deps = [
+    ":aes_cmac_parameters",
+    ":mac_key",
+    "//third_party/abseil-cpp/absl/base:core_headers",
+    "//third_party/abseil-cpp/absl/strings:str_format",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/boringssl:crypto",
+    "//third_party/tink/cc:partial_key_access_token",
+    "//third_party/tink/cc:restricted_data",
+    "//third_party/tink/cc/subtle:subtle_util",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : aes_cmac_proto_serialization
+source_set("aes_cmac_proto_serialization") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "aes_cmac_proto_serialization.cc",
+    "aes_cmac_proto_serialization.h",
+  ]
+  public_deps = [
+    ":aes_cmac_key",
+    ":aes_cmac_parameters",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:partial_key_access",
+    "//third_party/tink/cc:restricted_data",
+    "//third_party/tink/cc:secret_key_access_token",
+    "//third_party/tink/cc/internal:key_parser",
+    "//third_party/tink/cc/internal:key_serializer",
+    "//third_party/tink/cc/internal:mutable_serialization_registry",
+    "//third_party/tink/cc/internal:parameters_parser",
+    "//third_party/tink/cc/internal:parameters_serializer",
+    "//third_party/tink/cc/internal:proto_key_serialization",
+    "//third_party/tink/cc/internal:proto_parameters_serialization",
+    "//third_party/tink/cc/proto:aes_cmac_proto",
+    "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : hmac_parameters
+source_set("hmac_parameters") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "hmac_parameters.cc",
+    "hmac_parameters.h",
+  ]
+  public_deps = [
+    ":mac_parameters",
+    "//third_party/abseil-cpp/absl/log:log",
+    "//third_party/tink/cc:crypto_format",
+    "//third_party/tink/cc/internal:util",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : hmac_key
+source_set("hmac_key") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "hmac_key.cc",
+    "hmac_key.h",
+  ]
+  public_deps = [
+    ":hmac_parameters",
+    ":mac_key",
+    "//third_party/abseil-cpp/absl/base:core_headers",
+    "//third_party/abseil-cpp/absl/strings:str_format",
+    "//third_party/abseil-cpp/absl/strings:strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:partial_key_access_token",
+    "//third_party/tink/cc:restricted_data",
+    "//third_party/tink/cc/subtle:subtle_util",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
+
+# CC Library : hmac_proto_serialization
+source_set("hmac_proto_serialization") {
+  configs += [ "//build/config:no_rtti" ]
+  configs -= [ "//build/config:no_rtti" ]
+  sources = [
+    "hmac_proto_serialization.cc",
+    "hmac_proto_serialization.h",
+  ]
+  public_deps = [
+    ":hmac_key",
+    ":hmac_parameters",
+    "//third_party/abseil-cpp/absl/status:status",
+    "//third_party/abseil-cpp/absl/types:optional",
+    "//third_party/tink/cc:partial_key_access",
+    "//third_party/tink/cc:restricted_data",
+    "//third_party/tink/cc:secret_key_access_token",
+    "//third_party/tink/cc/internal:key_parser",
+    "//third_party/tink/cc/internal:key_serializer",
+    "//third_party/tink/cc/internal:mutable_serialization_registry",
+    "//third_party/tink/cc/internal:parameters_parser",
+    "//third_party/tink/cc/internal:parameters_serializer",
+    "//third_party/tink/cc/internal:proto_key_serialization",
+    "//third_party/tink/cc/internal:proto_parameters_serialization",
+    "//third_party/tink/cc/proto:common_proto",
+    "//third_party/tink/cc/proto:hmac_proto",
+    "//third_party/tink/cc/proto:tink_proto",
+    "//third_party/tink/cc/util:status",
+    "//third_party/tink/cc/util:statusor",
+  ]
+  public_configs = [ "//third_party/tink:tink_config" ]
+}
diff --git a/cc/mac/CMakeLists.txt b/cc/mac/CMakeLists.txt
index ab763f1..95e5335 100644
--- a/cc/mac/CMakeLists.txt
+++ b/cc/mac/CMakeLists.txt
@@ -30,7 +30,9 @@
     mac_config.h
   DEPS
     tink::mac::aes_cmac_key_manager
+    tink::mac::aes_cmac_proto_serialization
     tink::mac::hmac_key_manager
+    tink::mac::hmac_proto_serialization
     tink::mac::mac_wrapper
     absl::core_headers
     absl::memory
@@ -151,6 +153,7 @@
   DEPS
     absl::strings
     tink::core::mac
+  TESTONLY
 )
 
 tink_cc_library(
@@ -160,11 +163,119 @@
     aes_cmac_parameters.h
   DEPS
     tink::mac::mac_parameters
+    absl::log
     tink::core::crypto_format
+    tink::internal::util
     tink::util::status
     tink::util::statusor
 )
 
+tink_cc_library(
+  NAME aes_cmac_key
+  SRCS
+    aes_cmac_key.cc
+    aes_cmac_key.h
+  DEPS
+    tink::mac::aes_cmac_parameters
+    tink::mac::mac_key
+    absl::core_headers
+    absl::strings
+    absl::str_format
+    absl::optional
+    crypto
+    tink::core::partial_key_access_token
+    tink::core::restricted_data
+    tink::subtle::subtle_util
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_library(
+  NAME aes_cmac_proto_serialization
+  SRCS
+    aes_cmac_proto_serialization.cc
+    aes_cmac_proto_serialization.h
+  DEPS
+    tink::mac::aes_cmac_key
+    tink::mac::aes_cmac_parameters
+    absl::status
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::mutable_serialization_registry
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::util::status
+    tink::util::statusor
+    tink::proto::aes_cmac_cc_proto
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_library(
+  NAME hmac_parameters
+  SRCS
+    hmac_parameters.cc
+    hmac_parameters.h
+  DEPS
+    tink::mac::mac_parameters
+    absl::log
+    tink::core::crypto_format
+    tink::internal::util
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_library(
+  NAME hmac_key
+  SRCS
+    hmac_key.cc
+    hmac_key.h
+  DEPS
+    tink::mac::hmac_parameters
+    tink::mac::mac_key
+    absl::core_headers
+    absl::strings
+    absl::str_format
+    absl::optional
+    tink::core::partial_key_access_token
+    tink::core::restricted_data
+    tink::subtle::subtle_util
+    tink::util::status
+    tink::util::statusor
+)
+
+tink_cc_library(
+  NAME hmac_proto_serialization
+  SRCS
+    hmac_proto_serialization.cc
+    hmac_proto_serialization.h
+  DEPS
+    tink::mac::hmac_key
+    tink::mac::hmac_parameters
+    absl::status
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::core::secret_key_access_token
+    tink::internal::key_parser
+    tink::internal::key_serializer
+    tink::internal::mutable_serialization_registry
+    tink::internal::parameters_parser
+    tink::internal::parameters_serializer
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::util::status
+    tink::util::statusor
+    tink::proto::common_cc_proto
+    tink::proto::hmac_cc_proto
+    tink::proto::tink_cc_proto
+)
+
 # tests
 
 tink_cc_test(
@@ -193,22 +304,32 @@
   SRCS
     mac_config_test.cc
   DEPS
+    tink::mac::aes_cmac_key
     tink::mac::aes_cmac_key_manager
+    tink::mac::aes_cmac_parameters
+    tink::mac::hmac_key
     tink::mac::hmac_key_manager
+    tink::mac::hmac_parameters
     tink::mac::mac_config
     tink::mac::mac_key_templates
     gmock
     absl::status
     crypto
     tink::core::chunked_mac
-    tink::core::config
+    tink::core::insecure_secret_key_access
     tink::core::keyset_handle
     tink::core::mac
+    tink::core::partial_key_access
     tink::core::registry
-    tink::config::tink_fips
+    tink::internal::fips_utils
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
     tink::util::status
     tink::util::test_matchers
     tink::util::test_util
+    tink::proto::common_cc_proto
+    tink::proto::tink_cc_proto
 )
 
 tink_cc_test(
@@ -304,3 +425,87 @@
     tink::util::statusor
     tink::util::test_matchers
 )
+
+tink_cc_test(
+  NAME aes_cmac_key_test
+  SRCS
+    aes_cmac_key_test.cc
+  DEPS
+    tink::mac::aes_cmac_key
+    tink::mac::aes_cmac_parameters
+    gmock
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_test(
+  NAME aes_cmac_proto_serialization_test
+  SRCS
+    aes_cmac_proto_serialization_test.cc
+  DEPS
+    tink::mac::aes_cmac_key
+    tink::mac::aes_cmac_parameters
+    tink::mac::aes_cmac_proto_serialization
+    gmock
+    tink::core::insecure_secret_key_access
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::subtle::random
+    tink::util::test_matchers
+    tink::proto::aes_cmac_cc_proto
+    tink::proto::tink_cc_proto
+)
+
+tink_cc_test(
+  NAME hmac_parameters_test
+  SRCS
+    hmac_parameters_test.cc
+  DEPS
+    tink::mac::hmac_parameters
+    gmock
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_test(
+  NAME hmac_key_test
+  SRCS
+    hmac_key_test.cc
+  DEPS
+    tink::mac::hmac_key
+    tink::mac::hmac_parameters
+    gmock
+    absl::optional
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::util::statusor
+    tink::util::test_matchers
+)
+
+tink_cc_test(
+  NAME hmac_proto_serialization_test
+  SRCS
+    hmac_proto_serialization_test.cc
+  DEPS
+    tink::mac::hmac_key
+    tink::mac::hmac_parameters
+    tink::mac::hmac_proto_serialization
+    gmock
+    tink::core::insecure_secret_key_access
+    tink::core::partial_key_access
+    tink::core::restricted_data
+    tink::internal::mutable_serialization_registry
+    tink::internal::proto_key_serialization
+    tink::internal::proto_parameters_serialization
+    tink::subtle::random
+    tink::util::test_matchers
+    tink::proto::common_cc_proto
+    tink::proto::hmac_cc_proto
+    tink::proto::tink_cc_proto
+)
diff --git a/cc/mac/aes_cmac_key.cc b/cc/mac/aes_cmac_key.cc
new file mode 100644
index 0000000..cd16cac
--- /dev/null
+++ b/cc/mac/aes_cmac_key.cc
@@ -0,0 +1,108 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/aes_cmac_key.h"
+
+#include <memory>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_format.h"
+#include "absl/types/optional.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/subtle_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+util::StatusOr<AesCmacKey> AesCmacKey::Create(
+    const AesCmacParameters& parameters, const RestrictedData& key_bytes,
+    absl::optional<int> id_requirement, PartialKeyAccessToken token) {
+  if (parameters.KeySizeInBytes() != key_bytes.size()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Key size does not match AES-CMAC parameters");
+  }
+  if (parameters.HasIdRequirement() && !id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key without ID requirement with parameters with ID "
+        "requirement");
+  }
+  if (!parameters.HasIdRequirement() && id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key with ID requirement with parameters without ID "
+        "requirement");
+  }
+  util::StatusOr<std::string> output_prefix =
+      ComputeOutputPrefix(parameters, id_requirement);
+  if (!output_prefix.ok()) {
+    return output_prefix.status();
+  }
+  return AesCmacKey(parameters, key_bytes, id_requirement,
+                    *std::move(output_prefix));
+}
+
+util::StatusOr<std::string> AesCmacKey::ComputeOutputPrefix(
+    const AesCmacParameters& parameters, absl::optional<int> id_requirement) {
+  switch (parameters.GetVariant()) {
+    case AesCmacParameters::Variant::kNoPrefix:
+      return std::string("");  // Empty prefix.
+    case AesCmacParameters::Variant::kLegacy:
+      ABSL_FALLTHROUGH_INTENDED;
+    case AesCmacParameters::Variant::kCrunchy:
+      if (!id_requirement.has_value()) {
+        return util::Status(
+            absl::StatusCode::kInvalidArgument,
+            "id requirement must have value with kCrunchy or kLegacy");
+      }
+      return absl::StrCat(absl::HexStringToBytes("00"),
+                          subtle::BigEndian32(*id_requirement));
+    case AesCmacParameters::Variant::kTink:
+      if (!id_requirement.has_value()) {
+        return util::Status(absl::StatusCode::kInvalidArgument,
+                            "id requirement must have value with kTink");
+      }
+      return absl::StrCat(absl::HexStringToBytes("01"),
+                          subtle::BigEndian32(*id_requirement));
+    default:
+      return util::Status(
+          absl::StatusCode::kInvalidArgument,
+          absl::StrCat("Invalid variant: ", parameters.GetVariant()));
+  }
+}
+
+bool AesCmacKey::operator==(const Key& other) const {
+  const AesCmacKey* that = dynamic_cast<const AesCmacKey*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (GetParameters() != that->GetParameters()) {
+    return false;
+  }
+  if (id_requirement_ != that->id_requirement_) {
+    return false;
+  }
+  return key_bytes_ == that->key_bytes_;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/aes_cmac_key.h b/cc/mac/aes_cmac_key.h
new file mode 100644
index 0000000..c29500b
--- /dev/null
+++ b/cc/mac/aes_cmac_key.h
@@ -0,0 +1,88 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_MAC_AES_CMAC_KEY_H_
+#define TINK_MAC_AES_CMAC_KEY_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/types/optional.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/mac/mac_key.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+class AesCmacKey : public MacKey {
+ public:
+  // Copyable and movable.
+  AesCmacKey(const AesCmacKey& other) = default;
+  AesCmacKey& operator=(const AesCmacKey& other) = default;
+  AesCmacKey(AesCmacKey&& other) = default;
+  AesCmacKey& operator=(AesCmacKey&& other) = default;
+
+  // Creates a new AES-CMAC key.  If the parameters specify a variant that uses
+  // a prefix, then the id is used to compute this prefix.
+  static util::StatusOr<AesCmacKey> Create(const AesCmacParameters& parameters,
+                                           const RestrictedData& key_bytes,
+                                           absl::optional<int> id_requirement,
+                                           PartialKeyAccessToken token);
+
+  // Returns the underlying AES key.
+  util::StatusOr<RestrictedData> GetKeyBytes(
+      PartialKeyAccessToken token) const {
+    return key_bytes_;
+  }
+
+  absl::string_view GetOutputPrefix() const override { return output_prefix_; }
+
+  const AesCmacParameters& GetParameters() const override {
+    return parameters_;
+  }
+
+  absl::optional<int> GetIdRequirement() const override {
+    return id_requirement_;
+  }
+
+  bool operator==(const Key& other) const override;
+
+ private:
+  AesCmacKey(const AesCmacParameters& parameters,
+             const RestrictedData& key_bytes,
+             absl::optional<int> id_requirement, std::string output_prefix)
+      : parameters_(parameters),
+        key_bytes_(key_bytes),
+        id_requirement_(id_requirement),
+        output_prefix_(std::move(output_prefix)) {}
+
+  static util::StatusOr<std::string> ComputeOutputPrefix(
+      const AesCmacParameters& parameters, absl::optional<int> id_requirement);
+
+  AesCmacParameters parameters_;
+  RestrictedData key_bytes_;
+  absl::optional<int> id_requirement_;
+  std::string output_prefix_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_MAC_AES_CMAC_KEY_H_
diff --git a/cc/mac/aes_cmac_key_manager.h b/cc/mac/aes_cmac_key_manager.h
index 8617b46..7ee5a96 100644
--- a/cc/mac/aes_cmac_key_manager.h
+++ b/cc/mac/aes_cmac_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_MAC_AES_CMAC_KEY_MANAGER_H_
 #define TINK_MAC_AES_CMAC_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/mac/aes_cmac_key_test.cc b/cc/mac/aes_cmac_key_test.cc
new file mode 100644
index 0000000..8bb87cf
--- /dev/null
+++ b/cc/mac/aes_cmac_key_test.cc
@@ -0,0 +1,253 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/aes_cmac_key.h"
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  AesCmacParameters::Variant variant;
+  absl::optional<int> id_requirement;
+  std::string output_prefix;
+};
+
+using AesCmacKeyTest = TestWithParam<std::tuple<int, int, TestCase>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    AesCmacKeyTestSuite, AesCmacKeyTest,
+    Combine(Values(16, 32), Range(10, 16),
+            Values(TestCase{AesCmacParameters::Variant::kTink, 0x02030400,
+                            std::string("\x01\x02\x03\x04\x00", 5)},
+                   TestCase{AesCmacParameters::Variant::kCrunchy, 0x01030005,
+                            std::string("\x00\x01\x03\x00\x05", 5)},
+                   TestCase{AesCmacParameters::Variant::kLegacy, 0x01020304,
+                            std::string("\x00\x01\x02\x03\x04", 5)},
+                   TestCase{AesCmacParameters::Variant::kNoPrefix,
+                            absl::nullopt, ""})));
+
+TEST_P(AesCmacKeyTest, CreateSucceeds) {
+  int key_size;
+  int cryptographic_tag_size;
+  TestCase test_case;
+  std::tie(key_size, cryptographic_tag_size, test_case) = GetParam();
+
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      key_size, cryptographic_tag_size, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  EXPECT_THAT(key->GetParameters(), Eq(*params));
+  EXPECT_THAT(key->GetIdRequirement(), Eq(test_case.id_requirement));
+  EXPECT_THAT(key->GetOutputPrefix(), Eq(test_case.output_prefix));
+}
+
+TEST(AesCmacKeyTest, CreateKeyWithMismatchedKeySizeFails) {
+  // Key size parameter is 32 bytes.
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  // Key material is 16 bytes (another valid key length).
+  RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/16);
+
+  EXPECT_THAT(AesCmacKey::Create(*params, mismatched_secret,
+                                 /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesCmacKeyTest, CreateKeyWithWrongIdRequirementFails) {
+  util::StatusOr<AesCmacParameters> no_prefix_params =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/16,
+                                AesCmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(no_prefix_params, IsOk());
+
+  util::StatusOr<AesCmacParameters> tink_params =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/16,
+                                AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  EXPECT_THAT(AesCmacKey::Create(*no_prefix_params, secret,
+                                 /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacKey::Create(*tink_params, secret,
+                                 /*id_requirement=*/absl::nullopt,
+                                 GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesCmacKeyTest, GetAesCmacKey) {
+  int key_size;
+  int cryptographic_tag_size;
+  TestCase test_case;
+  std::tie(key_size, cryptographic_tag_size, test_case) = GetParam();
+
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      key_size, cryptographic_tag_size, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), IsOkAndHolds(secret));
+}
+
+TEST_P(AesCmacKeyTest, KeyEquals) {
+  int key_size;
+  int cryptographic_tag_size;
+  TestCase test_case;
+  std::tie(key_size, cryptographic_tag_size, test_case) = GetParam();
+
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      key_size, cryptographic_tag_size, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<AesCmacKey> other_key = AesCmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key == *other_key);
+  EXPECT_TRUE(*other_key == *key);
+  EXPECT_FALSE(*key != *other_key);
+  EXPECT_FALSE(*other_key != *key);
+}
+
+TEST(AesCmacKeyTest, DifferentFormatNotEqual) {
+  util::StatusOr<AesCmacParameters> legacy_params =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/16,
+                                AesCmacParameters::Variant::kLegacy);
+  ASSERT_THAT(legacy_params, IsOk());
+
+  util::StatusOr<AesCmacParameters> tink_params =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/16,
+                                AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<AesCmacKey> key =
+      AesCmacKey::Create(*legacy_params, secret, /*id_requirement=*/0x01020304,
+                         GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<AesCmacKey> other_key =
+      AesCmacKey::Create(*tink_params, secret, /*id_requirement=*/0x01020304,
+                         GetPartialKeyAccess());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesCmacKeyTest, DifferentSecretDataNotEqual) {
+  util::StatusOr<AesCmacParameters> params =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/16,
+                                AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret1 = RestrictedData(/*num_random_bytes=*/32);
+  RestrictedData secret2 = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *params, secret1, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<AesCmacKey> other_key = AesCmacKey::Create(
+      *params, secret2, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(AesCmacKeyTest, DifferentIdRequirementNotEqual) {
+  util::StatusOr<AesCmacParameters> params =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/16,
+                                AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *params, secret, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<AesCmacKey> other_key = AesCmacKey::Create(
+      *params, secret, /*id_requirement=*/0x02030405, GetPartialKeyAccess());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/aes_cmac_parameters.cc b/cc/mac/aes_cmac_parameters.cc
index c4d8429..4064d2f 100644
--- a/cc/mac/aes_cmac_parameters.cc
+++ b/cc/mac/aes_cmac_parameters.cc
@@ -22,7 +22,10 @@
 #include <ostream>
 #include <set>
 
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
 #include "tink/crypto_format.h"
+#include "tink/internal/util.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -30,7 +33,14 @@
 namespace tink {
 
 util::StatusOr<AesCmacParameters> AesCmacParameters::Create(
-    int cryptographic_tag_size_in_bytes, Variant variant) {
+    int key_size_in_bytes, int cryptographic_tag_size_in_bytes,
+    Variant variant) {
+  if (key_size_in_bytes != 16 && key_size_in_bytes != 32) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Key size should be either 16 or 32 bytes, got ",
+                     key_size_in_bytes, " bytes."));
+  }
   if (cryptographic_tag_size_in_bytes < 10) {
     return util::Status(
         absl::StatusCode::kInvalidArgument,
@@ -51,7 +61,8 @@
         absl::StatusCode::kInvalidArgument,
         "Cannot create AES-CMAC parameters with unknown variant.");
   }
-  return AesCmacParameters(cryptographic_tag_size_in_bytes, variant);
+  return AesCmacParameters(key_size_in_bytes, cryptographic_tag_size_in_bytes,
+                           variant);
 }
 
 int AesCmacParameters::TotalTagSizeInBytes() const {
@@ -64,8 +75,7 @@
       return CryptographicTagSizeInBytes();
     default:
       // Parameters objects with unknown variants should never be created.
-      std::cerr << "AES-CMAC parameters has an unknown variant." << std::endl;
-      std::exit(1);
+      internal::LogFatal("AES-CMAC parameters has an unknown variant.");
   }
 }
 
@@ -75,9 +85,17 @@
   if (that == nullptr) {
     return false;
   }
-  return cryptographic_tag_size_in_bytes_ ==
-             that->cryptographic_tag_size_in_bytes_ &&
-         variant_ == that->variant_;
+  if (key_size_in_bytes_ != that->key_size_in_bytes_) {
+    return false;
+  }
+  if (cryptographic_tag_size_in_bytes_ !=
+      that->cryptographic_tag_size_in_bytes_) {
+    return false;
+  }
+  if (variant_ != that->variant_) {
+    return false;
+  }
+  return true;
 }
 
 }  // namespace tink
diff --git a/cc/mac/aes_cmac_parameters.h b/cc/mac/aes_cmac_parameters.h
index a4a09a7..06a560a 100644
--- a/cc/mac/aes_cmac_parameters.h
+++ b/cc/mac/aes_cmac_parameters.h
@@ -53,14 +53,18 @@
   AesCmacParameters(AesCmacParameters&& other) = default;
   AesCmacParameters& operator=(AesCmacParameters&& other) = default;
 
-  // Creates a new AES-CMAC parameters object. Returns an error status if
-  // `cryptographic_tag_size_in_bytes` falls outside [10,...,16].  Otherwise,
-  // returns the parameters object.
+  // Creates a new AES-CMAC parameters object unless an error occurs. An error
+  // occurs under one of the following conditions:
+  // 1. `key_size_in_bytes` is a value other than 16 or 32
+  // 2. `cryptographic_tag_size_in_bytes` falls outside [10,...,16]
   static util::StatusOr<AesCmacParameters> Create(
-      int cryptographic_tag_size_in_bytes, Variant variant);
+      int key_size_in_bytes, int cryptographic_tag_size_in_bytes,
+      Variant variant);
 
   Variant GetVariant() const { return variant_; }
 
+  int KeySizeInBytes() const { return key_size_in_bytes_; }
+
   // Returns the size of the tag, which is computed cryptographically from the
   // message. Note that this may differ from the total size of the tag, as for
   // some keys, Tink prefixes the tag with a key dependent output prefix.
@@ -79,10 +83,13 @@
   bool operator==(const Parameters& other) const override;
 
  private:
-  AesCmacParameters(int cryptographic_tag_size_in_bytes, Variant variant)
-      : cryptographic_tag_size_in_bytes_(cryptographic_tag_size_in_bytes),
+  AesCmacParameters(int key_size_in_bytes, int cryptographic_tag_size_in_bytes,
+                    Variant variant)
+      : key_size_in_bytes_(key_size_in_bytes),
+        cryptographic_tag_size_in_bytes_(cryptographic_tag_size_in_bytes),
         variant_(variant) {}
 
+  int key_size_in_bytes_;
   int cryptographic_tag_size_in_bytes_;
   Variant variant_;
 };
diff --git a/cc/mac/aes_cmac_parameters_test.cc b/cc/mac/aes_cmac_parameters_test.cc
index db1f2c3..7284b7c 100644
--- a/cc/mac/aes_cmac_parameters_test.cc
+++ b/cc/mac/aes_cmac_parameters_test.cc
@@ -38,38 +38,48 @@
 
 struct CreateTestCase {
   AesCmacParameters::Variant variant;
-  int cryptographic_tag_size_in_bytes;
-  int total_tag_size_in_bytes;
+  int key_size;
+  int cryptographic_tag_size;
+  int total_tag_size;
   bool has_id_requirement;
 };
 
-class AesCmacParametersCreateTest : public TestWithParam<CreateTestCase> {};
+using AesCmacParametersCreateTest = TestWithParam<CreateTestCase>;
 
 INSTANTIATE_TEST_SUITE_P(
     AesCmacParametersCreateTestSuite, AesCmacParametersCreateTest,
-    Values(CreateTestCase{AesCmacParameters::Variant::kTink, 10, 15, true},
-           CreateTestCase{AesCmacParameters::Variant::kCrunchy, 12, 17, true},
-           CreateTestCase{AesCmacParameters::Variant::kLegacy, 14, 19, true},
-           CreateTestCase{AesCmacParameters::Variant::kNoPrefix, 16, 16,
-                          false}));
+    Values(CreateTestCase{AesCmacParameters::Variant::kTink, /*key_size=*/16,
+                          /*cryptographic_tag_size=*/10, /*total_tag_size=*/15,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{AesCmacParameters::Variant::kCrunchy, /*key_size=*/16,
+                          /*cryptographic_tag_size=*/12, /*total_tag_size=*/17,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{AesCmacParameters::Variant::kLegacy, /*key_size=*/32,
+                          /*cryptographic_tag_size=*/14, /*total_tag_size=*/19,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{AesCmacParameters::Variant::kNoPrefix,
+                          /*key_size=*/32, /*cryptographic_tag_size=*/16,
+                          /*total_tag_size=*/16,
+                          /*has_id_requirement=*/false}));
 
 TEST_P(AesCmacParametersCreateTest, Create) {
   CreateTestCase test_case = GetParam();
 
   util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
-      test_case.cryptographic_tag_size_in_bytes, test_case.variant);
+      test_case.key_size, test_case.cryptographic_tag_size, test_case.variant);
   ASSERT_THAT(parameters, IsOk());
 
   EXPECT_THAT(parameters->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(parameters->KeySizeInBytes(), Eq(test_case.key_size));
   EXPECT_THAT(parameters->CryptographicTagSizeInBytes(),
-              Eq(test_case.cryptographic_tag_size_in_bytes));
-  EXPECT_THAT(parameters->TotalTagSizeInBytes(),
-              Eq(test_case.total_tag_size_in_bytes));
+              Eq(test_case.cryptographic_tag_size));
+  EXPECT_THAT(parameters->TotalTagSizeInBytes(), Eq(test_case.total_tag_size));
   EXPECT_THAT(parameters->HasIdRequirement(), Eq(test_case.has_id_requirement));
 }
 
 TEST(AesCmacParametersTest, CreateWithInvalidVariantFails) {
   EXPECT_THAT(AesCmacParameters::Create(
+                  /*key_size_in_bytes=*/32,
                   /*cryptographic_tag_size_in_bytes=*/12,
                   AesCmacParameters::Variant::
                       kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements)
@@ -77,39 +87,69 @@
               StatusIs(absl::StatusCode::kInvalidArgument));
 }
 
+TEST(AesCmacParametersTest, CreateWithInvalidKeySizeFails) {
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/15,
+                                        /*cryptographic_tag_size_in_bytes=*/16,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/17,
+                                        /*cryptographic_tag_size_in_bytes=*/16,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/31,
+                                        /*cryptographic_tag_size_in_bytes=*/16,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/33,
+                                        /*cryptographic_tag_size_in_bytes=*/16,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
 TEST(AesCmacParametersTest, CreateWithInvalidTagSizeFails) {
   // Too small.
-  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/7,
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                        /*cryptographic_tag_size_in_bytes=*/7,
                                         AesCmacParameters::Variant::kNoPrefix)
                   .status(),
               StatusIs(absl::StatusCode::kInvalidArgument));
-  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/8,
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                        /*cryptographic_tag_size_in_bytes=*/8,
                                         AesCmacParameters::Variant::kNoPrefix)
                   .status(),
               StatusIs(absl::StatusCode::kInvalidArgument));
-  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/9,
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                        /*cryptographic_tag_size_in_bytes=*/9,
                                         AesCmacParameters::Variant::kNoPrefix)
                   .status(),
               StatusIs(absl::StatusCode::kInvalidArgument));
   // Too big;
-  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/17,
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                        /*cryptographic_tag_size_in_bytes=*/17,
                                         AesCmacParameters::Variant::kNoPrefix)
                   .status(),
               StatusIs(absl::StatusCode::kInvalidArgument));
-  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/18,
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                        /*cryptographic_tag_size_in_bytes=*/18,
                                         AesCmacParameters::Variant::kNoPrefix)
                   .status(),
               StatusIs(absl::StatusCode::kInvalidArgument));
-  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/19,
+  EXPECT_THAT(AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                        /*cryptographic_tag_size_in_bytes=*/19,
                                         AesCmacParameters::Variant::kNoPrefix)
                   .status(),
               StatusIs(absl::StatusCode::kInvalidArgument));
 }
 
 TEST(AesCmacParametersTest, CopyConstructor) {
-  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
-      /*cryptographic_tag_size_in_bytes=*/12,
-      AesCmacParameters::Variant::kTink);
+  util::StatusOr<AesCmacParameters> parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/12,
+                                AesCmacParameters::Variant::kTink);
   ASSERT_THAT(parameters, IsOk());
 
   AesCmacParameters copy(*parameters);
@@ -122,9 +162,10 @@
 }
 
 TEST(AesCmacParametersTest, CopyAssignment) {
-  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
-      /*cryptographic_tag_size_in_bytes=*/12,
-      AesCmacParameters::Variant::kTink);
+  util::StatusOr<AesCmacParameters> parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/12,
+                                AesCmacParameters::Variant::kTink);
   ASSERT_THAT(parameters, IsOk());
 
   AesCmacParameters copy = *parameters;
@@ -136,28 +177,29 @@
   EXPECT_THAT(copy.HasIdRequirement(), Eq(parameters->HasIdRequirement()));
 }
 
-class AesCmacParametersVariantTest
-    : public TestWithParam<std::tuple<AesCmacParameters::Variant, int>> {};
+using AesCmacParametersVariantTest =
+    TestWithParam<std::tuple<int, int, AesCmacParameters::Variant>>;
 
-INSTANTIATE_TEST_SUITE_P(AesCmacParametersVariantTestSuite,
-                         AesCmacParametersVariantTest,
-                         Combine(Values(AesCmacParameters::Variant::kTink,
-                                        AesCmacParameters::Variant::kCrunchy,
-                                        AesCmacParameters::Variant::kLegacy,
-                                        AesCmacParameters::Variant::kNoPrefix),
-                                 Range(10, 16)));
+INSTANTIATE_TEST_SUITE_P(
+    AesCmacParametersVariantTestSuite, AesCmacParametersVariantTest,
+    Combine(Values(16, 32), Range(10, 16),
+            Values(AesCmacParameters::Variant::kTink,
+                   AesCmacParameters::Variant::kCrunchy,
+                   AesCmacParameters::Variant::kLegacy,
+                   AesCmacParameters::Variant::kNoPrefix)));
 
 TEST_P(AesCmacParametersVariantTest, ParametersEquals) {
-  AesCmacParameters::Variant variant;
+  int key_size;
   int cryptographic_tag_size;
-  std::tie(variant, cryptographic_tag_size) = GetParam();
+  AesCmacParameters::Variant variant;
+  std::tie(key_size, cryptographic_tag_size, variant) = GetParam();
 
   util::StatusOr<AesCmacParameters> parameters =
-      AesCmacParameters::Create(cryptographic_tag_size, variant);
+      AesCmacParameters::Create(key_size, cryptographic_tag_size, variant);
   ASSERT_THAT(parameters, IsOk());
 
   util::StatusOr<AesCmacParameters> other_parameters =
-      AesCmacParameters::Create(cryptographic_tag_size, variant);
+      AesCmacParameters::Create(key_size, cryptographic_tag_size, variant);
   ASSERT_THAT(other_parameters, IsOk());
 
   EXPECT_TRUE(*parameters == *other_parameters);
@@ -166,16 +208,34 @@
   EXPECT_FALSE(*other_parameters != *parameters);
 }
 
-TEST(AesCmacParametersTest, TagSizeNotEqual) {
-  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
-      /*cryptographic_tag_size_in_bytes=*/10,
-      AesCmacParameters::Variant::kNoPrefix);
+TEST(AesCmacParametersTest, KeySizeNotEqual) {
+  util::StatusOr<AesCmacParameters> parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/16,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kNoPrefix);
   ASSERT_THAT(parameters, IsOk());
 
   util::StatusOr<AesCmacParameters> other_parameters =
-      AesCmacParameters::Create(
-          /*cryptographic_tag_size_in_bytes=*/11,
-          AesCmacParameters::Variant::kNoPrefix);
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesCmacParametersTest, TagSizeNotEqual) {
+  util::StatusOr<AesCmacParameters> parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesCmacParameters> other_parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/11,
+                                AesCmacParameters::Variant::kNoPrefix);
   ASSERT_THAT(other_parameters, IsOk());
 
   EXPECT_TRUE(*parameters != *other_parameters);
@@ -183,15 +243,16 @@
 }
 
 TEST(AesCmacParametersTest, VariantNotEqual) {
-  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
-      /*cryptographic_tag_size_in_bytes=*/10,
-      AesCmacParameters::Variant::kNoPrefix);
+  util::StatusOr<AesCmacParameters> parameters =
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kNoPrefix);
   ASSERT_THAT(parameters, IsOk());
 
   util::StatusOr<AesCmacParameters> other_parameters =
-      AesCmacParameters::Create(
-          /*cryptographic_tag_size_in_bytes=*/10,
-          AesCmacParameters::Variant::kTink);
+      AesCmacParameters::Create(/*key_size_in_bytes=*/32,
+                                /*cryptographic_tag_size_in_bytes=*/10,
+                                AesCmacParameters::Variant::kTink);
   ASSERT_THAT(other_parameters, IsOk());
 
   EXPECT_TRUE(*parameters != *other_parameters);
diff --git a/cc/mac/aes_cmac_proto_serialization.cc b/cc/mac/aes_cmac_proto_serialization.cc
new file mode 100644
index 0000000..5c7b77b
--- /dev/null
+++ b/cc/mac/aes_cmac_proto_serialization.cc
@@ -0,0 +1,248 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/aes_cmac_proto_serialization.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/mac/aes_cmac_key.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/aes_cmac.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::google::crypto::tink::AesCmacKeyFormat;
+using ::google::crypto::tink::AesCmacParams;
+using ::google::crypto::tink::OutputPrefixType;
+
+using AesCmacProtoParametersParserImpl =
+    internal::ParametersParserImpl<internal::ProtoParametersSerialization,
+                                   AesCmacParameters>;
+using AesCmacProtoParametersSerializerImpl =
+    internal::ParametersSerializerImpl<AesCmacParameters,
+                                       internal::ProtoParametersSerialization>;
+using AesCmacProtoKeyParserImpl =
+    internal::KeyParserImpl<internal::ProtoKeySerialization, AesCmacKey>;
+using AesCmacProtoKeySerializerImpl =
+    internal::KeySerializerImpl<AesCmacKey, internal::ProtoKeySerialization>;
+
+const absl::string_view kTypeUrl =
+    "type.googleapis.com/google.crypto.tink.AesCmacKey";
+
+util::StatusOr<AesCmacParameters::Variant> ToVariant(
+    OutputPrefixType output_prefix_type) {
+  switch (output_prefix_type) {
+    case OutputPrefixType::CRUNCHY:
+      return AesCmacParameters::Variant::kCrunchy;
+    case OutputPrefixType::LEGACY:
+      return AesCmacParameters::Variant::kLegacy;
+    case OutputPrefixType::RAW:
+      return AesCmacParameters::Variant::kNoPrefix;
+    case OutputPrefixType::TINK:
+      return AesCmacParameters::Variant::kTink;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine AesCmacParameters::Variant");
+  }
+}
+
+util::StatusOr<OutputPrefixType> ToOutputPrefixType(
+    AesCmacParameters::Variant variant) {
+  switch (variant) {
+    case AesCmacParameters::Variant::kCrunchy:
+      return OutputPrefixType::CRUNCHY;
+    case AesCmacParameters::Variant::kLegacy:
+      return OutputPrefixType::LEGACY;
+    case AesCmacParameters::Variant::kNoPrefix:
+      return OutputPrefixType::RAW;
+    case AesCmacParameters::Variant::kTink:
+      return OutputPrefixType::TINK;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine output prefix type");
+  }
+}
+
+util::StatusOr<AesCmacParameters> ParseParameters(
+    const internal::ProtoParametersSerialization& serialization) {
+  if (serialization.GetKeyTemplate().type_url() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing AesCmacParameters.");
+  }
+
+  AesCmacKeyFormat proto_key_format;
+  if (!proto_key_format.ParseFromString(
+          serialization.GetKeyTemplate().value())) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse AesCmacKeyFormat proto");
+  }
+
+  util::StatusOr<AesCmacParameters::Variant> variant =
+      ToVariant(serialization.GetKeyTemplate().output_prefix_type());
+  if (!variant.ok()) return variant.status();
+
+  return AesCmacParameters::Create(proto_key_format.key_size(),
+                                   proto_key_format.params().tag_size(),
+                                   *variant);
+}
+
+util::StatusOr<internal::ProtoParametersSerialization> SerializeParameters(
+    const AesCmacParameters& parameters) {
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(parameters.GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+  AesCmacParams proto_params;
+  proto_params.set_tag_size(parameters.CryptographicTagSizeInBytes());
+  AesCmacKeyFormat proto_key_format;
+  proto_key_format.set_key_size(parameters.KeySizeInBytes());
+  *proto_key_format.mutable_params() = proto_params;
+
+  return internal::ProtoParametersSerialization::Create(
+      kTypeUrl, *output_prefix_type, proto_key_format.SerializeAsString());
+}
+
+util::StatusOr<AesCmacKey> ParseKey(
+    const internal::ProtoKeySerialization& serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  if (serialization.TypeUrl() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing AesCmacKey.");
+  }
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+  google::crypto::tink::AesCmacKey proto_key;
+  RestrictedData restricted_data = serialization.SerializedKeyProto();
+  // OSS proto library complains if input is not converted to a string.
+  if (!proto_key.ParseFromString(
+          std::string(restricted_data.GetSecret(*token)))) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse AesCmacKey proto");
+  }
+  if (proto_key.version() != 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Only version 0 keys are accepted.");
+  }
+
+  util::StatusOr<AesCmacParameters::Variant> variant =
+      ToVariant(serialization.GetOutputPrefixType());
+  if (!variant.ok()) return variant.status();
+
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      proto_key.key_value().length(), proto_key.params().tag_size(), *variant);
+  if (!parameters.ok()) return parameters.status();
+
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *parameters, RestrictedData(proto_key.key_value(), *token),
+      serialization.IdRequirement(), GetPartialKeyAccess());
+  if (!key.ok()) return key.status();
+
+  return *key;
+}
+
+util::StatusOr<internal::ProtoKeySerialization> SerializeKey(
+    const AesCmacKey& key, absl::optional<SecretKeyAccessToken> token) {
+  util::StatusOr<RestrictedData> restricted_input =
+      key.GetKeyBytes(GetPartialKeyAccess());
+  if (!restricted_input.ok()) return restricted_input.status();
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+
+  AesCmacParams proto_params;
+  proto_params.set_tag_size(key.GetParameters().CryptographicTagSizeInBytes());
+  google::crypto::tink::AesCmacKey proto_key;
+  *proto_key.mutable_params() = proto_params;
+  proto_key.set_version(0);
+  // OSS proto library complains if input is not converted to a string.
+  proto_key.set_key_value(std::string(restricted_input->GetSecret(*token)));
+
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(key.GetParameters().GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+  RestrictedData restricted_output =
+      RestrictedData(proto_key.SerializeAsString(), *token);
+  return internal::ProtoKeySerialization::Create(
+      kTypeUrl, restricted_output, google::crypto::tink::KeyData::SYMMETRIC,
+      *output_prefix_type, key.GetIdRequirement());
+}
+
+AesCmacProtoParametersParserImpl* AesCmacProtoParametersParser() {
+  static auto* parser =
+      new AesCmacProtoParametersParserImpl(kTypeUrl, ParseParameters);
+  return parser;
+}
+
+AesCmacProtoParametersSerializerImpl* AesCmacProtoParametersSerializer() {
+  static auto* serializer =
+      new AesCmacProtoParametersSerializerImpl(kTypeUrl, SerializeParameters);
+  return serializer;
+}
+
+AesCmacProtoKeyParserImpl* AesCmacProtoKeyParser() {
+  static auto* parser = new AesCmacProtoKeyParserImpl(kTypeUrl, ParseKey);
+  return parser;
+}
+
+AesCmacProtoKeySerializerImpl* AesCmacProtoKeySerializer() {
+  static auto* serializer = new AesCmacProtoKeySerializerImpl(SerializeKey);
+  return serializer;
+}
+
+}  // namespace
+
+util::Status RegisterAesCmacProtoSerialization() {
+  util::Status status =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .RegisterParametersParser(AesCmacProtoParametersParser());
+  if (!status.ok()) return status;
+
+  status =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .RegisterParametersSerializer(AesCmacProtoParametersSerializer());
+  if (!status.ok()) return status;
+
+  status = internal::MutableSerializationRegistry::GlobalInstance()
+               .RegisterKeyParser(AesCmacProtoKeyParser());
+  if (!status.ok()) return status;
+
+  return internal::MutableSerializationRegistry::GlobalInstance()
+      .RegisterKeySerializer(AesCmacProtoKeySerializer());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/aes_cmac_proto_serialization.h b/cc/mac/aes_cmac_proto_serialization.h
new file mode 100644
index 0000000..d34dcb6
--- /dev/null
+++ b/cc/mac/aes_cmac_proto_serialization.h
@@ -0,0 +1,31 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_MAC_AES_CMAC_PROTO_SERIALIZATION_H_
+#define TINK_MAC_AES_CMAC_PROTO_SERIALIZATION_H_
+
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+// Registers proto parsers and serializers for AES-CMAC parameters and keys.
+crypto::tink::util::Status RegisterAesCmacProtoSerialization();
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_MAC_AES_CMAC_PROTO_SERIALIZATION_H_
diff --git a/cc/mac/aes_cmac_proto_serialization_test.cc b/cc/mac/aes_cmac_proto_serialization_test.cc
new file mode 100644
index 0000000..cda1093
--- /dev/null
+++ b/cc/mac/aes_cmac_proto_serialization_test.cc
@@ -0,0 +1,373 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/aes_cmac_proto_serialization.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/mac/aes_cmac_key.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_matchers.h"
+#include "proto/aes_cmac.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::subtle::Random;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::AesCmacKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::NotNull;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  AesCmacParameters::Variant variant;
+  OutputPrefixType output_prefix_type;
+  int key_size;
+  int tag_size;
+  int total_size;
+  absl::optional<int> id;
+  std::string output_prefix;
+};
+
+class AesCmacProtoSerializationTest : public TestWithParam<TestCase> {
+ protected:
+  void SetUp() override {
+    internal::MutableSerializationRegistry::GlobalInstance().Reset();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    AesCmacProtoSerializationTestSuite, AesCmacProtoSerializationTest,
+    Values(TestCase{AesCmacParameters::Variant::kTink, OutputPrefixType::TINK,
+                    /*key_size=*/16, /*tag_size=*/10, /*total_size=*/15,
+                    /*id=*/0x02030400,
+                    /*output_prefix=*/std::string("\x01\x02\x03\x04\x00", 5)},
+           TestCase{AesCmacParameters::Variant::kCrunchy,
+                    OutputPrefixType::CRUNCHY, /*key_size=*/16,
+                    /*tag_size=*/12, /*total_size=*/17, /*id=*/0x01030005,
+                    /*output_prefix=*/std::string("\x00\x01\x03\x00\x05", 5)},
+           TestCase{AesCmacParameters::Variant::kLegacy,
+                    OutputPrefixType::LEGACY, /*key_size=*/32,
+                    /*cryptographic_tag_size=*/14, /*total_tag_size=*/19,
+                    /*id=*/0x01020304,
+                    /*output_prefix=*/std::string("\x00\x01\x02\x03\x04", 5)},
+           TestCase{AesCmacParameters::Variant::kNoPrefix,
+                    OutputPrefixType::RAW, /*key_size=*/32,
+                    /*cryptographic_tag_size=*/16, /*total_tag_size=*/16,
+                    /*id=*/absl::nullopt, /*output_prefix=*/""}));
+
+TEST_P(AesCmacProtoSerializationTest, ParseParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  AesCmacKeyFormat key_format_proto;
+  key_format_proto.set_key_size(test_case.key_size);
+  key_format_proto.mutable_params()->set_tag_size(test_case.tag_size);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey",
+          test_case.output_prefix_type, key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params, IsOk());
+  EXPECT_THAT((*params)->HasIdRequirement(), test_case.id.has_value());
+
+  const AesCmacParameters* cmac_params =
+      dynamic_cast<const AesCmacParameters*>(params->get());
+  ASSERT_THAT(cmac_params, NotNull());
+  EXPECT_THAT(cmac_params->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(cmac_params->KeySizeInBytes(), Eq(test_case.key_size));
+  EXPECT_THAT(cmac_params->CryptographicTagSizeInBytes(),
+              Eq(test_case.tag_size));
+  EXPECT_THAT(cmac_params->TotalTagSizeInBytes(), Eq(test_case.total_size));
+}
+
+TEST_F(AesCmacProtoSerializationTest, ParseParametersWithInvalidSerialization) {
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  AesCmacKeyFormat key_format_proto;
+  key_format_proto.set_key_size(16);
+  key_format_proto.mutable_params()->set_tag_size(10);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey",
+          OutputPrefixType::RAW, "invalid_serialization");
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesCmacProtoSerializationTest, ParseParametersWithUnkownOutputPrefix) {
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  AesCmacKeyFormat key_format_proto;
+  key_format_proto.set_key_size(16);
+  key_format_proto.mutable_params()->set_tag_size(10);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey",
+          OutputPrefixType::UNKNOWN_PREFIX,
+          key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesCmacProtoSerializationTest, SerializeParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      test_case.key_size, test_case.tag_size, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.AesCmacKey"));
+
+  const internal::ProtoParametersSerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoParametersSerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().type_url(),
+              Eq("type.googleapis.com/google.crypto.tink.AesCmacKey"));
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().output_prefix_type(),
+              Eq(test_case.output_prefix_type));
+
+  AesCmacKeyFormat key_format;
+  ASSERT_THAT(
+      key_format.ParseFromString(proto_serialization->GetKeyTemplate().value()),
+      IsTrue());
+  ASSERT_THAT(key_format.key_size(), Eq(test_case.key_size));
+  ASSERT_THAT(key_format.params().tag_size(), Eq(test_case.tag_size));
+}
+
+TEST_P(AesCmacProtoSerializationTest, ParseKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  google::crypto::tink::AesCmacKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  key_proto.mutable_params()->set_tag_size(test_case.tag_size);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey", serialized_key,
+          KeyData::SYMMETRIC, test_case.output_prefix_type, test_case.id);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key, IsOk());
+  EXPECT_THAT((*key)->GetIdRequirement(), Eq(test_case.id));
+  EXPECT_THAT((*key)->GetParameters().HasIdRequirement(),
+              test_case.id.has_value());
+
+  const AesCmacKey* cmac_key = dynamic_cast<const AesCmacKey*>(key->get());
+  ASSERT_THAT(cmac_key, NotNull());
+  util::StatusOr<RestrictedData> parsed_key =
+      cmac_key->GetKeyBytes(GetPartialKeyAccess());
+  ASSERT_THAT(parsed_key, IsOk());
+  EXPECT_THAT(parsed_key->GetSecret(InsecureSecretKeyAccess::Get()),
+              Eq(raw_key_bytes));
+  EXPECT_THAT(cmac_key->GetOutputPrefix(), Eq(test_case.output_prefix));
+  EXPECT_THAT(cmac_key->GetParameters().GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(cmac_key->GetParameters().KeySizeInBytes(),
+              Eq(test_case.key_size));
+  EXPECT_THAT(cmac_key->GetParameters().CryptographicTagSizeInBytes(),
+              Eq(test_case.tag_size));
+  EXPECT_THAT(cmac_key->GetParameters().TotalTagSizeInBytes(),
+              test_case.total_size);
+  EXPECT_THAT(cmac_key->GetParameters().HasIdRequirement(),
+              test_case.id.has_value());
+}
+
+TEST_F(AesCmacProtoSerializationTest, ParseKeyWithInvalidSerialization) {
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  RestrictedData serialized_key =
+      RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesCmacProtoSerializationTest, ParseKeyNoSecretKeyAccess) {
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  google::crypto::tink::AesCmacKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  key_proto.mutable_params()->set_tag_size(10);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, absl::nullopt);
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(AesCmacProtoSerializationTest, ParseKeyWithInvalidVersion) {
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  google::crypto::tink::AesCmacKey key_proto;
+  key_proto.set_version(1);  // Invalid version number.
+  key_proto.set_key_value(raw_key_bytes);
+  key_proto.mutable_params()->set_tag_size(10);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(AesCmacProtoSerializationTest, SerializeKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      test_case.key_size, test_case.tag_size, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      test_case.id, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.AesCmacKey"));
+
+  const internal::ProtoKeySerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoKeySerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->TypeUrl(),
+              Eq("type.googleapis.com/google.crypto.tink.AesCmacKey"));
+  EXPECT_THAT(proto_serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC));
+  EXPECT_THAT(proto_serialization->GetOutputPrefixType(),
+              Eq(test_case.output_prefix_type));
+  EXPECT_THAT(proto_serialization->IdRequirement(), Eq(test_case.id));
+
+  google::crypto::tink::AesCmacKey proto_key;
+  // OSS proto library complains if input is not converted to a string.
+  ASSERT_THAT(proto_key.ParseFromString(std::string(
+                  proto_serialization->SerializedKeyProto().GetSecret(
+                      InsecureSecretKeyAccess::Get()))),
+              IsTrue());
+  EXPECT_THAT(proto_key.key_value().size(), Eq(test_case.key_size));
+  EXPECT_THAT(proto_key.params().tag_size(), Eq(test_case.tag_size));
+}
+
+TEST_F(AesCmacProtoSerializationTest, SerializeKeyNoSecretKeyAccess) {
+  ASSERT_THAT(RegisterAesCmacProtoSerialization(), IsOk());
+
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/16, /*cryptographic_tag_size_in_bytes=*/10,
+      AesCmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  util::StatusOr<AesCmacKey> key = AesCmacKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(*key, absl::nullopt);
+  ASSERT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/failing_mac.cc b/cc/mac/failing_mac.cc
index 999bb83..5a1b49c 100644
--- a/cc/mac/failing_mac.cc
+++ b/cc/mac/failing_mac.cc
@@ -15,6 +15,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/mac/failing_mac.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/mac/failing_mac.h b/cc/mac/failing_mac.h
index 4eed37d..84bde37 100644
--- a/cc/mac/failing_mac.h
+++ b/cc/mac/failing_mac.h
@@ -16,6 +16,7 @@
 #ifndef TINK_MAC_FAILING_MAC_H_
 #define TINK_MAC_FAILING_MAC_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
diff --git a/cc/mac/hmac_key.cc b/cc/mac/hmac_key.cc
new file mode 100644
index 0000000..388dc31
--- /dev/null
+++ b/cc/mac/hmac_key.cc
@@ -0,0 +1,109 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/hmac_key.h"
+
+#include <memory>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_format.h"
+#include "absl/types/optional.h"
+#include "tink/mac/hmac_parameters.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/subtle_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+util::StatusOr<HmacKey> HmacKey::Create(const HmacParameters& parameters,
+                                        const RestrictedData& key_bytes,
+                                        absl::optional<int> id_requirement,
+                                        PartialKeyAccessToken token) {
+  if (parameters.KeySizeInBytes() != key_bytes.size()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Key size does not match HMAC parameters");
+  }
+  if (parameters.HasIdRequirement() && !id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key without ID requirement with parameters with ID "
+        "requirement");
+  }
+  if (!parameters.HasIdRequirement() && id_requirement.has_value()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create key with ID requirement with parameters without ID "
+        "requirement");
+  }
+  util::StatusOr<std::string> output_prefix =
+      ComputeOutputPrefix(parameters, id_requirement);
+  if (!output_prefix.ok()) {
+    return output_prefix.status();
+  }
+  return HmacKey(parameters, key_bytes, id_requirement,
+                 *std::move(output_prefix));
+}
+
+util::StatusOr<std::string> HmacKey::ComputeOutputPrefix(
+    const HmacParameters& parameters, absl::optional<int> id_requirement) {
+  switch (parameters.GetVariant()) {
+    case HmacParameters::Variant::kNoPrefix:
+      return std::string("");  // Empty prefix.
+    case HmacParameters::Variant::kLegacy:
+      ABSL_FALLTHROUGH_INTENDED;
+    case HmacParameters::Variant::kCrunchy:
+      if (!id_requirement.has_value()) {
+        return util::Status(
+            absl::StatusCode::kInvalidArgument,
+            "id requirement must have value with kCrunchy or kLegacy");
+      }
+      return absl::StrCat(absl::HexStringToBytes("00"),
+                          subtle::BigEndian32(*id_requirement));
+    case HmacParameters::Variant::kTink:
+      if (!id_requirement.has_value()) {
+        return util::Status(absl::StatusCode::kInvalidArgument,
+                            "id requirement must have value with kTink");
+      }
+      return absl::StrCat(absl::HexStringToBytes("01"),
+                          subtle::BigEndian32(*id_requirement));
+    default:
+      return util::Status(
+          absl::StatusCode::kInvalidArgument,
+          absl::StrCat("Invalid variant: ", parameters.GetVariant()));
+  }
+}
+
+bool HmacKey::operator==(const Key& other) const {
+  const HmacKey* that = dynamic_cast<const HmacKey*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (GetParameters() != that->GetParameters()) {
+    return false;
+  }
+  if (id_requirement_ != that->id_requirement_) {
+    return false;
+  }
+  return key_bytes_ == that->key_bytes_;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/hmac_key.h b/cc/mac/hmac_key.h
new file mode 100644
index 0000000..30f6bf5
--- /dev/null
+++ b/cc/mac/hmac_key.h
@@ -0,0 +1,85 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_MAC_HMAC_KEY_H_
+#define TINK_MAC_HMAC_KEY_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/types/optional.h"
+#include "tink/mac/hmac_parameters.h"
+#include "tink/mac/mac_key.h"
+#include "tink/partial_key_access_token.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+class HmacKey : public MacKey {
+ public:
+  // Copyable and movable.
+  HmacKey(const HmacKey& other) = default;
+  HmacKey& operator=(const HmacKey& other) = default;
+  HmacKey(HmacKey&& other) = default;
+  HmacKey& operator=(HmacKey&& other) = default;
+
+  // Creates a new HMAC key.  If the parameters specify a variant that uses
+  // a prefix, then the id is used to compute this prefix.
+  static util::StatusOr<HmacKey> Create(const HmacParameters& parameters,
+                                        const RestrictedData& key_bytes,
+                                        absl::optional<int> id_requirement,
+                                        PartialKeyAccessToken token);
+
+  // Returns the underlying HMAC key bytes.
+  util::StatusOr<RestrictedData> GetKeyBytes(
+      PartialKeyAccessToken token) const {
+    return key_bytes_;
+  }
+
+  absl::string_view GetOutputPrefix() const override { return output_prefix_; }
+
+  const HmacParameters& GetParameters() const override { return parameters_; }
+
+  absl::optional<int> GetIdRequirement() const override {
+    return id_requirement_;
+  }
+
+  bool operator==(const Key& other) const override;
+
+ private:
+  HmacKey(const HmacParameters& parameters, const RestrictedData& key_bytes,
+          absl::optional<int> id_requirement, std::string output_prefix)
+      : parameters_(parameters),
+        key_bytes_(key_bytes),
+        id_requirement_(id_requirement),
+        output_prefix_(std::move(output_prefix)) {}
+
+  static util::StatusOr<std::string> ComputeOutputPrefix(
+      const HmacParameters& parameters, absl::optional<int> id_requirement);
+
+  HmacParameters parameters_;
+  RestrictedData key_bytes_;
+  absl::optional<int> id_requirement_;
+  std::string output_prefix_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_MAC_HMAC_KEY_H_
diff --git a/cc/mac/hmac_key_manager.h b/cc/mac/hmac_key_manager.h
index b696a53..0dc34a9 100644
--- a/cc/mac/hmac_key_manager.h
+++ b/cc/mac/hmac_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_MAC_HMAC_KEY_MANAGER_H_
 #define TINK_MAC_HMAC_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/mac/hmac_key_manager_test.cc b/cc/mac/hmac_key_manager_test.cc
index db17da7..7367623 100644
--- a/cc/mac/hmac_key_manager_test.cc
+++ b/cc/mac/hmac_key_manager_test.cc
@@ -17,13 +17,14 @@
 #include "tink/mac/hmac_key_manager.h"
 
 #include <memory>
+#include <sstream>
 #include <string>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
-#include "tink/core/key_manager_impl.h"
 #include "tink/chunked_mac.h"
+#include "tink/core/key_manager_impl.h"
 #include "tink/mac.h"
 #include "tink/util/istream_input_stream.h"
 #include "tink/util/secret_data.h"
@@ -42,7 +43,6 @@
 using ::google::crypto::tink::HashType;
 using ::google::crypto::tink::HmacKey;
 using ::google::crypto::tink::HmacKeyFormat;
-using ::google::crypto::tink::KeyData;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
diff --git a/cc/mac/hmac_key_test.cc b/cc/mac/hmac_key_test.cc
new file mode 100644
index 0000000..e35aca7
--- /dev/null
+++ b/cc/mac/hmac_key_test.cc
@@ -0,0 +1,255 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/hmac_key.h"
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+#include "tink/mac/hmac_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  HmacParameters::Variant variant;
+  absl::optional<int> id_requirement;
+  std::string output_prefix;
+};
+
+using HmacKeyTest =
+    TestWithParam<std::tuple<int, int, HmacParameters::HashType, TestCase>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    HmacKeyTestSuite, HmacKeyTest,
+    Combine(Values(16, 32), Range(10, 20),
+            Values(HmacParameters::HashType::kSha1,
+                   HmacParameters::HashType::kSha224,
+                   HmacParameters::HashType::kSha256,
+                   HmacParameters::HashType::kSha384,
+                   HmacParameters::HashType::kSha512),
+            Values(TestCase{HmacParameters::Variant::kTink, 0x02030400,
+                            std::string("\x01\x02\x03\x04\x00", 5)},
+                   TestCase{HmacParameters::Variant::kCrunchy, 0x01030005,
+                            std::string("\x00\x01\x03\x00\x05", 5)},
+                   TestCase{HmacParameters::Variant::kLegacy, 0x01020304,
+                            std::string("\x00\x01\x02\x03\x04", 5)},
+                   TestCase{HmacParameters::Variant::kNoPrefix, absl::nullopt,
+                            ""})));
+
+TEST_P(HmacKeyTest, CreateSucceeds) {
+  int key_size;
+  int cryptographic_tag_size;
+  HmacParameters::HashType hash_type;
+  TestCase test_case;
+  std::tie(key_size, cryptographic_tag_size, hash_type, test_case) = GetParam();
+
+  util::StatusOr<HmacParameters> params = HmacParameters::Create(
+      key_size, cryptographic_tag_size, hash_type, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<HmacKey> key = HmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  EXPECT_THAT(key->GetParameters(), Eq(*params));
+  EXPECT_THAT(key->GetIdRequirement(), Eq(test_case.id_requirement));
+  EXPECT_THAT(key->GetOutputPrefix(), Eq(test_case.output_prefix));
+}
+
+TEST(HmacKeyTest, CreateKeyWithMismatchedKeySizeFails) {
+  // Key size parameter is 32 bytes.
+  util::StatusOr<HmacParameters> params = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      HmacParameters::HashType::kSha256, HmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  // Key material is 16 bytes (another valid key length).
+  RestrictedData mismatched_secret = RestrictedData(/*num_random_bytes=*/16);
+
+  EXPECT_THAT(HmacKey::Create(*params, mismatched_secret,
+                              /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(HmacKeyTest, CreateKeyWithWrongIdRequirementFails) {
+  util::StatusOr<HmacParameters> no_prefix_params = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      HmacParameters::HashType::kSha512, HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(no_prefix_params, IsOk());
+
+  util::StatusOr<HmacParameters> tink_params = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      HmacParameters::HashType::kSha512, HmacParameters::Variant::kTink);
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  EXPECT_THAT(HmacKey::Create(*no_prefix_params, secret,
+                              /*id_requirement=*/123, GetPartialKeyAccess())
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(
+      HmacKey::Create(*tink_params, secret,
+                      /*id_requirement=*/absl::nullopt, GetPartialKeyAccess())
+          .status(),
+      StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(HmacKeyTest, GetKeyBytes) {
+  int key_size;
+  int cryptographic_tag_size;
+  HmacParameters::HashType hash_type;
+  TestCase test_case;
+  std::tie(key_size, cryptographic_tag_size, hash_type, test_case) = GetParam();
+
+  util::StatusOr<HmacParameters> params = HmacParameters::Create(
+      key_size, cryptographic_tag_size, hash_type, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+
+  util::StatusOr<HmacKey> key = HmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  EXPECT_THAT(key->GetKeyBytes(GetPartialKeyAccess()), IsOkAndHolds(secret));
+}
+
+TEST_P(HmacKeyTest, KeyEquals) {
+  int key_size;
+  int cryptographic_tag_size;
+  HmacParameters::HashType hash_type;
+  TestCase test_case;
+  std::tie(key_size, cryptographic_tag_size, hash_type, test_case) = GetParam();
+
+  util::StatusOr<HmacParameters> params = HmacParameters::Create(
+      key_size, cryptographic_tag_size, hash_type, test_case.variant);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(key_size);
+  util::StatusOr<HmacKey> key = HmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<HmacKey> other_key = HmacKey::Create(
+      *params, secret, test_case.id_requirement, GetPartialKeyAccess());
+  ASSERT_THAT(other_key, IsOk());
+
+  EXPECT_TRUE(*key == *other_key);
+  EXPECT_TRUE(*other_key == *key);
+  EXPECT_FALSE(*key != *other_key);
+  EXPECT_FALSE(*other_key != *key);
+}
+
+TEST(HmacKeyTest, DifferentFormatNotEqual) {
+  util::StatusOr<HmacParameters> legacy_params = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      HmacParameters::HashType::kSha256, HmacParameters::Variant::kLegacy);
+  ASSERT_THAT(legacy_params, IsOk());
+
+  util::StatusOr<HmacParameters> tink_params = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      HmacParameters::HashType::kSha256, HmacParameters::Variant::kTink);
+  ASSERT_THAT(tink_params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<HmacKey> key =
+      HmacKey::Create(*legacy_params, secret, /*id_requirement=*/0x01020304,
+                      GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<HmacKey> other_key =
+      HmacKey::Create(*tink_params, secret, /*id_requirement=*/0x01020304,
+                      GetPartialKeyAccess());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(HmacKeyTest, DifferentSecretDataNotEqual) {
+  util::StatusOr<HmacParameters> params = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      HmacParameters::HashType::kSha384, HmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret1 = RestrictedData(/*num_random_bytes=*/32);
+  RestrictedData secret2 = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<HmacKey> key = HmacKey::Create(
+      *params, secret1, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<HmacKey> other_key = HmacKey::Create(
+      *params, secret2, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+TEST(HmacKeyTest, DifferentIdRequirementNotEqual) {
+  util::StatusOr<HmacParameters> params = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      HmacParameters::HashType::kSha224, HmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  RestrictedData secret = RestrictedData(/*num_random_bytes=*/32);
+
+  util::StatusOr<HmacKey> key = HmacKey::Create(
+      *params, secret, /*id_requirement=*/0x01020304, GetPartialKeyAccess());
+  ASSERT_THAT(key.status(), IsOk());
+
+  util::StatusOr<HmacKey> other_key = HmacKey::Create(
+      *params, secret, /*id_requirement=*/0x02030405, GetPartialKeyAccess());
+  ASSERT_THAT(other_key.status(), IsOk());
+
+  EXPECT_TRUE(*key != *other_key);
+  EXPECT_TRUE(*other_key != *key);
+  EXPECT_FALSE(*key == *other_key);
+  EXPECT_FALSE(*other_key == *key);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/hmac_parameters.cc b/cc/mac/hmac_parameters.cc
new file mode 100644
index 0000000..ad94b7b
--- /dev/null
+++ b/cc/mac/hmac_parameters.cc
@@ -0,0 +1,126 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/hmac_parameters.h"
+
+#include <cstdlib>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "tink/crypto_format.h"
+#include "tink/internal/util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+util::Status ValidateTagSizeBytes(int cryptographic_tag_size_in_bytes,
+                                  HmacParameters::HashType hash_type) {
+  if (cryptographic_tag_size_in_bytes < 10) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Tag size should be at least 10 bytes, got ",
+                     cryptographic_tag_size_in_bytes, " bytes."));
+  }
+  std::map<HmacParameters::HashType, uint32_t> max_tag_size = {
+      {HmacParameters::HashType::kSha1, 20},
+      {HmacParameters::HashType::kSha224, 28},
+      {HmacParameters::HashType::kSha256, 32},
+      {HmacParameters::HashType::kSha384, 48},
+      {HmacParameters::HashType::kSha512, 64}};
+  if (max_tag_size.find(hash_type) == max_tag_size.end()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Cannot create HMAC parameters with given hash type. ",
+                     hash_type, " not supported."));
+  }
+  if (cryptographic_tag_size_in_bytes > max_tag_size[hash_type]) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Tag size is too big for given ", hash_type, " , got ",
+                     cryptographic_tag_size_in_bytes, " bytes."));
+  }
+  return util::OkStatus();
+}
+
+}  // namespace
+
+util::StatusOr<HmacParameters> HmacParameters::Create(
+    int key_size_in_bytes, int cryptographic_tag_size_in_bytes,
+    HashType hash_type, Variant variant) {
+  if (key_size_in_bytes < 16) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        absl::StrCat("Key size must be at least 16 bytes, got ",
+                                     key_size_in_bytes, " bytes."));
+  }
+  util::Status status =
+      ValidateTagSizeBytes(cryptographic_tag_size_in_bytes, hash_type);
+  if (!status.ok()) return status;
+  static const std::set<Variant>* supported_variants =
+      new std::set<Variant>({Variant::kTink, Variant::kCrunchy,
+                             Variant::kLegacy, Variant::kNoPrefix});
+  if (supported_variants->find(variant) == supported_variants->end()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Cannot create HMAC parameters with unknown variant.");
+  }
+  return HmacParameters(key_size_in_bytes, cryptographic_tag_size_in_bytes,
+                        hash_type, variant);
+}
+
+int HmacParameters::TotalTagSizeInBytes() const {
+  switch (variant_) {
+    case Variant::kTink:
+    case Variant::kCrunchy:
+    case Variant::kLegacy:
+      return CryptographicTagSizeInBytes() + CryptoFormat::kNonRawPrefixSize;
+    case Variant::kNoPrefix:
+      return CryptographicTagSizeInBytes();
+    default:
+      // Parameters objects with unknown variants should never be created.
+      internal::LogFatal("HMAC parameters has an unknown variant.");
+  }
+}
+
+bool HmacParameters::operator==(const Parameters& other) const {
+  const HmacParameters* that = dynamic_cast<const HmacParameters*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  if (key_size_in_bytes_ != that->key_size_in_bytes_) {
+    return false;
+  }
+  if (cryptographic_tag_size_in_bytes_ !=
+      that->cryptographic_tag_size_in_bytes_) {
+    return false;
+  }
+  if (hash_type_ != that->hash_type_) {
+    return false;
+  }
+  if (variant_ != that->variant_) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/hmac_parameters.h b/cc/mac/hmac_parameters.h
new file mode 100644
index 0000000..1dc7a39
--- /dev/null
+++ b/cc/mac/hmac_parameters.h
@@ -0,0 +1,115 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_MAC_HMAC_PARAMETERS_H_
+#define TINK_MAC_HMAC_PARAMETERS_H_
+
+#include <memory>
+
+#include "tink/mac/mac_parameters.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes the parameters of an `HmacKey`.
+class HmacParameters : public MacParameters {
+ public:
+  // Describes the details of a MAC computation.
+  //
+  // The usual HMAC key is used for variant `NO_PREFIX`. Other variants
+  // slightly change how the MAC is computed, or add a prefix to every
+  // computation depending on the key id.
+  enum class Variant : int {
+    // Prepends '0x01<big endian key id>' to tag.
+    kTink = 1,
+    // Prepends '0x00<big endian key id>' to tag.
+    kCrunchy = 2,
+    // Appends a 0-byte to input message BEFORE computing the tag, then
+    // prepends '0x00<big endian key id>' to tag.
+    kLegacy = 3,
+    // Does not prepend any prefix (i.e., keys must have no ID requirement).
+    kNoPrefix = 4,
+    // Added to guard from failures that may be caused by future expansions.
+    kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+  };
+
+  // Describes the hash algorithm used.
+  enum class HashType : int {
+    kSha1 = 1,
+    kSha224 = 2,
+    kSha256 = 3,
+    kSha384 = 4,
+    kSha512 = 5,
+    kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+  };
+
+  // Copyable and movable.
+  HmacParameters(const HmacParameters& other) = default;
+  HmacParameters& operator=(const HmacParameters& other) = default;
+  HmacParameters(HmacParameters&& other) = default;
+  HmacParameters& operator=(HmacParameters&& other) = default;
+
+  // Creates a new HMAC parameters object unless an error occurs. An error
+  // occurs under one of the following conditions:
+  // 1. `key_size_in_bytes` is a value smaller than 16 bytes
+  // 2. `cryptographic_tag_size_in_bytes` is either less than 10 bytes or
+  // greater than the maximum value accepted by the corresponding hash algorithm
+  static util::StatusOr<HmacParameters> Create(
+      int key_size_in_bytes, int cryptographic_tag_size_in_bytes,
+      HashType hash_type, Variant variant);
+
+  Variant GetVariant() const { return variant_; }
+
+  HashType GetHashType() const { return hash_type_; }
+
+  int KeySizeInBytes() const { return key_size_in_bytes_; }
+
+  // Returns the size of the tag, which is computed cryptographically from the
+  // message. Note that this may differ from the total size of the tag, as for
+  // some keys, Tink prefixes the tag with a key dependent output prefix.
+  int CryptographicTagSizeInBytes() const {
+    return cryptographic_tag_size_in_bytes_;
+  }
+
+  // Returns the size of the cryptographic tag plus the size of the prefix with
+  // which this key prefixes every cryptographic tag.
+  int TotalTagSizeInBytes() const;
+
+  bool HasIdRequirement() const override {
+    return variant_ != Variant::kNoPrefix;
+  }
+
+  bool operator==(const Parameters& other) const override;
+
+ private:
+  HmacParameters(int key_size_in_bytes, int cryptographic_tag_size_in_bytes,
+                 HashType hash_type, Variant variant)
+      : key_size_in_bytes_(key_size_in_bytes),
+        cryptographic_tag_size_in_bytes_(cryptographic_tag_size_in_bytes),
+        hash_type_(hash_type),
+        variant_(variant) {}
+
+  int key_size_in_bytes_;
+  int cryptographic_tag_size_in_bytes_;
+  HashType hash_type_;
+  Variant variant_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_MAC_HMAC_PARAMETERS_H_
diff --git a/cc/mac/hmac_parameters_test.cc b/cc/mac/hmac_parameters_test.cc
new file mode 100644
index 0000000..e9dfeb0
--- /dev/null
+++ b/cc/mac/hmac_parameters_test.cc
@@ -0,0 +1,308 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/hmac_parameters.h"
+
+#include <memory>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct CreateTestCase {
+  HmacParameters::Variant variant;
+  int key_size;
+  int cryptographic_tag_size;
+  int total_tag_size;
+  HmacParameters::HashType hash_type;
+  bool has_id_requirement;
+};
+
+using HmacParametersCreateTest = TestWithParam<CreateTestCase>;
+
+INSTANTIATE_TEST_SUITE_P(
+    HmacParametersCreateTestSuite, HmacParametersCreateTest,
+    Values(CreateTestCase{HmacParameters::Variant::kNoPrefix, /*key_size=*/16,
+                          /*cryptographic_tag_size=*/20, /*total_tag_size=*/20,
+                          HmacParameters::HashType::kSha1,
+                          /*has_id_requirement=*/false},
+           CreateTestCase{HmacParameters::Variant::kTink, /*key_size=*/16,
+                          /*cryptographic_tag_size=*/28, /*total_tag_size=*/33,
+                          HmacParameters::HashType::kSha224,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{HmacParameters::Variant::kCrunchy, /*key_size=*/16,
+                          /*cryptographic_tag_size=*/32, /*total_tag_size=*/37,
+                          HmacParameters::HashType::kSha256,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{HmacParameters::Variant::kLegacy, /*key_size=*/32,
+                          /*cryptographic_tag_size=*/48, /*total_tag_size=*/53,
+                          HmacParameters::HashType::kSha384,
+                          /*has_id_requirement=*/true},
+           CreateTestCase{HmacParameters::Variant::kNoPrefix,
+                          /*key_size=*/32, /*cryptographic_tag_size=*/64,
+                          /*total_tag_size=*/64,
+                          HmacParameters::HashType::kSha512,
+                          /*has_id_requirement=*/false}));
+
+TEST_P(HmacParametersCreateTest, Create) {
+  CreateTestCase test_case = GetParam();
+
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      test_case.key_size, test_case.cryptographic_tag_size, test_case.hash_type,
+      test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  EXPECT_THAT(parameters->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(parameters->KeySizeInBytes(), Eq(test_case.key_size));
+  EXPECT_THAT(parameters->CryptographicTagSizeInBytes(),
+              Eq(test_case.cryptographic_tag_size));
+  EXPECT_THAT(parameters->TotalTagSizeInBytes(), Eq(test_case.total_tag_size));
+  EXPECT_THAT(parameters->GetHashType(), Eq(test_case.hash_type));
+  EXPECT_THAT(parameters->HasIdRequirement(), Eq(test_case.has_id_requirement));
+}
+
+TEST(HmacParametersTest, CreateWithInvalidVariantFails) {
+  EXPECT_THAT(HmacParameters::Create(
+                  /*key_size_in_bytes=*/16,
+                  /*cryptographic_tag_size_in_bytes=*/12,
+                  HmacParameters::HashType::kSha256,
+                  HmacParameters::Variant::
+                      kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(HmacParametersTest, CreateWithInvalidHashTypeFails) {
+  EXPECT_THAT(HmacParameters::Create(
+                  /*key_size_in_bytes=*/32,
+                  /*cryptographic_tag_size_in_bytes=*/12,
+                  HmacParameters::HashType::
+                      kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements,
+                  HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(HmacParametersTest, CreateWithInvalidKeySizeFails) {
+  EXPECT_THAT(HmacParameters::Create(/*key_size_in_bytes=*/15,
+                                     /*cryptographic_tag_size_in_bytes=*/16,
+                                     HmacParameters::HashType::kSha256,
+                                     HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(HmacParametersTest, CreateWithInvalidTagSizeFails) {
+  // Too small.
+  EXPECT_THAT(HmacParameters::Create(/*key_size_in_bytes=*/32,
+                                     /*cryptographic_tag_size_in_bytes=*/7,
+                                     HmacParameters::HashType::kSha224,
+                                     HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  // Too big for kSha1.
+  EXPECT_THAT(HmacParameters::Create(/*key_size_in_bytes=*/32,
+                                     /*cryptographic_tag_size_in_bytes=*/21,
+                                     HmacParameters::HashType::kSha1,
+                                     HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  // Too big for kSha224.
+  EXPECT_THAT(HmacParameters::Create(/*key_size_in_bytes=*/32,
+                                     /*cryptographic_tag_size_in_bytes=*/29,
+                                     HmacParameters::HashType::kSha224,
+                                     HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  // Too big for kSha256;
+  EXPECT_THAT(HmacParameters::Create(/*key_size_in_bytes=*/32,
+                                     /*cryptographic_tag_size_in_bytes=*/33,
+                                     HmacParameters::HashType::kSha256,
+                                     HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  // Too big for kSha384;
+  EXPECT_THAT(HmacParameters::Create(/*key_size_in_bytes=*/32,
+                                     /*cryptographic_tag_size_in_bytes=*/49,
+                                     HmacParameters::HashType::kSha384,
+                                     HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  // Too big for kSha512;
+  EXPECT_THAT(HmacParameters::Create(/*key_size_in_bytes=*/32,
+                                     /*cryptographic_tag_size_in_bytes=*/65,
+                                     HmacParameters::HashType::kSha512,
+                                     HmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(HmacParametersTest, CopyConstructor) {
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/12, HmacParameters::HashType::kSha256,
+      HmacParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  HmacParameters copy(*parameters);
+  EXPECT_THAT(copy.GetVariant(), Eq(parameters->GetVariant()));
+  EXPECT_THAT(copy.CryptographicTagSizeInBytes(),
+              Eq(parameters->CryptographicTagSizeInBytes()));
+  EXPECT_THAT(copy.TotalTagSizeInBytes(),
+              Eq(parameters->TotalTagSizeInBytes()));
+  EXPECT_THAT(copy.GetHashType(), Eq(parameters->GetHashType()));
+  EXPECT_THAT(copy.HasIdRequirement(), Eq(parameters->HasIdRequirement()));
+}
+
+TEST(HmacParametersTest, CopyAssignment) {
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/12, HmacParameters::HashType::kSha512,
+      HmacParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  HmacParameters copy = *parameters;
+  EXPECT_THAT(copy.GetVariant(), Eq(parameters->GetVariant()));
+  EXPECT_THAT(copy.CryptographicTagSizeInBytes(),
+              Eq(parameters->CryptographicTagSizeInBytes()));
+  EXPECT_THAT(copy.TotalTagSizeInBytes(),
+              Eq(parameters->TotalTagSizeInBytes()));
+  EXPECT_THAT(copy.GetHashType(), Eq(parameters->GetHashType()));
+  EXPECT_THAT(copy.HasIdRequirement(), Eq(parameters->HasIdRequirement()));
+}
+
+using HmacParametersVariantTest = TestWithParam<
+    std::tuple<int, int, HmacParameters::HashType, HmacParameters::Variant>>;
+
+INSTANTIATE_TEST_SUITE_P(HmacParametersVariantTestSuite,
+                         HmacParametersVariantTest,
+                         Combine(Range(16, 32), Range(10, 20),
+                                 Values(HmacParameters::HashType::kSha1,
+                                        HmacParameters::HashType::kSha224,
+                                        HmacParameters::HashType::kSha256,
+                                        HmacParameters::HashType::kSha384,
+                                        HmacParameters::HashType::kSha512),
+                                 Values(HmacParameters::Variant::kTink,
+                                        HmacParameters::Variant::kCrunchy,
+                                        HmacParameters::Variant::kLegacy,
+                                        HmacParameters::Variant::kNoPrefix)));
+
+TEST_P(HmacParametersVariantTest, ParametersEquals) {
+  int key_size;
+  int cryptographic_tag_size;
+  HmacParameters::HashType hash_type;
+  HmacParameters::Variant variant;
+  std::tie(key_size, cryptographic_tag_size, hash_type, variant) = GetParam();
+
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      key_size, cryptographic_tag_size, hash_type, variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<HmacParameters> other_parameters = HmacParameters::Create(
+      key_size, cryptographic_tag_size, hash_type, variant);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters == *other_parameters);
+  EXPECT_TRUE(*other_parameters == *parameters);
+  EXPECT_FALSE(*parameters != *other_parameters);
+  EXPECT_FALSE(*other_parameters != *parameters);
+}
+
+TEST(HmacParametersTest, KeySizeNotEqual) {
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/16,
+      /*cryptographic_tag_size_in_bytes=*/10, HmacParameters::HashType::kSha224,
+      HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<HmacParameters> other_parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/10, HmacParameters::HashType::kSha224,
+      HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(HmacParametersTest, HashTypeNotEqual) {
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/10, HmacParameters::HashType::kSha256,
+      HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<HmacParameters> other_parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/10, HmacParameters::HashType::kSha512,
+      HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(HmacParametersTest, TagSizeNotEqual) {
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/10, HmacParameters::HashType::kSha256,
+      HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<HmacParameters> other_parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/11, HmacParameters::HashType::kSha256,
+      HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(HmacParametersTest, VariantNotEqual) {
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/10, HmacParameters::HashType::kSha256,
+      HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<HmacParameters> other_parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32,
+      /*cryptographic_tag_size_in_bytes=*/10, HmacParameters::HashType::kSha256,
+      HmacParameters::Variant::kTink);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/hmac_proto_serialization.cc b/cc/mac/hmac_proto_serialization.cc
new file mode 100644
index 0000000..aaa6ced
--- /dev/null
+++ b/cc/mac/hmac_proto_serialization.cc
@@ -0,0 +1,305 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/hmac_proto_serialization.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "absl/types/optional.h"
+#include "tink/internal/key_parser.h"
+#include "tink/internal/key_serializer.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/parameters_parser.h"
+#include "tink/internal/parameters_serializer.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/mac/hmac_key.h"
+#include "tink/mac/hmac_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/common.pb.h"
+#include "proto/hmac.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::HmacKeyFormat;
+using ::google::crypto::tink::HmacParams;
+using ::google::crypto::tink::OutputPrefixType;
+
+using HmacProtoParametersParserImpl =
+    internal::ParametersParserImpl<internal::ProtoParametersSerialization,
+                                   HmacParameters>;
+using HmacProtoParametersSerializerImpl =
+    internal::ParametersSerializerImpl<HmacParameters,
+                                       internal::ProtoParametersSerialization>;
+using HmacProtoKeyParserImpl =
+    internal::KeyParserImpl<internal::ProtoKeySerialization, HmacKey>;
+using HmacProtoKeySerializerImpl =
+    internal::KeySerializerImpl<HmacKey, internal::ProtoKeySerialization>;
+
+const absl::string_view kTypeUrl =
+    "type.googleapis.com/google.crypto.tink.HmacKey";
+
+util::StatusOr<HmacParameters::Variant> ToVariant(
+    OutputPrefixType output_prefix_type) {
+  switch (output_prefix_type) {
+    case OutputPrefixType::CRUNCHY:
+      return HmacParameters::Variant::kCrunchy;
+    case OutputPrefixType::LEGACY:
+      return HmacParameters::Variant::kLegacy;
+    case OutputPrefixType::RAW:
+      return HmacParameters::Variant::kNoPrefix;
+    case OutputPrefixType::TINK:
+      return HmacParameters::Variant::kTink;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine HmacParameters::Variant");
+  }
+}
+
+util::StatusOr<OutputPrefixType> ToOutputPrefixType(
+    HmacParameters::Variant variant) {
+  switch (variant) {
+    case HmacParameters::Variant::kCrunchy:
+      return OutputPrefixType::CRUNCHY;
+    case HmacParameters::Variant::kLegacy:
+      return OutputPrefixType::LEGACY;
+    case HmacParameters::Variant::kNoPrefix:
+      return OutputPrefixType::RAW;
+    case HmacParameters::Variant::kTink:
+      return OutputPrefixType::TINK;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine output prefix type");
+  }
+}
+
+util::StatusOr<HmacParameters::HashType> ToHashType(HashType hash_type) {
+  switch (hash_type) {
+    case HashType::SHA1:
+      return HmacParameters::HashType::kSha1;
+    case HashType::SHA224:
+      return HmacParameters::HashType::kSha224;
+    case HashType::SHA256:
+      return HmacParameters::HashType::kSha256;
+    case HashType::SHA384:
+      return HmacParameters::HashType::kSha384;
+    case HashType::SHA512:
+      return HmacParameters::HashType::kSha512;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine HashType");
+  }
+}
+
+util::StatusOr<HashType> ToProtoHashType(HmacParameters::HashType hash_type) {
+  switch (hash_type) {
+    case HmacParameters::HashType::kSha1:
+      return HashType::SHA1;
+    case HmacParameters::HashType::kSha224:
+      return HashType::SHA224;
+    case HmacParameters::HashType::kSha256:
+      return HashType::SHA256;
+    case HmacParameters::HashType::kSha384:
+      return HashType::SHA384;
+    case HmacParameters::HashType::kSha512:
+      return HashType::SHA512;
+    default:
+      return util::Status(absl::StatusCode::kInvalidArgument,
+                          "Could not determine HmacParameters::HashType");
+  }
+}
+
+util::StatusOr<HmacParameters> ParseParameters(
+    const internal::ProtoParametersSerialization& serialization) {
+  if (serialization.GetKeyTemplate().type_url() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing HmacParameters.");
+  }
+
+  HmacKeyFormat proto_key_format;
+  if (!proto_key_format.ParseFromString(
+          serialization.GetKeyTemplate().value())) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse HmacKeyFormat proto");
+  }
+  if (proto_key_format.version() != 0) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Parsing HmacParameters failed: only version 0 is accepted");
+  }
+
+  util::StatusOr<HmacParameters::Variant> variant =
+      ToVariant(serialization.GetKeyTemplate().output_prefix_type());
+  if (!variant.ok()) return variant.status();
+
+  util::StatusOr<HmacParameters::HashType> hash_type =
+      ToHashType(proto_key_format.params().hash());
+  if (!hash_type.ok()) return variant.status();
+
+  return HmacParameters::Create(proto_key_format.key_size(),
+                                proto_key_format.params().tag_size(),
+                                *hash_type, *variant);
+}
+
+util::StatusOr<internal::ProtoParametersSerialization> SerializeParameters(
+    const HmacParameters& parameters) {
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(parameters.GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+  util::StatusOr<HashType> proto_hash_type =
+      ToProtoHashType(parameters.GetHashType());
+  if (!proto_hash_type.ok()) return proto_hash_type.status();
+
+  HmacParams proto_params;
+  proto_params.set_tag_size(parameters.CryptographicTagSizeInBytes());
+  proto_params.set_hash(*proto_hash_type);
+  HmacKeyFormat proto_key_format;
+  proto_key_format.set_key_size(parameters.KeySizeInBytes());
+  proto_key_format.set_version(0);
+  *proto_key_format.mutable_params() = proto_params;
+
+  return internal::ProtoParametersSerialization::Create(
+      kTypeUrl, *output_prefix_type, proto_key_format.SerializeAsString());
+}
+
+util::StatusOr<HmacKey> ParseKey(
+    const internal::ProtoKeySerialization& serialization,
+    absl::optional<SecretKeyAccessToken> token) {
+  if (serialization.TypeUrl() != kTypeUrl) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Wrong type URL when parsing HmacKey.");
+  }
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+
+  google::crypto::tink::HmacKey proto_key;
+  RestrictedData restricted_data = serialization.SerializedKeyProto();
+  // OSS proto library complains if input is not converted to a string.
+  if (!proto_key.ParseFromString(
+          std::string(restricted_data.GetSecret(*token)))) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Failed to parse HmacKey proto");
+  }
+  if (proto_key.version() != 0) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Only version 0 keys are accepted.");
+  }
+
+  util::StatusOr<HmacParameters::Variant> variant =
+      ToVariant(serialization.GetOutputPrefixType());
+  if (!variant.ok()) return variant.status();
+  util::StatusOr<HmacParameters::HashType> hash_type =
+      ToHashType(proto_key.params().hash());
+  if (!hash_type.ok()) return variant.status();
+
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      proto_key.key_value().length(), proto_key.params().tag_size(), *hash_type,
+      *variant);
+  if (!parameters.ok()) return parameters.status();
+
+  return HmacKey::Create(*parameters,
+                         RestrictedData(proto_key.key_value(), *token),
+                         serialization.IdRequirement(), GetPartialKeyAccess());
+}
+
+util::StatusOr<internal::ProtoKeySerialization> SerializeKey(
+    const HmacKey& key, absl::optional<SecretKeyAccessToken> token) {
+  util::StatusOr<RestrictedData> restricted_input =
+      key.GetKeyBytes(GetPartialKeyAccess());
+  if (!token.has_value()) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "SecretKeyAccess is required");
+  }
+  if (!restricted_input.ok()) return restricted_input.status();
+  util::StatusOr<HashType> proto_hash_type =
+      ToProtoHashType(key.GetParameters().GetHashType());
+  if (!proto_hash_type.ok()) return proto_hash_type.status();
+
+  HmacParams proto_params;
+  proto_params.set_tag_size(key.GetParameters().CryptographicTagSizeInBytes());
+  proto_params.set_hash(*proto_hash_type);
+  google::crypto::tink::HmacKey proto_key;
+  *proto_key.mutable_params() = proto_params;
+  proto_key.set_version(0);
+  // OSS proto library complains if input is not converted to a string.
+  proto_key.set_key_value(std::string(restricted_input->GetSecret(*token)));
+
+  util::StatusOr<OutputPrefixType> output_prefix_type =
+      ToOutputPrefixType(key.GetParameters().GetVariant());
+  if (!output_prefix_type.ok()) return output_prefix_type.status();
+
+  RestrictedData restricted_output =
+      RestrictedData(proto_key.SerializeAsString(), *token);
+  return internal::ProtoKeySerialization::Create(
+      kTypeUrl, restricted_output, google::crypto::tink::KeyData::SYMMETRIC,
+      *output_prefix_type, key.GetIdRequirement());
+}
+
+HmacProtoParametersParserImpl* HmacProtoParametersParser() {
+  static auto* parser =
+      new HmacProtoParametersParserImpl(kTypeUrl, ParseParameters);
+  return parser;
+}
+
+HmacProtoParametersSerializerImpl* HmacProtoParametersSerializer() {
+  static auto* serializer =
+      new HmacProtoParametersSerializerImpl(kTypeUrl, SerializeParameters);
+  return serializer;
+}
+
+HmacProtoKeyParserImpl* HmacProtoKeyParser() {
+  static auto* parser = new HmacProtoKeyParserImpl(kTypeUrl, ParseKey);
+  return parser;
+}
+
+HmacProtoKeySerializerImpl* HmacProtoKeySerializer() {
+  static auto* serializer = new HmacProtoKeySerializerImpl(SerializeKey);
+  return serializer;
+}
+
+}  // namespace
+
+util::Status RegisterHmacProtoSerialization() {
+  util::Status status =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .RegisterParametersParser(HmacProtoParametersParser());
+  if (!status.ok()) return status;
+
+  status = internal::MutableSerializationRegistry::GlobalInstance()
+               .RegisterParametersSerializer(HmacProtoParametersSerializer());
+  if (!status.ok()) return status;
+
+  status = internal::MutableSerializationRegistry::GlobalInstance()
+               .RegisterKeyParser(HmacProtoKeyParser());
+  if (!status.ok()) return status;
+
+  return internal::MutableSerializationRegistry::GlobalInstance()
+      .RegisterKeySerializer(HmacProtoKeySerializer());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/hmac_proto_serialization.h b/cc/mac/hmac_proto_serialization.h
new file mode 100644
index 0000000..2996827
--- /dev/null
+++ b/cc/mac/hmac_proto_serialization.h
@@ -0,0 +1,31 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_MAC_HMAC_PROTO_SERIALIZATION_H_
+#define TINK_MAC_HMAC_PROTO_SERIALIZATION_H_
+
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+// Registers proto parsers and serializers for HMAC parameters and keys.
+crypto::tink::util::Status RegisterHmacProtoSerialization();
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_MAC_HMAC_PROTO_SERIALIZATION_H_
diff --git a/cc/mac/hmac_proto_serialization_test.cc b/cc/mac/hmac_proto_serialization_test.cc
new file mode 100644
index 0000000..4bcc684
--- /dev/null
+++ b/cc/mac/hmac_proto_serialization_test.cc
@@ -0,0 +1,409 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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 "tink/mac/hmac_proto_serialization.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/mac/hmac_key.h"
+#include "tink/mac/hmac_parameters.h"
+#include "tink/partial_key_access.h"
+#include "tink/restricted_data.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_matchers.h"
+#include "proto/common.pb.h"
+#include "proto/hmac.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::subtle::Random;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::HmacKeyFormat;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::OutputPrefixType;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::NotNull;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct TestCase {
+  HmacParameters::Variant variant;
+  OutputPrefixType output_prefix_type;
+  HmacParameters::HashType hash_type;
+  HashType proto_hash_type;
+  int key_size;
+  int tag_size;
+  int total_size;
+  absl::optional<int> id;
+  std::string output_prefix;
+};
+
+class HmacProtoSerializationTest : public TestWithParam<TestCase> {
+ protected:
+  void SetUp() override {
+    internal::MutableSerializationRegistry::GlobalInstance().Reset();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    HmacProtoSerializationTestSuite, HmacProtoSerializationTest,
+    Values(TestCase{HmacParameters::Variant::kTink, OutputPrefixType::TINK,
+                    HmacParameters::HashType::kSha1, HashType::SHA1,
+                    /*key_size=*/16, /*cryptographic_tag_size=*/10,
+                    /*total_size=*/15, /*id=*/0x02030400,
+                    /*output_prefix=*/std::string("\x01\x02\x03\x04\x00", 5)},
+           TestCase{HmacParameters::Variant::kCrunchy,
+                    OutputPrefixType::CRUNCHY,
+                    HmacParameters::HashType::kSha224, HashType::SHA224,
+                    /*key_size=*/16, /*tag_size=*/12, /*total_size=*/17,
+                    /*id=*/0x01030005,
+                    /*output_prefix=*/std::string("\x00\x01\x03\x00\x05", 5)},
+           TestCase{HmacParameters::Variant::kLegacy, OutputPrefixType::LEGACY,
+                    HmacParameters::HashType::kSha256, HashType::SHA256,
+                    /*key_size=*/32, /*cryptographic_tag_size=*/14,
+                    /*total_tag_size=*/19, /*id=*/0x01020304,
+                    /*output_prefix=*/std::string("\x00\x01\x02\x03\x04", 5)},
+           TestCase{HmacParameters::Variant::kNoPrefix, OutputPrefixType::RAW,
+                    HmacParameters::HashType::kSha384, HashType::SHA384,
+                    /*key_size=*/32, /*cryptographic_tag_size=*/16,
+                    /*total_tag_size=*/16, /*id=*/absl::nullopt,
+                    /*output_prefix=*/""},
+           TestCase{HmacParameters::Variant::kNoPrefix, OutputPrefixType::RAW,
+                    HmacParameters::HashType::kSha512, HashType::SHA512,
+                    /*key_size=*/32, /*cryptographic_tag_size=*/20,
+                    /*total_tag_size=*/20, /*id=*/absl::nullopt,
+                    /*output_prefix=*/""}));
+
+TEST_P(HmacProtoSerializationTest, ParseParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  HmacKeyFormat key_format_proto;
+  key_format_proto.set_key_size(test_case.key_size);
+  key_format_proto.mutable_params()->set_tag_size(test_case.tag_size);
+  key_format_proto.mutable_params()->set_hash(test_case.proto_hash_type);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey",
+          test_case.output_prefix_type, key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> parsed_parameters =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(parsed_parameters, IsOk());
+  EXPECT_THAT((*parsed_parameters)->HasIdRequirement(),
+              test_case.id.has_value());
+
+  util::StatusOr<HmacParameters> expected_parameters =
+      HmacParameters::Create(test_case.key_size, test_case.tag_size,
+                             test_case.hash_type, test_case.variant);
+  ASSERT_THAT(expected_parameters, IsOk());
+  ASSERT_THAT(**parsed_parameters, Eq(*expected_parameters));
+}
+
+TEST_F(HmacProtoSerializationTest, ParseParametersWithInvalidSerialization) {
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey",
+          OutputPrefixType::RAW, "invalid_serialization");
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(HmacProtoSerializationTest, ParseParametersWithInvalidVersion) {
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  HmacKeyFormat key_format_proto;
+  key_format_proto.set_key_size(16);
+  key_format_proto.set_version(1);  // Invalid version.
+  key_format_proto.mutable_params()->set_tag_size(10);
+  key_format_proto.mutable_params()->set_hash(HashType::SHA256);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey",
+          OutputPrefixType::RAW, key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(HmacProtoSerializationTest, ParseParametersWithUnkownOutputPrefix) {
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  HmacKeyFormat key_format_proto;
+  key_format_proto.set_key_size(16);
+  key_format_proto.mutable_params()->set_tag_size(10);
+
+  util::StatusOr<internal::ProtoParametersSerialization> serialization =
+      internal::ProtoParametersSerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey",
+          OutputPrefixType::UNKNOWN_PREFIX,
+          key_format_proto.SerializeAsString());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *serialization);
+  ASSERT_THAT(params.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(HmacProtoSerializationTest, SerializeParameters) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  util::StatusOr<HmacParameters> parameters =
+      HmacParameters::Create(test_case.key_size, test_case.tag_size,
+                             test_case.hash_type, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.HmacKey"));
+
+  const internal::ProtoParametersSerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoParametersSerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().type_url(),
+              Eq("type.googleapis.com/google.crypto.tink.HmacKey"));
+  EXPECT_THAT(proto_serialization->GetKeyTemplate().output_prefix_type(),
+              Eq(test_case.output_prefix_type));
+
+  HmacKeyFormat key_format;
+  ASSERT_THAT(
+      key_format.ParseFromString(proto_serialization->GetKeyTemplate().value()),
+      IsTrue());
+  ASSERT_THAT(key_format.key_size(), Eq(test_case.key_size));
+  ASSERT_THAT(key_format.params().tag_size(), Eq(test_case.tag_size));
+  ASSERT_THAT(key_format.params().hash(), Eq(test_case.proto_hash_type));
+}
+
+TEST_P(HmacProtoSerializationTest, ParseKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  google::crypto::tink::HmacKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  key_proto.mutable_params()->set_tag_size(test_case.tag_size);
+  key_proto.mutable_params()->set_hash(test_case.proto_hash_type);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey", serialized_key,
+          KeyData::SYMMETRIC, test_case.output_prefix_type, test_case.id);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> parsed_key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_key, IsOk());
+  EXPECT_THAT((*parsed_key)->GetParameters().HasIdRequirement(),
+              test_case.id.has_value());
+  EXPECT_THAT((*parsed_key)->GetIdRequirement(), Eq(test_case.id));
+
+  util::StatusOr<HmacParameters> expected_parameters =
+      HmacParameters::Create(test_case.key_size, test_case.tag_size,
+                             test_case.hash_type, test_case.variant);
+  ASSERT_THAT(expected_parameters, IsOk());
+  util::StatusOr<HmacKey> expected_key = HmacKey::Create(
+      *expected_parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      test_case.id, GetPartialKeyAccess());
+
+  ASSERT_THAT(expected_key, IsOk());
+  ASSERT_THAT(**parsed_key, Eq(*expected_key));
+}
+
+TEST_F(HmacProtoSerializationTest, ParseKeyWithInvalidSerialization) {
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  google::crypto::tink::HmacKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  key_proto.mutable_params()->set_tag_size(10);
+  key_proto.mutable_params()->set_hash(HashType::SHA256);
+  RestrictedData serialized_key =
+      RestrictedData("invalid_serialization", InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(HmacProtoSerializationTest, ParseKeyWithInvalidVersion) {
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  google::crypto::tink::HmacKey key_proto;
+  key_proto.set_version(1);  // Invalid version number.
+  key_proto.set_key_value(raw_key_bytes);
+  key_proto.mutable_params()->set_tag_size(10);
+  key_proto.mutable_params()->set_hash(HashType::SHA256);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_F(HmacProtoSerializationTest, ParseKeyWithoutSecretKeyAccess) {
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  google::crypto::tink::HmacKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(raw_key_bytes);
+  key_proto.mutable_params()->set_tag_size(10);
+  key_proto.mutable_params()->set_hash(HashType::SHA256);
+  RestrictedData serialized_key = RestrictedData(
+      key_proto.SerializeAsString(), InsecureSecretKeyAccess::Get());
+
+  util::StatusOr<internal::ProtoKeySerialization> serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey", serialized_key,
+          KeyData::SYMMETRIC, OutputPrefixType::TINK,
+          /*id_requirement=*/0x23456789);
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *serialization, absl::nullopt);
+  ASSERT_THAT(key.status(), StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST_P(HmacProtoSerializationTest, SerializeKey) {
+  TestCase test_case = GetParam();
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  util::StatusOr<HmacParameters> parameters =
+      HmacParameters::Create(test_case.key_size, test_case.tag_size,
+                             test_case.hash_type, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(test_case.key_size);
+  util::StatusOr<HmacKey> key = HmacKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      test_case.id, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+  EXPECT_THAT((*serialization)->ObjectIdentifier(),
+              Eq("type.googleapis.com/google.crypto.tink.HmacKey"));
+
+  const internal::ProtoKeySerialization* proto_serialization =
+      dynamic_cast<const internal::ProtoKeySerialization*>(
+          serialization->get());
+  ASSERT_THAT(proto_serialization, NotNull());
+  EXPECT_THAT(proto_serialization->TypeUrl(),
+              Eq("type.googleapis.com/google.crypto.tink.HmacKey"));
+  EXPECT_THAT(proto_serialization->KeyMaterialType(), Eq(KeyData::SYMMETRIC));
+  EXPECT_THAT(proto_serialization->GetOutputPrefixType(),
+              Eq(test_case.output_prefix_type));
+  EXPECT_THAT(proto_serialization->IdRequirement(), Eq(test_case.id));
+
+  google::crypto::tink::HmacKey proto_key;
+  // OSS proto library complains if input is not converted to a string.
+  ASSERT_THAT(proto_key.ParseFromString(std::string(
+                  proto_serialization->SerializedKeyProto().GetSecret(
+                      InsecureSecretKeyAccess::Get()))),
+              IsTrue());
+  EXPECT_THAT(proto_key.key_value().size(), Eq(test_case.key_size));
+  EXPECT_THAT(proto_key.params().tag_size(), Eq(test_case.tag_size));
+  EXPECT_THAT(proto_key.params().hash(), Eq(test_case.proto_hash_type));
+}
+
+TEST_F(HmacProtoSerializationTest, SerializeKeyWithoutSecretKeyAccess) {
+  ASSERT_THAT(RegisterHmacProtoSerialization(), IsOk());
+
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/16, /*cryptographic_tag_size_in_bytes=*/10,
+      HmacParameters::HashType::kSha256, HmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  std::string raw_key_bytes = Random::GetRandomBytes(16);
+  util::StatusOr<HmacKey> key = HmacKey::Create(
+      *parameters,
+      RestrictedData(raw_key_bytes, InsecureSecretKeyAccess::Get()),
+      /*id_requirement=*/absl::nullopt, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialization =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(*key, absl::nullopt);
+  ASSERT_THAT(serialization.status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/internal/chunked_mac_impl.h b/cc/mac/internal/chunked_mac_impl.h
index 940cf47..6a5d0a8 100644
--- a/cc/mac/internal/chunked_mac_impl.h
+++ b/cc/mac/internal/chunked_mac_impl.h
@@ -14,8 +14,8 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_CHUNKEDMAC_INTERNAL_CHUNKED_MAC_IMPL_H_
-#define TINK_CHUNKEDMAC_INTERNAL_CHUNKED_MAC_IMPL_H_
+#ifndef TINK_MAC_INTERNAL_CHUNKED_MAC_IMPL_H_
+#define TINK_MAC_INTERNAL_CHUNKED_MAC_IMPL_H_
 
 #include <memory>
 #include <string>
@@ -92,4 +92,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_CHUNKEDMAC_INTERNAL_CHUNKED_MAC_IMPL_H_
+#endif  // TINK_MAC_INTERNAL_CHUNKED_MAC_IMPL_H_
diff --git a/cc/mac/internal/chunked_mac_wrapper.cc b/cc/mac/internal/chunked_mac_wrapper.cc
index 5988daa..c95853c 100644
--- a/cc/mac/internal/chunked_mac_wrapper.cc
+++ b/cc/mac/internal/chunked_mac_wrapper.cc
@@ -155,7 +155,7 @@
   util::StatusOr<std::unique_ptr<ChunkedMacVerification>> CreateVerification(
       absl::string_view tag) const override;
 
-  ~ChunkedMacSetWrapper() override {}
+  ~ChunkedMacSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<ChunkedMac>> mac_set_;
diff --git a/cc/mac/internal/chunked_mac_wrapper.h b/cc/mac/internal/chunked_mac_wrapper.h
index 2e8e0dd..ad86442 100644
--- a/cc/mac/internal/chunked_mac_wrapper.h
+++ b/cc/mac/internal/chunked_mac_wrapper.h
@@ -14,8 +14,8 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_CHUNKEDMAC_INTERNAL_CHUNKED_MAC_WRAPPER_H_
-#define TINK_CHUNKEDMAC_INTERNAL_CHUNKED_MAC_WRAPPER_H_
+#ifndef TINK_MAC_INTERNAL_CHUNKED_MAC_WRAPPER_H_
+#define TINK_MAC_INTERNAL_CHUNKED_MAC_WRAPPER_H_
 
 #include <memory>
 
@@ -47,4 +47,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_CHUNKEDMAC_INTERNAL_CHUNKED_MAC_WRAPPER_H_
+#endif  // TINK_MAC_INTERNAL_CHUNKED_MAC_WRAPPER_H_
diff --git a/cc/mac/mac_config.cc b/cc/mac/mac_config.cc
index 6b87fd3..5f9997a 100644
--- a/cc/mac/mac_config.cc
+++ b/cc/mac/mac_config.cc
@@ -20,7 +20,9 @@
 #include "tink/config/config_util.h"
 #include "tink/config/tink_fips.h"
 #include "tink/mac/aes_cmac_key_manager.h"
+#include "tink/mac/aes_cmac_proto_serialization.h"
 #include "tink/mac/hmac_key_manager.h"
+#include "tink/mac/hmac_proto_serialization.h"
 #include "tink/mac/internal/chunked_mac_wrapper.h"
 #include "tink/mac/mac_wrapper.h"
 #include "tink/registry.h"
@@ -33,12 +35,6 @@
 namespace tink {
 
 // static
-const RegistryConfig& MacConfig::Latest() {
-  static const RegistryConfig* config = new RegistryConfig();
-  return *config;
-}
-
-// static
 util::Status MacConfig::Register() {
   // Register primitive wrappers.
   auto status =
@@ -55,6 +51,9 @@
                                             true);
   if (!status.ok()) return status;
 
+  status = RegisterHmacProtoSerialization();
+  if (!status.ok()) return status;
+
   if (IsFipsModeEnabled()) {
     return util::OkStatus();
   }
@@ -64,6 +63,9 @@
       absl::make_unique<AesCmacKeyManager>(), true);
   if (!status.ok()) return status;
 
+  status = RegisterAesCmacProtoSerialization();
+  if (!status.ok()) return status;
+
   return util::OkStatus();
 }
 
diff --git a/cc/mac/mac_config.h b/cc/mac/mac_config.h
index 7213e61..1e482b0 100644
--- a/cc/mac/mac_config.h
+++ b/cc/mac/mac_config.h
@@ -34,14 +34,6 @@
 //
 class MacConfig {
  public:
-  static constexpr char kCatalogueName[] = "TinkMac";
-  static constexpr char kPrimitiveName[] = "Mac";
-
-  // Returns config of Mac implementations supported
-  // in the current Tink release.
-  ABSL_DEPRECATED("This is not supported anymore.")
-  static const google::crypto::tink::RegistryConfig& Latest();
-
   // Registers Mac primitive wrapper and key managers for all Mac key types
   // from the current Tink release.
   static crypto::tink::util::Status Register();
diff --git a/cc/mac/mac_config_test.cc b/cc/mac/mac_config_test.cc
index 6c63278..4f002f7 100644
--- a/cc/mac/mac_config_test.cc
+++ b/cc/mac/mac_config_test.cc
@@ -24,17 +24,27 @@
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "tink/chunked_mac.h"
-#include "tink/config.h"
-#include "tink/config/tink_fips.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/internal/mutable_serialization_registry.h"
+#include "tink/internal/proto_key_serialization.h"
+#include "tink/internal/proto_parameters_serialization.h"
 #include "tink/keyset_handle.h"
 #include "tink/mac.h"
+#include "tink/mac/aes_cmac_key.h"
 #include "tink/mac/aes_cmac_key_manager.h"
+#include "tink/mac/aes_cmac_parameters.h"
+#include "tink/mac/hmac_key.h"
 #include "tink/mac/hmac_key_manager.h"
+#include "tink/mac/hmac_parameters.h"
 #include "tink/mac/mac_key_templates.h"
+#include "tink/partial_key_access.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
 
 namespace crypto {
 namespace tink {
@@ -43,8 +53,10 @@
 using ::crypto::tink::test::DummyMac;
 using ::crypto::tink::test::IsOk;
 using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeyData;
 using ::google::crypto::tink::KeysetInfo;
 using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::HashType;
 using ::google::crypto::tink::KeyTemplate;
 using ::google::crypto::tink::OutputPrefixType;
 using ::testing::Values;
@@ -53,11 +65,12 @@
  protected:
   void SetUp() override {
     Registry::Reset();
+    internal::MutableSerializationRegistry::GlobalInstance().Reset();
   }
 };
 
 TEST_F(MacConfigTest, Basic) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
@@ -97,7 +110,7 @@
 // Tests that the MacWrapper has been properly registered and we can wrap
 // primitives.
 TEST_F(MacConfigTest, MacWrappersRegistered) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
@@ -128,6 +141,201 @@
       DummyMac("dummy").VerifyMac(mac_result.value(), "faked text").ok());
 }
 
+TEST_F(MacConfigTest, AesCmacProtoParamsSerializationRegistered) {
+  if (internal::IsFipsModeEnabled()) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
+  util::StatusOr<internal::ProtoParametersSerialization>
+      proto_params_serialization =
+          internal::ProtoParametersSerialization::Create(
+              MacKeyTemplates::AesCmac());
+  ASSERT_THAT(proto_params_serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> parsed_params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *proto_params_serialization);
+  ASSERT_THAT(parsed_params.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_params =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(*params);
+  ASSERT_THAT(serialized_params.status(),
+              StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(MacConfig::Register(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> parsed_params2 =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *proto_params_serialization);
+  ASSERT_THAT(parsed_params2, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_params2 =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(*params);
+  ASSERT_THAT(serialized_params2, IsOk());
+}
+
+TEST_F(MacConfigTest, AesCmacProtoKeySerializationRegistered) {
+  if (internal::IsFipsModeEnabled()) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
+  google::crypto::tink::AesCmacKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(subtle::Random::GetRandomBytes(32));
+  key_proto.mutable_params()->set_tag_size(16);
+
+  util::StatusOr<internal::ProtoKeySerialization> proto_key_serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.AesCmacKey",
+          RestrictedData(key_proto.SerializeAsString(),
+                         InsecureSecretKeyAccess::Get()),
+          KeyData::SYMMETRIC, OutputPrefixType::TINK, /*id_requirement=*/123);
+  ASSERT_THAT(proto_key_serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> parsed_key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *proto_key_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_key.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  util::StatusOr<AesCmacParameters> params = AesCmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/16,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(params, IsOk());
+
+  util::StatusOr<AesCmacKey> key =
+      AesCmacKey::Create(*params,
+                         RestrictedData(subtle::Random::GetRandomBytes(32),
+                                        InsecureSecretKeyAccess::Get()),
+                         /*id_requirement=*/123, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_key =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialized_key.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  ASSERT_THAT(MacConfig::Register(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> parsed_key2 =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *proto_key_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_key2, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_key2 =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialized_key2, IsOk());
+}
+
+TEST_F(MacConfigTest, HmacProtoParamsSerializationRegistered) {
+  if (internal::IsFipsModeEnabled()) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
+  util::StatusOr<internal::ProtoParametersSerialization>
+      proto_params_serialization =
+          internal::ProtoParametersSerialization::Create(
+              MacKeyTemplates::HmacSha256());
+  ASSERT_THAT(proto_params_serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> parsed_params =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *proto_params_serialization);
+  ASSERT_THAT(parsed_params.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/32,
+      HmacParameters::HashType::kSha256, HmacParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_parameters =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  ASSERT_THAT(serialized_parameters.status(),
+              StatusIs(absl::StatusCode::kNotFound));
+
+  // Register parser and serializer.
+  ASSERT_THAT(MacConfig::Register(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Parameters>> parsed_params2 =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseParameters(
+          *proto_params_serialization);
+  ASSERT_THAT(parsed_params2, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_params2 =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeParameters<internal::ProtoParametersSerialization>(
+              *parameters);
+  ASSERT_THAT(serialized_params2, IsOk());
+}
+
+TEST_F(MacConfigTest, HmacProtoKeySerializationRegistered) {
+  if (internal::IsFipsModeEnabled()) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
+  google::crypto::tink::HmacKey key_proto;
+  key_proto.set_version(0);
+  key_proto.set_key_value(subtle::Random::GetRandomBytes(32));
+  key_proto.mutable_params()->set_tag_size(32);
+  key_proto.mutable_params()->set_hash(HashType::SHA256);
+
+  util::StatusOr<internal::ProtoKeySerialization> proto_key_serialization =
+      internal::ProtoKeySerialization::Create(
+          "type.googleapis.com/google.crypto.tink.HmacKey",
+          RestrictedData(key_proto.SerializeAsString(),
+                         InsecureSecretKeyAccess::Get()),
+          KeyData::SYMMETRIC, OutputPrefixType::TINK, /*id_requirement=*/123);
+  ASSERT_THAT(proto_key_serialization, IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> parsed_key =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *proto_key_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_key.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  util::StatusOr<HmacParameters> parameters = HmacParameters::Create(
+      /*key_size_in_bytes=*/32, /*cryptographic_tag_size_in_bytes=*/32,
+      HmacParameters::HashType::kSha256, HmacParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<HmacKey> key =
+      HmacKey::Create(*parameters,
+                      RestrictedData(subtle::Random::GetRandomBytes(32),
+                                     InsecureSecretKeyAccess::Get()),
+                      /*id_requirement=*/123, GetPartialKeyAccess());
+  ASSERT_THAT(key, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_key =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialized_key.status(), StatusIs(absl::StatusCode::kNotFound));
+
+  // Register parser and serializer.
+  ASSERT_THAT(MacConfig::Register(), IsOk());
+
+  util::StatusOr<std::unique_ptr<Key>> parsed_key2 =
+      internal::MutableSerializationRegistry::GlobalInstance().ParseKey(
+          *proto_key_serialization, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_key2, IsOk());
+
+  util::StatusOr<std::unique_ptr<Serialization>> serialized_key2 =
+      internal::MutableSerializationRegistry::GlobalInstance()
+          .SerializeKey<internal::ProtoKeySerialization>(
+              *key, InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialized_key2, IsOk());
+}
+
 class ChunkedMacConfigTest : public ::testing::TestWithParam<KeyTemplate> {
  protected:
   void SetUp() override { Registry::Reset(); }
@@ -140,7 +348,7 @@
 // Tests that the ChunkedMacWrapper has been properly registered and we can get
 // primitives.
 TEST_P(ChunkedMacConfigTest, ChunkedMacWrappersRegistered) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
@@ -172,7 +380,7 @@
 
 // FIPS-only mode tests
 TEST_F(MacConfigTest, RegisterNonFipsTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode";
   }
 
@@ -188,7 +396,7 @@
 }
 
 TEST_F(MacConfigTest, RegisterFipsValidTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode";
   }
 
diff --git a/cc/mac/mac_factory.cc b/cc/mac/mac_factory.cc
index 954ac82..6f4a420 100644
--- a/cc/mac/mac_factory.cc
+++ b/cc/mac/mac_factory.cc
@@ -16,13 +16,14 @@
 
 #include "tink/mac/mac_factory.h"
 
+#include <memory>
+
 #include "tink/mac.h"
-#include "tink/registry.h"
 #include "tink/mac/mac_wrapper.h"
+#include "tink/registry.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
-
 namespace crypto {
 namespace tink {
 
diff --git a/cc/mac/mac_factory.h b/cc/mac/mac_factory.h
index 1fc427b..bf30bcb 100644
--- a/cc/mac/mac_factory.h
+++ b/cc/mac/mac_factory.h
@@ -17,6 +17,8 @@
 #ifndef TINK_MAC_MAC_FACTORY_H_
 #define TINK_MAC_MAC_FACTORY_H_
 
+#include <memory>
+
 #include "absl/base/macros.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
diff --git a/cc/mac/mac_factory_test.cc b/cc/mac/mac_factory_test.cc
index 7e70f5c..27b5d6c 100644
--- a/cc/mac/mac_factory_test.cc
+++ b/cc/mac/mac_factory_test.cc
@@ -33,7 +33,6 @@
 #include "proto/hmac.pb.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::TestKeysetHandle;
 using crypto::tink::test::AddRawKey;
 using crypto::tink::test::AddTinkKey;
 using google::crypto::tink::HashType;
diff --git a/cc/mac/mac_key.h b/cc/mac/mac_key.h
index 5e14e5f..d02933c 100644
--- a/cc/mac/mac_key.h
+++ b/cc/mac/mac_key.h
@@ -40,7 +40,7 @@
   // may be a prefix of another). To avoid this, built-in Tink keys use the
   // convention that the prefix is either '0x00<big endian key id>' or
   // '0x01<big endian key id>'.
-  virtual util::StatusOr<std::string> GetOutputPrefix() const = 0;
+  virtual absl::string_view GetOutputPrefix() const = 0;
 
   const MacParameters& GetParameters() const override = 0;
 
diff --git a/cc/mac/mac_wrapper.cc b/cc/mac/mac_wrapper.cc
index f1140bd..039b332 100644
--- a/cc/mac/mac_wrapper.cc
+++ b/cc/mac/mac_wrapper.cc
@@ -16,18 +16,19 @@
 
 #include "tink/mac/mac_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "tink/crypto_format.h"
-#include "tink/internal/util.h"
-#include "tink/mac.h"
-#include "tink/primitive_set.h"
 #include "tink/internal/monitoring_util.h"
 #include "tink/internal/registry_impl.h"
+#include "tink/internal/util.h"
+#include "tink/mac.h"
 #include "tink/monitoring/monitoring.h"
+#include "tink/primitive_set.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
@@ -59,7 +60,7 @@
   crypto::tink::util::Status VerifyMac(absl::string_view mac_value,
                                        absl::string_view data) const override;
 
-  ~MacSetWrapper() override {}
+  ~MacSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<Mac>> mac_set_;
diff --git a/cc/mac/mac_wrapper.h b/cc/mac/mac_wrapper.h
index c7379dc..3876544 100644
--- a/cc/mac/mac_wrapper.h
+++ b/cc/mac/mac_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_MAC_MAC_WRAPPER_H_
 #define TINK_MAC_MAC_WRAPPER_H_
 
+#include <memory>
+
 #include "absl/strings/string_view.h"
 #include "tink/mac.h"
 #include "tink/primitive_set.h"
diff --git a/cc/mac/mac_wrapper_test.cc b/cc/mac/mac_wrapper_test.cc
index 66069f1..d240e50 100644
--- a/cc/mac/mac_wrapper_test.cc
+++ b/cc/mac/mac_wrapper_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/mac/mac_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/monitoring/BUILD.bazel b/cc/monitoring/BUILD.bazel
index 22d3886..63fc32f 100644
--- a/cc/monitoring/BUILD.bazel
+++ b/cc/monitoring/BUILD.bazel
@@ -7,6 +7,8 @@
     hdrs = ["monitoring.h"],
     include_prefix = "tink/monitoring",
     deps = [
+        "//:key_status",
+        "//internal:key_status_util",
         "//util:statusor",
         "@com_google_absl//absl/container:flat_hash_map",
     ],
diff --git a/cc/monitoring/BUILD.gn b/cc/monitoring/BUILD.gn
index b7ce0fc..eae3e72 100644
--- a/cc/monitoring/BUILD.gn
+++ b/cc/monitoring/BUILD.gn
@@ -12,6 +12,8 @@
   sources = [ "monitoring.h" ]
   public_deps = [
     "//third_party/abseil-cpp/absl/container:flat_hash_map",
+    "//third_party/tink/cc:key_status",
+    "//third_party/tink/cc/internal:key_status_util",
     "//third_party/tink/cc/util:statusor",
   ]
   public_configs = [ "//third_party/tink:tink_config" ]
diff --git a/cc/monitoring/CMakeLists.txt b/cc/monitoring/CMakeLists.txt
index 9613885..60ad02c 100644
--- a/cc/monitoring/CMakeLists.txt
+++ b/cc/monitoring/CMakeLists.txt
@@ -6,6 +6,8 @@
     monitoring.h
   DEPS
     absl::flat_hash_map
+    tink::core::key_status
+    tink::internal::key_status_util
     tink::util::statusor
 )
 
@@ -16,4 +18,5 @@
   DEPS
     tink::monitoring::monitoring
     gmock
+  TESTONLY
 )
diff --git a/cc/monitoring/monitoring.h b/cc/monitoring/monitoring.h
index e25e99a..4c4dcc5 100644
--- a/cc/monitoring/monitoring.h
+++ b/cc/monitoring/monitoring.h
@@ -17,10 +17,13 @@
 #define TINK_MONITORING_MONITORING_H_
 
 #include <cstdint>
+#include <memory>
 #include <string>
 #include <vector>
 
 #include "absl/container/flat_hash_map.h"
+#include "tink/internal/key_status_util.h"
+#include "tink/key_status.h"
 #include "tink/util/statusor.h"
 
 namespace crypto {
@@ -33,41 +36,32 @@
   // Description about each entry of the KeySet.
   class Entry {
    public:
-    // Enum representation of KeyStatusType in tink/proto/tink.proto. Using an
-    // enum class prevents unintentional implicit conversions.
-    enum class KeyStatus : int {
-      kEnabled = 1,    // Can be used for cryptographic operations.
-      kDisabled = 2,   // Cannot be used (but can become kEnabled again).
-      kDestroyed = 3,  // Key data does not exist in this Keyset any more.
-      // Added to guard from failures that may be caused by future expansions.
-      kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
-    };
-
-    // Constructs a new KeySet entry with a given `status`, `key_id` and key
-    // format `parameters_as_string`.
-    Entry(KeyStatus status, uint32_t key_id,
-          absl::string_view parameters_as_string)
+    // Constructs a new KeySet entry with a given `status`, `key_id`,
+    // `key_type`, and `key_prefix`.
+    Entry(KeyStatus status, uint32_t key_id, absl::string_view key_type,
+          absl::string_view key_prefix)
         : status_(status),
           key_id_(key_id),
-          parameters_as_string_(parameters_as_string) {}
+          key_type_(key_type),
+          key_prefix_(key_prefix) {}
 
     // Returns the status of this entry.
-    KeyStatus GetStatus() const { return status_; }
+    std::string GetStatus() const { return internal::ToKeyStatusName(status_); }
     // Returns the ID of the entry within the keyset.
     uint32_t GetKeyId() const { return key_id_; }
-    // Returns the parameters in a serialized form.
-    //
-    // *WARNING* the actual content of `parameters_as_string_` is considered
-    // unstable and might change in future versions of Tink. A user should not
-    // rely on a specific representation of the key_format.
-    std::string GetParametersAsString() const { return parameters_as_string_; }
+    // Returns the key type.
+    std::string GetKeyType() const { return key_type_; }
+    // Returns the key prefix.
+    std::string GetKeyPrefix() const { return key_prefix_; }
 
    private:
     const KeyStatus status_;
     // Identifies a key within a keyset.
     const uint32_t key_id_;
-    // This field stores information about the parameters.
-    const std::string parameters_as_string_;
+    // This field stores the key type.
+    const std::string key_type_;
+    // Stores the key output prefix.
+    const std::string key_prefix_;
   };
 
   // Constructs a MonitoringKeySetInfo object with the given
diff --git a/cc/monitoring/monitoring_client_mocks.h b/cc/monitoring/monitoring_client_mocks.h
index 8a59b47..34b5cbd 100644
--- a/cc/monitoring/monitoring_client_mocks.h
+++ b/cc/monitoring/monitoring_client_mocks.h
@@ -13,8 +13,8 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
-#ifndef TINK_MONITORING_MOCK_MONITORING_CLIENT_FACTORY_H_
-#define TINK_MONITORING_MOCK_MONITORING_CLIENT_FACTORY_H_
+#ifndef TINK_MONITORING_MONITORING_CLIENT_MOCKS_H_
+#define TINK_MONITORING_MONITORING_CLIENT_MOCKS_H_
 
 #include <cstdint>
 
@@ -42,4 +42,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_MONITORING_MOCK_MONITORING_CLIENT_FACTORY_H_
+#endif  // TINK_MONITORING_MONITORING_CLIENT_MOCKS_H_
diff --git a/cc/output_stream.h b/cc/output_stream.h
index c7de9b3..969e153 100644
--- a/cc/output_stream.h
+++ b/cc/output_stream.h
@@ -27,8 +27,8 @@
 // Protocol Buffers' google::protobuf::io::ZeroCopyOutputStream.
 class OutputStream {
  public:
-  OutputStream() {}
-  virtual ~OutputStream() {}
+  OutputStream() = default;
+  virtual ~OutputStream() = default;
 
   // Obtains a buffer into which data can be written.  Any data written
   // into this buffer will eventually (maybe instantly, maybe later on)
diff --git a/cc/output_stream_with_result.h b/cc/output_stream_with_result.h
index 882a075..ee7759f 100644
--- a/cc/output_stream_with_result.h
+++ b/cc/output_stream_with_result.h
@@ -59,7 +59,7 @@
 class OutputStreamWithResult : public OutputStream {
  public:
   OutputStreamWithResult() : closed_(false) {}
-  ~OutputStreamWithResult() override {}
+  ~OutputStreamWithResult() override = default;
 
   // The return type is StatusOr<T> if T != Status, and Status otherwise.
   using ResultType =
diff --git a/cc/partial_key_access.h b/cc/partial_key_access.h
new file mode 100644
index 0000000..f029faf
--- /dev/null
+++ b/cc/partial_key_access.h
@@ -0,0 +1,41 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_PARTIAL_KEY_ACCESS_H_
+#define TINK_PARTIAL_KEY_ACCESS_H_
+
+#include "tink/partial_key_access_token.h"
+
+namespace crypto {
+namespace tink {
+
+// Returns a `PartialKeyAccessToken`.
+//
+// Accessing parts of keys can produce unexpected incompatibilities:
+// https://developers.google.com/tink/design/access_control#accessing_partial_keys
+//
+// This function can be used to access partial key material. Within Google,
+// access to this function is restricted by the build system. Outside of Google,
+// users can search their codebase for `GetPartialKeyAccess()` to find
+// instances where it is used.
+inline PartialKeyAccessToken GetPartialKeyAccess() {
+  return PartialKeyAccessToken();
+}
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_PARTIAL_KEY_ACCESS_H_
diff --git a/cc/partial_key_access_token.h b/cc/partial_key_access_token.h
new file mode 100644
index 0000000..fe5a180
--- /dev/null
+++ b/cc/partial_key_access_token.h
@@ -0,0 +1,42 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_PARTIAL_KEY_ACCESS_TOKEN_H_
+#define TINK_PARTIAL_KEY_ACCESS_TOKEN_H_
+
+namespace crypto {
+namespace tink {
+
+class PartialKeyAccessToken {
+ public:
+  // Copyable and movable.
+  PartialKeyAccessToken(const PartialKeyAccessToken& other) = default;
+  PartialKeyAccessToken& operator=(const PartialKeyAccessToken& other) =
+      default;
+  PartialKeyAccessToken(PartialKeyAccessToken&& other) = default;
+  PartialKeyAccessToken& operator=(PartialKeyAccessToken&& other) = default;
+
+ private:
+  // `GetPartialKeyAccess()` requires access to the constructor.
+  friend PartialKeyAccessToken GetPartialKeyAccess();
+
+  PartialKeyAccessToken() = default;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_PARTIAL_KEY_ACCESS_TOKEN_H_
diff --git a/cc/prf/BUILD.bazel b/cc/prf/BUILD.bazel
index cf4d7b6..40eb6ab 100644
--- a/cc/prf/BUILD.bazel
+++ b/cc/prf/BUILD.bazel
@@ -90,11 +90,15 @@
         ":prf_set",
         "//:primitive_set",
         "//:primitive_wrapper",
+        "//internal:monitoring_util",
+        "//internal:registry_impl",
+        "//monitoring",
         "//proto:tink_cc_proto",
         "//util:status",
         "//util:statusor",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
     ],
 )
 
@@ -195,10 +199,14 @@
         ":prf_set",
         ":prf_set_wrapper",
         "//:primitive_set",
+        "//:registry",
+        "//monitoring:monitoring_client_mocks",
         "//proto:tink_cc_proto",
+        "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -265,9 +273,8 @@
         ":prf_config",
         ":prf_key_templates",
         ":prf_set",
-        "//:config",
         "//:tink_cc",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
diff --git a/cc/prf/CMakeLists.txt b/cc/prf/CMakeLists.txt
index 028587e..ebe61bc 100644
--- a/cc/prf/CMakeLists.txt
+++ b/cc/prf/CMakeLists.txt
@@ -79,8 +79,12 @@
     tink::prf::prf_set
     absl::memory
     absl::status
+    absl::statusor
     tink::core::primitive_set
     tink::core::primitive_wrapper
+    tink::internal::monitoring_util
+    tink::internal::registry_impl
+    tink::monitoring::monitoring
     tink::util::status
     tink::util::statusor
     tink::proto::tink_cc_proto
@@ -183,8 +187,12 @@
     tink::prf::prf_set_wrapper
     gmock
     absl::memory
+    absl::status
     absl::strings
     tink::core::primitive_set
+    tink::core::registry
+    tink::monitoring::monitoring_client_mocks
+    tink::util::status
     tink::util::statusor
     tink::util::test_matchers
     tink::proto::tink_cc_proto
@@ -255,8 +263,7 @@
     absl::status
     crypto
     tink::core::cc
-    tink::core::config
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::status
     tink::util::test_matchers
     tink::util::test_util
diff --git a/cc/prf/aes_cmac_prf_key_manager.h b/cc/prf/aes_cmac_prf_key_manager.h
index fdb5844..3d7c26a 100644
--- a/cc/prf/aes_cmac_prf_key_manager.h
+++ b/cc/prf/aes_cmac_prf_key_manager.h
@@ -17,6 +17,7 @@
 #define TINK_PRF_AES_CMAC_PRF_KEY_MANAGER_H_
 
 #include <algorithm>
+#include <memory>
 #include <string>
 #include <vector>
 
diff --git a/cc/prf/aes_cmac_prf_key_manager_test.cc b/cc/prf/aes_cmac_prf_key_manager_test.cc
index e08f861..7ca8c40 100644
--- a/cc/prf/aes_cmac_prf_key_manager_test.cc
+++ b/cc/prf/aes_cmac_prf_key_manager_test.cc
@@ -15,6 +15,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include "tink/prf/aes_cmac_prf_key_manager.h"
 
+#include <memory>
 #include <sstream>
 #include <string>
 
diff --git a/cc/prf/hkdf_prf_key_manager.h b/cc/prf/hkdf_prf_key_manager.h
index fe860a3..0140e4b 100644
--- a/cc/prf/hkdf_prf_key_manager.h
+++ b/cc/prf/hkdf_prf_key_manager.h
@@ -17,6 +17,7 @@
 #ifndef TINK_PRF_HKDF_PRF_KEY_MANAGER_H_
 #define TINK_PRF_HKDF_PRF_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/prf/hkdf_prf_key_manager_test.cc b/cc/prf/hkdf_prf_key_manager_test.cc
index d985694..155cd5c 100644
--- a/cc/prf/hkdf_prf_key_manager_test.cc
+++ b/cc/prf/hkdf_prf_key_manager_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/prf/hkdf_prf_key_manager.h"
 
+#include <memory>
+#include <sstream>
 #include <string>
 #include <utility>
 
diff --git a/cc/prf/hmac_prf_key_manager.h b/cc/prf/hmac_prf_key_manager.h
index 4074cfc..89113d0 100644
--- a/cc/prf/hmac_prf_key_manager.h
+++ b/cc/prf/hmac_prf_key_manager.h
@@ -17,6 +17,7 @@
 #define TINK_PRF_HMAC_PRF_KEY_MANAGER_H_
 
 #include <algorithm>
+#include <map>
 #include <memory>
 #include <string>
 #include <vector>
diff --git a/cc/prf/hmac_prf_key_manager_test.cc b/cc/prf/hmac_prf_key_manager_test.cc
index d1453f0..e5ad56d 100644
--- a/cc/prf/hmac_prf_key_manager_test.cc
+++ b/cc/prf/hmac_prf_key_manager_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/prf/hmac_prf_key_manager.h"
 
+#include <sstream>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
@@ -39,7 +41,6 @@
 using ::google::crypto::tink::HashType;
 using ::google::crypto::tink::HmacPrfKey;
 using ::google::crypto::tink::HmacPrfKeyFormat;
-using ::google::crypto::tink::KeyData;
 using ::testing::HasSubstr;
 using ::testing::Not;
 using ::testing::SizeIs;
diff --git a/cc/prf/prf_config_test.cc b/cc/prf/prf_config_test.cc
index eb685de..f86df0b 100644
--- a/cc/prf/prf_config_test.cc
+++ b/cc/prf/prf_config_test.cc
@@ -20,8 +20,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
-#include "tink/config.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/keyset_handle.h"
 #include "tink/prf/hmac_prf_key_manager.h"
 #include "tink/prf/prf_key_templates.h"
@@ -44,7 +43,7 @@
 };
 
 TEST_F(PrfConfigTest, RegisterWorks) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
   }
 
@@ -59,7 +58,7 @@
 
 // FIPS-only mode tests
 TEST_F(PrfConfigTest, RegisterNonFipsTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode";
   }
 
@@ -77,7 +76,7 @@
 }
 
 TEST_F(PrfConfigTest, RegisterFipsValidTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode";
   }
 
diff --git a/cc/prf/prf_key_templates.cc b/cc/prf/prf_key_templates.cc
index 92c0378..fb8003f 100644
--- a/cc/prf/prf_key_templates.cc
+++ b/cc/prf/prf_key_templates.cc
@@ -15,6 +15,8 @@
 ////////////////////////////////////////////////////////////////////////////////
 #include "tink/prf/prf_key_templates.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "tink/prf/aes_cmac_prf_key_manager.h"
 #include "tink/prf/hkdf_prf_key_manager.h"
diff --git a/cc/prf/prf_set.h b/cc/prf/prf_set.h
index 51c0636..ddf630b 100644
--- a/cc/prf/prf_set.h
+++ b/cc/prf/prf_set.h
@@ -46,7 +46,7 @@
 // for non-deterministic MAC algorithms.
 class Prf {
  public:
-  virtual ~Prf() {}
+  virtual ~Prf() = default;
   // Computes the PRF selected by the underlying key on input and
   // returns the first outputLength bytes.
   // When choosing this parameter keep the birthday paradox in mind.
@@ -67,7 +67,7 @@
 // the Keyset.
 class PrfSet {
  public:
-  virtual ~PrfSet() {}
+  virtual ~PrfSet() = default;
   // The primary ID of the keyset.
   virtual uint32_t GetPrimaryId() const = 0;
   // A map of the PRFs represented by the keys in this keyset.
diff --git a/cc/prf/prf_set_test.cc b/cc/prf/prf_set_test.cc
index f76ed88..00e5038 100644
--- a/cc/prf/prf_set_test.cc
+++ b/cc/prf/prf_set_test.cc
@@ -17,8 +17,10 @@
 #include "tink/prf/prf_set.h"
 
 #include <map>
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/prf/prf_set_wrapper.cc b/cc/prf/prf_set_wrapper.cc
index f244a27..af81b56 100644
--- a/cc/prf/prf_set_wrapper.cc
+++ b/cc/prf/prf_set_wrapper.cc
@@ -15,10 +15,20 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/prf/prf_set_wrapper.h"
 
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "tink/internal/monitoring_util.h"
+#include "tink/internal/registry_impl.h"
+#include "tink/monitoring/monitoring.h"
+#include "tink/prf/prf_set.h"
 #include "tink/util/status.h"
 #include "proto/tink.pb.h"
 
@@ -29,12 +39,59 @@
 
 namespace {
 
+constexpr absl::string_view kPrimitive = "prf";
+constexpr absl::string_view kComputeApi = "compute";
+
+class MonitoredPrf : public Prf {
+ public:
+  explicit MonitoredPrf(uint32_t key_id, const Prf* prf,
+                        MonitoringClient* monitoring_client)
+      : key_id_(key_id), prf_(prf), monitoring_client_(monitoring_client) {}
+  ~MonitoredPrf() override = default;
+
+  MonitoredPrf(MonitoredPrf&& other) = default;
+  MonitoredPrf& operator=(MonitoredPrf&& other) = default;
+
+  MonitoredPrf(const MonitoredPrf&) = delete;
+  MonitoredPrf& operator=(const MonitoredPrf&) = delete;
+
+  util::StatusOr<std::string> Compute(absl::string_view input,
+                                      size_t output_length) const override {
+    util::StatusOr<std::string> result = prf_->Compute(input, output_length);
+    if (!result.ok()) {
+      if (monitoring_client_ != nullptr) {
+        monitoring_client_->LogFailure();
+      }
+      return result.status();
+    }
+
+    if (monitoring_client_ != nullptr) {
+      monitoring_client_->Log(key_id_, input.size());
+    }
+    return result.value();
+  }
+
+ private:
+  uint32_t key_id_;
+  const Prf* prf_;
+  MonitoringClient* monitoring_client_;
+};
+
 class PrfSetPrimitiveWrapper : public PrfSet {
  public:
-  explicit PrfSetPrimitiveWrapper(std::unique_ptr<PrimitiveSet<Prf>> prf_set)
-      : prf_set_(std::move(prf_set)) {
+  explicit PrfSetPrimitiveWrapper(
+      std::unique_ptr<PrimitiveSet<Prf>> prf_set,
+      std::unique_ptr<MonitoringClient> monitoring_client = nullptr)
+      : prf_set_(std::move(prf_set)),
+        monitoring_client_(std::move(monitoring_client)) {
+    wrapped_prfs_.reserve(prf_set_->get_raw_primitives().value()->size());
     for (const auto& prf : *prf_set_->get_raw_primitives().value()) {
-      prfs_.insert({prf->get_key_id(), &prf->get_primitive()});
+      std::unique_ptr<Prf> wrapped_prf = std::make_unique<MonitoredPrf>(
+                                  prf->get_key_id(), &prf->get_primitive(),
+                                  monitoring_client_.get());
+
+      prfs_.insert({prf->get_key_id(), wrapped_prf.get()});
+      wrapped_prfs_.push_back(std::move(wrapped_prf));
     }
   }
 
@@ -43,10 +100,12 @@
   }
   const std::map<uint32_t, Prf*>& GetPrfs() const override { return prfs_; }
 
-  ~PrfSetPrimitiveWrapper() override {}
+  ~PrfSetPrimitiveWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<Prf>> prf_set_;
+  std::unique_ptr<MonitoringClient> monitoring_client_;
+  std::vector<std::unique_ptr<Prf>> wrapped_prfs_;
   std::map<uint32_t, Prf*> prfs_;
 };
 
@@ -74,7 +133,26 @@
     std::unique_ptr<PrimitiveSet<Prf>> prf_set) const {
   util::Status status = Validate(prf_set.get());
   if (!status.ok()) return status;
-  return {absl::make_unique<PrfSetPrimitiveWrapper>(std::move(prf_set))};
+
+  MonitoringClientFactory* const monitoring_factory =
+      internal::RegistryImpl::GlobalInstance().GetMonitoringClientFactory();
+  // Monitoring is not enabled. Create a wrapper without monitoring clients.
+  if (monitoring_factory == nullptr) {
+    return {absl::make_unique<PrfSetPrimitiveWrapper>(std::move(prf_set))};
+  }
+  util::StatusOr<MonitoringKeySetInfo> keyset_info =
+      internal::MonitoringKeySetInfoFromPrimitiveSet(*prf_set);
+  if (!keyset_info.ok()) {
+    return keyset_info.status();
+  }
+  util::StatusOr<std::unique_ptr<MonitoringClient>> monitoring_client =
+      monitoring_factory->New(
+          MonitoringContext(kPrimitive, kComputeApi, *keyset_info));
+  if (!monitoring_client.ok()) {
+    return monitoring_client.status();
+  }
+  return {absl::make_unique<PrfSetPrimitiveWrapper>(
+      std::move(prf_set), *std::move(monitoring_client))};
 }
 
 }  // namespace tink
diff --git a/cc/prf/prf_set_wrapper_test.cc b/cc/prf/prf_set_wrapper_test.cc
index 095c672..b100de1 100644
--- a/cc/prf/prf_set_wrapper_test.cc
+++ b/cc/prf/prf_set_wrapper_test.cc
@@ -16,6 +16,7 @@
 #include "tink/prf/prf_set_wrapper.h"
 
 #include <cstdint>
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -23,9 +24,13 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
+#include "absl/status/status.h"
 #include "absl/strings/string_view.h"
+#include "tink/monitoring/monitoring_client_mocks.h"
 #include "tink/prf/prf_set.h"
 #include "tink/primitive_set.h"
+#include "tink/registry.h"
+#include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 #include "proto/tink.pb.h"
@@ -38,11 +43,24 @@
 using ::crypto::tink::test::IsOkAndHolds;
 using ::google::crypto::tink::KeysetInfo;
 using ::google::crypto::tink::KeyStatusType;
+using ::testing::_;
+using ::testing::ByMove;
 using ::testing::Key;
+using ::testing::NiceMock;
 using ::testing::Not;
+using ::testing::Return;
 using ::testing::StrEq;
+using ::testing::Test;
 using ::testing::UnorderedElementsAre;
 
+KeysetInfo::KeyInfo MakeKey(uint32_t id) {
+  KeysetInfo::KeyInfo key;
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::RAW);
+  key.set_key_id(id);
+  key.set_status(KeyStatusType::ENABLED);
+  return key;
+}
+
 class FakePrf : public Prf {
  public:
   explicit FakePrf(const std::string& output) : output_(output) {}
@@ -65,14 +83,6 @@
     return prf_set_->AddPrimitive(std::move(prf), key_info);
   }
 
-  KeysetInfo::KeyInfo MakeKey(uint32_t id) {
-    KeysetInfo::KeyInfo key;
-    key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::RAW);
-    key.set_key_id(id);
-    key.set_status(KeyStatusType::ENABLED);
-    return key;
-  }
-
   std::unique_ptr<PrimitiveSet<Prf>>& PrfSet() { return prf_set_; }
 
  private:
@@ -134,6 +144,104 @@
               IsOkAndHolds(StrEq("different")));
 }
 
+// Tests for the monitoring behavior.
+class PrfSetWrapperWithMonitoringTest : public Test {
+ protected:
+  // Reset the global registry.
+  void SetUp() override {
+    Registry::Reset();
+    // Setup mocks for catching Monitoring calls.
+    auto monitoring_client_factory =
+        absl::make_unique<MockMonitoringClientFactory>();
+    auto monitoring_client =
+        absl::make_unique<NiceMock<MockMonitoringClient>>();
+    monitoring_client_ref_ = monitoring_client.get();
+    // Monitoring tests expect that the client factory will create the
+    // corresponding MockMonitoringClients.
+    EXPECT_CALL(*monitoring_client_factory, New(_))
+        .WillOnce(
+            Return(ByMove(util::StatusOr<std::unique_ptr<MonitoringClient>>(
+                std::move(monitoring_client)))));
+
+    ASSERT_THAT(internal::RegistryImpl::GlobalInstance()
+                    .RegisterMonitoringClientFactory(
+                        std::move(monitoring_client_factory)),
+                IsOk());
+    ASSERT_THAT(
+        internal::RegistryImpl::GlobalInstance().GetMonitoringClientFactory(),
+        Not(testing::IsNull()));
+  }
+
+  // Cleanup the registry to avoid mock leaks.
+  ~PrfSetWrapperWithMonitoringTest() override { Registry::Reset(); }
+
+  NiceMock<MockMonitoringClient>* monitoring_client_ref_;
+};
+
+class AlwaysFailingPrf : public Prf {
+ public:
+  AlwaysFailingPrf() = default;
+
+  util::StatusOr<std::string> Compute(absl::string_view input,
+                                      size_t output_length) const override {
+    return util::Status(absl::StatusCode::kOutOfRange, "AlwaysFailingPrf");
+  }
+};
+
+TEST_F(PrfSetWrapperWithMonitoringTest, WrapKeysetWithMonitoringFailure) {
+  const absl::flat_hash_map<std::string, std::string> annotations = {
+      {"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
+  auto primitive_set = absl::make_unique<PrimitiveSet<Prf>>(annotations);
+  util::StatusOr<PrimitiveSet<Prf>::Entry<Prf>*> entry =
+      primitive_set->AddPrimitive(absl::make_unique<AlwaysFailingPrf>(),
+                                  MakeKey(/*id=*/1));
+  ASSERT_THAT(entry, IsOk());
+  ASSERT_THAT(primitive_set->set_primary(entry.value()), IsOk());
+  ASSERT_THAT(primitive_set
+                  ->AddPrimitive(absl::make_unique<FakePrf>("output"),
+                                 MakeKey(/*id=*/1))
+                  .status(),
+              IsOk());
+  util::StatusOr<std::unique_ptr<PrfSet>> prf_set =
+      PrfSetWrapper().Wrap(std::move(primitive_set));
+  ASSERT_THAT(prf_set, IsOk());
+  EXPECT_CALL(*monitoring_client_ref_, LogFailure());
+  EXPECT_THAT((*prf_set)->ComputePrimary("input", /*output_length=*/16),
+              Not(IsOk()));
+}
+
+TEST_F(PrfSetWrapperWithMonitoringTest, WrapKeysetWithMonitoringVerifySuccess) {
+  const absl::flat_hash_map<std::string, std::string> annotations = {
+      {"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
+  auto primitive_set = absl::make_unique<PrimitiveSet<Prf>>(annotations);
+
+  util::StatusOr<PrimitiveSet<Prf>::Entry<Prf>*> entry =
+      primitive_set->AddPrimitive(absl::make_unique<FakePrf>("output"),
+                                  MakeKey(/*id=*/1));
+  ASSERT_THAT(entry, IsOk());
+  ASSERT_THAT(primitive_set->set_primary(entry.value()), IsOk());
+  ASSERT_THAT(primitive_set
+                  ->AddPrimitive(absl::make_unique<FakePrf>("output"),
+                                 MakeKey(/*id=*/1))
+                  .status(),
+              IsOk());
+
+  util::StatusOr<std::unique_ptr<PrfSet>> prf_set =
+      PrfSetWrapper().Wrap(std::move(primitive_set));
+  ASSERT_THAT(prf_set, IsOk());
+  std::map<uint32_t, Prf*> prf_map = (*prf_set)->GetPrfs();
+  std::string input = "input";
+  for (const auto& entry : prf_map) {
+    EXPECT_CALL(*monitoring_client_ref_, Log(entry.first, input.size()));
+    EXPECT_THAT((entry.second)->Compute(input, /*output_length=*/16).status(),
+                IsOk());
+  }
+  input = "hello_world";
+  EXPECT_CALL(*monitoring_client_ref_,
+              Log((*prf_set)->GetPrimaryId(), input.size()));
+  EXPECT_THAT((*prf_set)->ComputePrimary(input, /*output_length=*/16), IsOk());
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/primitive_set.h b/cc/primitive_set.h
index 76ae5ce..9f6439c 100644
--- a/cc/primitive_set.h
+++ b/cc/primitive_set.h
@@ -17,10 +17,13 @@
 #ifndef TINK_PRIMITIVE_SET_H_
 #define TINK_PRIMITIVE_SET_H_
 
+#include <algorithm>
+#include <memory>
 #include <string>
-#include <unordered_map>
+#include <utility>
 #include <vector>
 
+#include "absl/base/thread_annotations.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
@@ -113,48 +116,16 @@
   };
 
   typedef std::vector<std::unique_ptr<Entry<P>>> Primitives;
+  typedef absl::flat_hash_map<std::string, Primitives>
+      CiphertextPrefixToPrimitivesMap;
 
-  // Constructs an empty PrimitiveSet.
-  // Note: This is equivalent to PrimitiveSet<P>(/*annotations=*/{}).
-  PrimitiveSet<P>() = default;
-  // Constructs an empty PrimitiveSet with `annotations`.
-  explicit PrimitiveSet<P>(
-      const absl::flat_hash_map<std::string, std::string>& annotations)
-      : annotations_(annotations) {}
+ private:
+  // Helper methods for mutations, used by the Builder and the deprecated
+  // mutation methods on PrimitiveSet.
 
-  // Adds 'primitive' to this set for the specified 'key'.
-  crypto::tink::util::StatusOr<Entry<P>*> AddPrimitive(
-      std::unique_ptr<P> primitive,
-      const google::crypto::tink::KeysetInfo::KeyInfo& key_info) {
-    auto entry_or = Entry<P>::New(std::move(primitive), key_info);
-    if (!entry_or.ok()) return entry_or.status();
-
-    absl::MutexLock lock(&primitives_mutex_);
-    std::string identifier = entry_or.value()->get_identifier();
-    primitives_[identifier].push_back(std::move(entry_or.value()));
-    return primitives_[identifier].back().get();
-  }
-
-  // Returns the entries with primitives identifed by 'identifier'.
-  crypto::tink::util::StatusOr<const Primitives*> get_primitives(
-      absl::string_view identifier) {
-    absl::MutexLock lock(&primitives_mutex_);
-    typename CiphertextPrefixToPrimitivesMap::iterator found =
-        primitives_.find(std::string(identifier));
-    if (found == primitives_.end()) {
-      return ToStatusF(absl::StatusCode::kNotFound,
-                       "No primitives found for identifier '%s'.", identifier);
-    }
-    return &(found->second);
-  }
-
-  // Returns all primitives that use RAW prefix.
-  crypto::tink::util::StatusOr<const Primitives*> get_raw_primitives() {
-    return get_primitives(CryptoFormat::kRawPrefix);
-  }
-
-  // Sets the given 'primary' as the primary primitive of this set.
-  crypto::tink::util::Status set_primary(Entry<P>* primary) {
+  static crypto::tink::util::Status SetPrimaryImpl(
+      Entry<P>** output, Entry<P>* primary,
+      const CiphertextPrefixToPrimitivesMap& primitives) {
     if (!primary) {
       return util::Status(absl::StatusCode::kInvalidArgument,
                           "The primary primitive must be non-null.");
@@ -163,23 +134,190 @@
       return util::Status(absl::StatusCode::kInvalidArgument,
                           "Primary has to be enabled.");
     }
-    auto entries_result = get_primitives(primary->get_identifier());
-    if (!entries_result.ok()) {
+
+    if (primitives.count(primary->get_identifier()) == 0) {
       return util::Status(absl::StatusCode::kInvalidArgument,
                           "Primary cannot be set to an entry which is "
                           "not held by this primitive set.");
     }
 
-    primary_ = primary;
+    *output = primary;
     return crypto::tink::util::OkStatus();
   }
 
-  // Returns the entry with the primary primitive.
-  const Entry<P>* get_primary() const { return primary_; }
+  static crypto::tink::util::StatusOr<Entry<P>*> AddPrimitiveImpl(
+      std::unique_ptr<P> primitive,
+      const google::crypto::tink::KeysetInfo::KeyInfo& key_info,
+      CiphertextPrefixToPrimitivesMap& primitives,
+      std::vector<Entry<P>*>& primitives_in_keyset_order) {
+    auto entry_or = Entry<P>::New(std::move(primitive), key_info);
+    if (!entry_or.ok()) return entry_or.status();
 
-  // Returns all entries currently in this primitive set.
-  const std::vector<Entry<P>*> get_all() const {
-    absl::MutexLock lock(&primitives_mutex_);
+    std::string identifier = entry_or.value()->get_identifier();
+    primitives[identifier].push_back(std::move(entry_or.value()));
+
+    Entry<P>* stored_entry = primitives[identifier].back().get();
+    primitives_in_keyset_order.push_back(stored_entry);
+    return stored_entry;
+  }
+
+ public:
+  // Builder is used to construct PrimitiveSet objects. Objects returned by
+  // the builder are immutable. Calling any of the non-const methods on them
+  // will fail.
+  class Builder {
+   public:
+    // Adds 'primitive' to this set for the specified 'key'.
+    Builder& AddPrimitive(
+        std::unique_ptr<P> primitive,
+        const google::crypto::tink::KeysetInfo::KeyInfo& key_info) & {
+      absl::MutexLock lock(&mutex_);
+      if (!status_.ok()) return *this;
+      status_ = AddPrimitiveImpl(std::move(primitive), key_info, primitives_,
+                                 primitives_in_keyset_order_)
+                    .status();
+      return *this;
+    }
+
+    Builder&& AddPrimitive(
+        std::unique_ptr<P> primitive,
+        const google::crypto::tink::KeysetInfo::KeyInfo& key_info) && {
+      return std::move(AddPrimitive(std::move(primitive), key_info));
+    }
+
+    // Adds 'primitive' to this set for the specified 'key' and marks it
+    // primary.
+    Builder& AddPrimaryPrimitive(
+        std::unique_ptr<P> primitive,
+        const google::crypto::tink::KeysetInfo::KeyInfo& key_info) & {
+      absl::MutexLock lock(&mutex_);
+      if (!status_.ok()) return *this;
+      auto entry_result =
+          AddPrimitiveImpl(std::move(primitive), key_info, primitives_,
+                           primitives_in_keyset_order_);
+      if (!entry_result.ok()) {
+        status_ = entry_result.status();
+        return *this;
+      }
+      status_ = SetPrimaryImpl(&primary_, entry_result.value(), primitives_);
+      return *this;
+    }
+
+    Builder&& AddPrimaryPrimitive(
+        std::unique_ptr<P> primitive,
+        const google::crypto::tink::KeysetInfo::KeyInfo& key_info) && {
+      return std::move(AddPrimaryPrimitive(std::move(primitive), key_info));
+    }
+
+    // Add the given annotations. Existing annotations will not be overwritten.
+    Builder& AddAnnotations(
+        absl::flat_hash_map<std::string, std::string> annotations) & {
+      absl::MutexLock lock(&mutex_);
+      annotations_.merge(std::move(annotations));
+      return *this;
+    }
+
+    Builder&& AddAnnotations(
+        absl::flat_hash_map<std::string, std::string> annotations) && {
+      return std::move(AddAnnotations(std::move(annotations)));
+    }
+
+    crypto::tink::util::StatusOr<PrimitiveSet<P>> Build() && {
+      absl::MutexLock lock(&mutex_);
+      if (!status_.ok()) return status_;
+      return PrimitiveSet<P>(std::move(primitives_), primary_,
+                             std::move(primitives_in_keyset_order_),
+                             std::move(annotations_));
+    }
+
+   private:
+    // Owned by primitives_.
+    Entry<P>* primary_ ABSL_GUARDED_BY(mutex_) = nullptr;
+    CiphertextPrefixToPrimitivesMap primitives_ ABSL_GUARDED_BY(mutex_);
+    // Entries in the original keyset key order, all owned by primitives_.
+    std::vector<Entry<P>*> primitives_in_keyset_order_ ABSL_GUARDED_BY(mutex_);
+    absl::flat_hash_map<std::string, std::string> annotations_
+        ABSL_GUARDED_BY(mutex_);
+    absl::Mutex mutex_;
+    crypto::tink::util::Status status_ ABSL_GUARDED_BY(mutex_);
+  };
+
+  // PrimitiveSet is movable, but not copyable
+  PrimitiveSet(PrimitiveSet&&) = default;
+  PrimitiveSet<P>& operator=(PrimitiveSet&&) = default;
+  PrimitiveSet(const PrimitiveSet&) = delete;
+  PrimitiveSet<P>& operator=(const PrimitiveSet&) = delete;
+
+  // Constructs an empty PrimitiveSet.
+  // Note: This is equivalent to PrimitiveSet<P>(/*annotations=*/{}).
+  ABSL_DEPRECATED(
+      "Constructing PrimitiveSet using constructors is deprecated. Use "
+      "PrimitiveSet<>::Builder instead.")
+  PrimitiveSet<P>() = default;
+  // Constructs an empty PrimitiveSet with `annotations`.
+  ABSL_DEPRECATED(
+      "Constructing PrimitiveSet using constructors is deprecated. Use "
+      "PrimitiveSet<>::Builder instead.")
+  explicit PrimitiveSet<P>(
+      const absl::flat_hash_map<std::string, std::string>& annotations)
+      : annotations_(annotations) {}
+
+  // Adds 'primitive' to this set for the specified 'key'.
+  ABSL_DEPRECATED(
+      "Mutating PrimitiveSets after construction is deprecated. Use "
+      "PrimitiveSet<>::Builder instead.")
+  crypto::tink::util::StatusOr<Entry<P>*> AddPrimitive(
+      std::unique_ptr<P> primitive,
+      const google::crypto::tink::KeysetInfo::KeyInfo& key_info) {
+    if (!is_mutable()) {
+      return util::Status(absl::StatusCode::kFailedPrecondition,
+                          "PrimitiveSet is not mutable.");
+    }
+
+    absl::MutexLock lock(primitives_mutex_.get());
+    return AddPrimitiveImpl(std::move(primitive), key_info, primitives_,
+                            primitives_in_keyset_order_);
+  }
+
+  // Returns the entries with primitives identified by 'identifier'.
+  crypto::tink::util::StatusOr<const Primitives*> get_primitives(
+      absl::string_view identifier) const {
+    absl::MutexLockMaybe lock(primitives_mutex_.get());
+    auto found = primitives_.find(std::string(identifier));
+    if (found == primitives_.end()) {
+      return ToStatusF(absl::StatusCode::kNotFound,
+                       "No primitives found for identifier '%s'.", identifier);
+    }
+    return &(found->second);
+  }
+
+  // Returns all primitives that use RAW prefix.
+  crypto::tink::util::StatusOr<const Primitives*> get_raw_primitives() const {
+    return get_primitives(CryptoFormat::kRawPrefix);
+  }
+
+  // Sets the given 'primary' as the primary primitive of this set.
+  ABSL_DEPRECATED(
+      "Mutating PrimitiveSets after construction is deprecated. Use "
+      "PrimitiveSet<>::Builder instead.")
+  crypto::tink::util::Status set_primary(Entry<P>* primary) {
+    if (!is_mutable()) {
+      return util::Status(absl::StatusCode::kFailedPrecondition,
+                          "PrimitiveSet is not mutable.");
+    }
+    absl::MutexLock lock(primitives_mutex_.get());
+    return SetPrimaryImpl(&primary_, primary, primitives_);
+  }
+
+  // Returns the entry with the primary primitive.
+  const Entry<P>* get_primary() const {
+    absl::MutexLockMaybe lock(primitives_mutex_.get());
+    return primary_;
+  }
+
+  // Returns all entries.
+  std::vector<Entry<P>*> get_all() const {
+    absl::MutexLockMaybe lock(primitives_mutex_.get());
     std::vector<Entry<P>*> result;
     for (const auto& prefix_and_vector : primitives_) {
       for (const auto& primitive : prefix_and_vector.second) {
@@ -189,21 +327,43 @@
     return result;
   }
 
+  // Returns all entries in the original keyset key order.
+  std::vector<Entry<P>*> get_all_in_keyset_order() const {
+    absl::MutexLockMaybe lock(primitives_mutex_.get());
+    return primitives_in_keyset_order_;
+  }
+
   const absl::flat_hash_map<std::string, std::string>& get_annotations() const {
     return annotations_;
   }
 
+  bool is_mutable() const { return primitives_mutex_ != nullptr; }
+
  private:
-  typedef std::unordered_map<std::string, Primitives>
-      CiphertextPrefixToPrimitivesMap;
-  // The Entry<P> object is owned by primitives_
-  Entry<P>* primary_ = nullptr;
-  mutable absl::Mutex primitives_mutex_;
+  // Constructs an empty PrimitiveSet.
+  // Note: This is equivalent to PrimitiveSet<P>(/*annotations=*/{}).
+  PrimitiveSet(CiphertextPrefixToPrimitivesMap primitives, Entry<P>* primary,
+               std::vector<Entry<P>*> primitives_in_keyset_order,
+               absl::flat_hash_map<std::string, std::string> annotations)
+      : primary_(primary),
+        primitives_mutex_(nullptr),
+        primitives_(std::move(primitives)),
+        primitives_in_keyset_order_(std::move(primitives_in_keyset_order)),
+        annotations_(std::move(annotations)) {}
+
+  // Owned by primitives_.
+  Entry<P>* primary_ ABSL_GUARDED_BY(primitives_mutex_) = nullptr;
+  // If primitives_mutex_ is a nullptr, PrimitiveSet is immutable and lock-free.
+  // If not nullptr, primitives_mutex_ guards all read and write access.
+  mutable std::unique_ptr<absl::Mutex> primitives_mutex_ =
+      absl::make_unique<absl::Mutex>();
   CiphertextPrefixToPrimitivesMap primitives_
       ABSL_GUARDED_BY(primitives_mutex_);
+  // Entries in the original keyset key order, all owned by primitives_.
+  std::vector<Entry<P>*> primitives_in_keyset_order_
+      ABSL_GUARDED_BY(primitives_mutex_);
 
-  // Annotations for the set of primitives.
-  const absl::flat_hash_map<std::string, std::string> annotations_;
+  absl::flat_hash_map<std::string, std::string> annotations_;
 };
 
 }  // namespace tink
diff --git a/cc/primitive_wrapper.h b/cc/primitive_wrapper.h
index beadd8b..1ba6f22 100644
--- a/cc/primitive_wrapper.h
+++ b/cc/primitive_wrapper.h
@@ -31,10 +31,15 @@
 //
 // PrimitiveWrappers need to be written for every new primitive. They can be
 // registered in the registry to be fully integrated in Tink.
-template <typename InputPrimitive, typename Primitive>
+template <typename InputPrimitiveParam, typename PrimitiveParam>
 class PrimitiveWrapper {
  public:
-  virtual ~PrimitiveWrapper() {}
+  virtual ~PrimitiveWrapper() = default;
+
+  // Useful when writing templated code.
+  using InputPrimitive = InputPrimitiveParam;
+  using Primitive = PrimitiveParam;
+
   virtual crypto::tink::util::StatusOr<std::unique_ptr<Primitive>> Wrap(
       std::unique_ptr<PrimitiveSet<InputPrimitive>> primitive_set) const = 0;
 };
diff --git a/cc/proto/aes_cmac.proto b/cc/proto/aes_cmac.proto
index b214834..541ff58 100644
--- a/cc/proto/aes_cmac.proto
+++ b/cc/proto/aes_cmac.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_cmac_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_cmac_go_proto";
 
 message AesCmacParams {
   uint32 tag_size = 1;
diff --git a/cc/proto/aes_cmac_prf.proto b/cc/proto/aes_cmac_prf.proto
index 58e5f67..b2efc6d 100644
--- a/cc/proto/aes_cmac_prf.proto
+++ b/cc/proto/aes_cmac_prf.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_cmac_prf_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_cmac_prf_go_proto";
 
 // key_type: type.googleapis.com/google.crypto.tink.AesCmacPrfKey
 message AesCmacPrfKey {
diff --git a/cc/proto/aes_ctr.proto b/cc/proto/aes_ctr.proto
index ecdb256..721699c 100644
--- a/cc/proto/aes_ctr.proto
+++ b/cc/proto/aes_ctr.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_ctr_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_ctr_go_proto";
 
 message AesCtrParams {
   uint32 iv_size = 1;
diff --git a/cc/proto/aes_ctr_hmac_aead.proto b/cc/proto/aes_ctr_hmac_aead.proto
index dcf541d..91ccb9b 100644
--- a/cc/proto/aes_ctr_hmac_aead.proto
+++ b/cc/proto/aes_ctr_hmac_aead.proto
@@ -23,7 +23,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_ctr_hmac_aead_go_proto";
 
 message AesCtrHmacAeadKeyFormat {
   AesCtrKeyFormat aes_ctr_key_format = 1;
diff --git a/cc/proto/aes_ctr_hmac_streaming.proto b/cc/proto/aes_ctr_hmac_streaming.proto
index 064b630..776e9bd 100644
--- a/cc/proto/aes_ctr_hmac_streaming.proto
+++ b/cc/proto/aes_ctr_hmac_streaming.proto
@@ -23,7 +23,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_ctr_hmac_streaming_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_ctr_hmac_streaming_go_proto";
 
 message AesCtrHmacStreamingParams {
   uint32 ciphertext_segment_size = 1;
diff --git a/cc/proto/aes_eax.proto b/cc/proto/aes_eax.proto
index c673306..c1bf500 100644
--- a/cc/proto/aes_eax.proto
+++ b/cc/proto/aes_eax.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_eax_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_eax_go_proto";
 
 // only allowing tag size in bytes = 16
 message AesEaxParams {
diff --git a/cc/proto/aes_gcm.proto b/cc/proto/aes_gcm.proto
index fba7a89..2551aa4 100644
--- a/cc/proto/aes_gcm.proto
+++ b/cc/proto/aes_gcm.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_gcm_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_gcm_go_proto";
 option objc_class_prefix = "TINKPB";
 
 message AesGcmKeyFormat {
diff --git a/cc/proto/aes_gcm_hkdf_streaming.proto b/cc/proto/aes_gcm_hkdf_streaming.proto
index 61fb479..5ec7ca4 100644
--- a/cc/proto/aes_gcm_hkdf_streaming.proto
+++ b/cc/proto/aes_gcm_hkdf_streaming.proto
@@ -24,7 +24,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_gcm_hkdf_streaming_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_gcm_hkdf_streaming_go_proto";
 
 message AesGcmHkdfStreamingParams {
   uint32 ciphertext_segment_size = 1;
diff --git a/cc/proto/aes_gcm_siv.proto b/cc/proto/aes_gcm_siv.proto
index df9fada..220d79f 100644
--- a/cc/proto/aes_gcm_siv.proto
+++ b/cc/proto/aes_gcm_siv.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_gcm_siv_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_gcm_siv_go_proto";
 
 // The only allowed IV size is 12 bytes and tag size is 16 bytes.
 // Thus, accept no params.
diff --git a/cc/proto/aes_siv.proto b/cc/proto/aes_siv.proto
index 0023027..ccb8d3c 100644
--- a/cc/proto/aes_siv.proto
+++ b/cc/proto/aes_siv.proto
@@ -20,7 +20,15 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/aes_siv_go_proto";
+option go_package = "github.com/google/tink/go/proto/aes_siv_go_proto";
+
+// Tink implements RFC 5297 (https://www.rfc-editor.org/rfc/rfc5297) for
+// AES-SIV, putting the SIV/Tag at the beginning of the ciphertext.
+//
+// While the RFC 5297 supports a list of associated datas, Tink only supports
+// exactly one associated data, which corresponds to a list with one element in
+// RFC 5297. An empty associated data is a list with one empty element, and not
+// an empty list.
 
 message AesSivKeyFormat {
   // Only valid value is: 64.
diff --git a/cc/proto/cached_dek_aead.proto b/cc/proto/cached_dek_aead.proto
index 10bcde5..9b1a33f 100644
--- a/cc/proto/cached_dek_aead.proto
+++ b/cc/proto/cached_dek_aead.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/cached_dek_aead_go_proto";
+option go_package = "github.com/google/tink/go/proto/cached_dek_aead_go_proto";
 
 message CachedDekAeadKeyFormat {
   // Required.
diff --git a/cc/proto/cached_dek_envelope.proto b/cc/proto/cached_dek_envelope.proto
index 1b096ad..8739b83 100644
--- a/cc/proto/cached_dek_envelope.proto
+++ b/cc/proto/cached_dek_envelope.proto
@@ -22,7 +22,7 @@
 
 option java_multiple_files = true;
 option java_package = "com.google.crypto.tink.proto";
-option go_package = "github.com/google/tink/proto/cached_dek_envelope_go_proto";
+option go_package = "github.com/google/tink/go/proto/cached_dek_envelope_go_proto";
 
 message CachedDekEnvelopeAeadKeyFormat {
   // Required.
diff --git a/cc/proto/chacha20_poly1305.proto b/cc/proto/chacha20_poly1305.proto
index 2cd6ead..ef8ab6e 100644
--- a/cc/proto/chacha20_poly1305.proto
+++ b/cc/proto/chacha20_poly1305.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/chacha20_poly1305_go_proto";
+option go_package = "github.com/google/tink/go/proto/chacha20_poly1305_go_proto";
 
 message ChaCha20Poly1305KeyFormat {}
 
diff --git a/cc/proto/common.proto b/cc/proto/common.proto
index eaff8d3..4546064 100644
--- a/cc/proto/common.proto
+++ b/cc/proto/common.proto
@@ -21,7 +21,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/common_go_proto";
+option go_package = "github.com/google/tink/go/proto/common_go_proto";
 
 enum EllipticCurveType {
   UNKNOWN_CURVE = 0;
diff --git a/cc/proto/config.proto b/cc/proto/config.proto
index ebbd742..cff6506 100644
--- a/cc/proto/config.proto
+++ b/cc/proto/config.proto
@@ -21,7 +21,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/config_go_proto";
+option go_package = "github.com/google/tink/go/proto/config_go_proto";
 
 // An entry that describes a key type to be used with Tink library,
 // specifying the corresponding primitive, key manager, and deprecation status.
diff --git a/cc/proto/ecdsa.proto b/cc/proto/ecdsa.proto
index 6ba3970..2ce461f 100644
--- a/cc/proto/ecdsa.proto
+++ b/cc/proto/ecdsa.proto
@@ -23,7 +23,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/ecdsa_go_proto";
+option go_package = "github.com/google/tink/go/proto/ecdsa_go_proto";
 
 enum EcdsaSignatureEncoding {
   UNKNOWN_ENCODING = 0;
@@ -80,4 +80,5 @@
 message EcdsaKeyFormat {
   // Required.
   EcdsaParams params = 2;
+  uint32 version = 3;
 }
diff --git a/cc/proto/ecies_aead_hkdf.proto b/cc/proto/ecies_aead_hkdf.proto
index 9470991..0c06ee3 100644
--- a/cc/proto/ecies_aead_hkdf.proto
+++ b/cc/proto/ecies_aead_hkdf.proto
@@ -24,7 +24,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/ecies_aead_hkdf_go_proto";
+option go_package = "github.com/google/tink/go/proto/ecies_aead_hkdf_go_proto";
 
 // Protos for keys for ECIES with HKDF and AEAD encryption.
 //
diff --git a/cc/proto/ed25519.proto b/cc/proto/ed25519.proto
index 669f33a..613c59f 100644
--- a/cc/proto/ed25519.proto
+++ b/cc/proto/ed25519.proto
@@ -23,10 +23,10 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/ed25519_go_proto";
+option go_package = "github.com/google/tink/go/proto/ed25519_go_proto";
 
 message Ed25519KeyFormat {
-    uint32 version = 1;
+  uint32 version = 1;
 }
 
 // key_type: type.googleapis.com/google.crypto.tink.Ed25519PublicKey
diff --git a/cc/proto/empty.proto b/cc/proto/empty.proto
index 33831a9..beeba07 100644
--- a/cc/proto/empty.proto
+++ b/cc/proto/empty.proto
@@ -20,6 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/empty_go_proto";
+option go_package = "github.com/google/tink/go/proto/empty_go_proto";
 
 message Empty {}
diff --git a/cc/proto/hkdf_prf.proto b/cc/proto/hkdf_prf.proto
index 3d3cbe9..38e69c5 100644
--- a/cc/proto/hkdf_prf.proto
+++ b/cc/proto/hkdf_prf.proto
@@ -22,12 +22,16 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/hkdf_prf_proto";
+option go_package = "github.com/google/tink/go/proto/hkdf_prf_proto";
 
 message HkdfPrfParams {
   HashType hash = 1;
-  // Salt, optional in RFC 5869. Using "" is equivalent to zeros of length up to
-  // the block length of the HMac.
+  // Optional.
+  //
+  // An unspecified or zero-length value is equivalent to a sequence of zeros
+  // (0x00) with a length equal to the output size of hash.
+  //
+  // See https://rfc-editor.org/rfc/rfc5869.
   bytes salt = 2;
 }
 
diff --git a/cc/proto/hmac.proto b/cc/proto/hmac.proto
index 2733e51..bdd4e8a 100644
--- a/cc/proto/hmac.proto
+++ b/cc/proto/hmac.proto
@@ -22,7 +22,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/hmac_go_proto";
+option go_package = "github.com/google/tink/go/proto/hmac_go_proto";
 
 message HmacParams {
   HashType hash = 1;  // HashType is an enum.
diff --git a/cc/proto/hmac_prf.proto b/cc/proto/hmac_prf.proto
index 7b3c52d..87ef97d 100644
--- a/cc/proto/hmac_prf.proto
+++ b/cc/proto/hmac_prf.proto
@@ -22,7 +22,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/hmac_prf_go_proto";
+option go_package = "github.com/google/tink/go/proto/hmac_prf_go_proto";
 
 message HmacPrfParams {
   HashType hash = 1;  // HashType is an enum.
diff --git a/cc/proto/hpke.proto b/cc/proto/hpke.proto
index 847864a..f794e77 100644
--- a/cc/proto/hpke.proto
+++ b/cc/proto/hpke.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/hpke_proto";
+option go_package = "github.com/google/tink/go/proto/hpke_proto";
 
 enum HpkeKem {
   KEM_UNKNOWN = 0;
diff --git a/cc/proto/jwt_ecdsa.proto b/cc/proto/jwt_ecdsa.proto
index 4c80fe1..ce78b04 100644
--- a/cc/proto/jwt_ecdsa.proto
+++ b/cc/proto/jwt_ecdsa.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/jwt_ecdsa_go_proto";
+option go_package = "github.com/google/tink/go/proto/jwt_ecdsa_go_proto";
 
 // See https://datatracker.ietf.org/doc/html/rfc7518#section-3.4
 enum JwtEcdsaAlgorithm {
diff --git a/cc/proto/jwt_hmac.proto b/cc/proto/jwt_hmac.proto
index e54a51d..a499638 100644
--- a/cc/proto/jwt_hmac.proto
+++ b/cc/proto/jwt_hmac.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/jwt_hmac_go_proto";
+option go_package = "github.com/google/tink/go/proto/jwt_hmac_go_proto";
 
 // See https://datatracker.ietf.org/doc/html/rfc7518#section-3.2
 enum JwtHmacAlgorithm {
diff --git a/cc/proto/jwt_rsa_ssa_pkcs1.proto b/cc/proto/jwt_rsa_ssa_pkcs1.proto
index adf31c8..54a9731 100644
--- a/cc/proto/jwt_rsa_ssa_pkcs1.proto
+++ b/cc/proto/jwt_rsa_ssa_pkcs1.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/rsa_ssa_pkcs1_go_proto";
+option go_package = "github.com/google/tink/go/proto/rsa_ssa_pkcs1_go_proto";
 
 // See https://datatracker.ietf.org/doc/html/rfc7518#section-3.3
 enum JwtRsaSsaPkcs1Algorithm {
diff --git a/cc/proto/jwt_rsa_ssa_pss.proto b/cc/proto/jwt_rsa_ssa_pss.proto
index 4312645..eb2d454 100644
--- a/cc/proto/jwt_rsa_ssa_pss.proto
+++ b/cc/proto/jwt_rsa_ssa_pss.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/jwt_rsa_ssa_pss_go_proto";
+option go_package = "github.com/google/tink/go/proto/jwt_rsa_ssa_pss_go_proto";
 
 // See https://datatracker.ietf.org/doc/html/rfc7518#section-3.5
 enum JwtRsaSsaPssAlgorithm {
diff --git a/cc/proto/kms_aead.proto b/cc/proto/kms_aead.proto
index e818788..16de8ee 100644
--- a/cc/proto/kms_aead.proto
+++ b/cc/proto/kms_aead.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/kms_aead_go_proto";
+option go_package = "github.com/google/tink/go/proto/kms_aead_go_proto";
 
 message KmsAeadKeyFormat {
   // Required.
diff --git a/cc/proto/kms_envelope.proto b/cc/proto/kms_envelope.proto
index fa806e6..8b0fd83 100644
--- a/cc/proto/kms_envelope.proto
+++ b/cc/proto/kms_envelope.proto
@@ -22,7 +22,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/kms_envelope_go_proto";
+option go_package = "github.com/google/tink/go/proto/kms_envelope_go_proto";
 
 message KmsEnvelopeAeadKeyFormat {
   // Required.
diff --git a/cc/proto/prf_based_deriver.proto b/cc/proto/prf_based_deriver.proto
index 06dd334..e58a2cd 100644
--- a/cc/proto/prf_based_deriver.proto
+++ b/cc/proto/prf_based_deriver.proto
@@ -22,7 +22,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/prf_based_deriver_go_proto";
+option go_package = "github.com/google/tink/go/proto/prf_based_deriver_go_proto";
 
 message PrfBasedDeriverParams {
   KeyTemplate derived_key_template = 1;
diff --git a/cc/proto/rsa_ssa_pkcs1.proto b/cc/proto/rsa_ssa_pkcs1.proto
index 961189d..9797ee0 100644
--- a/cc/proto/rsa_ssa_pkcs1.proto
+++ b/cc/proto/rsa_ssa_pkcs1.proto
@@ -24,7 +24,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/rsa_ssa_pkcs1_go_proto";
+option go_package = "github.com/google/tink/go/proto/rsa_ssa_pkcs1_go_proto";
 
 message RsaSsaPkcs1Params {
   // Hash function used in computing hash of the signing message
diff --git a/cc/proto/rsa_ssa_pss.proto b/cc/proto/rsa_ssa_pss.proto
index 8e7903f..1150057 100644
--- a/cc/proto/rsa_ssa_pss.proto
+++ b/cc/proto/rsa_ssa_pss.proto
@@ -25,7 +25,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/rsa_ssa_pss_go_proto";
+option go_package = "github.com/google/tink/go/proto/rsa_ssa_pss_go_proto";
 
 message RsaSsaPssParams {
   // Hash function used in computing hash of the signing message
diff --git a/cc/proto/tink.proto b/cc/proto/tink.proto
index 1787581..8b3d100 100644
--- a/cc/proto/tink.proto
+++ b/cc/proto/tink.proto
@@ -21,7 +21,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/tink_go_proto";
+option go_package = "github.com/google/tink/go/proto/tink_go_proto";
 option objc_class_prefix = "TINKPB";
 
 // Each instantiation of a Tink primitive is identified by type_url,
diff --git a/cc/proto/xchacha20_poly1305.proto b/cc/proto/xchacha20_poly1305.proto
index cc52624..a2613f1 100644
--- a/cc/proto/xchacha20_poly1305.proto
+++ b/cc/proto/xchacha20_poly1305.proto
@@ -20,7 +20,7 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option go_package = "github.com/google/tink/proto/xchacha20_poly1305_go_proto";
+option go_package = "github.com/google/tink/go/proto/xchacha20_poly1305_go_proto";
 
 message XChaCha20Poly1305KeyFormat {
   uint32 version = 1;
diff --git a/cc/proto_keyset_format.cc b/cc/proto_keyset_format.cc
new file mode 100644
index 0000000..e666ed9
--- /dev/null
+++ b/cc/proto_keyset_format.cc
@@ -0,0 +1,101 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "tink/proto_keyset_format.h"
+
+#include <ios>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "tink/binary_keyset_reader.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/util/secret_data.h"
+
+namespace crypto {
+namespace tink {
+
+crypto::tink::util::StatusOr<KeysetHandle> ParseKeysetFromProtoKeysetFormat(
+    absl::string_view serialized_keyset, SecretKeyAccessToken token) {
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::KeysetReader>>
+      keyset_reader = BinaryKeysetReader::New(serialized_keyset);
+  if (!keyset_reader.ok()) {
+    return keyset_reader.status();
+  }
+  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> result =
+    CleartextKeysetHandle::Read(std::move(*keyset_reader));
+  if (!result.ok()) {
+    return result.status();
+  }
+  return std::move(**result);
+}
+
+crypto::tink::util::StatusOr<util::SecretData>
+SerializeKeysetToProtoKeysetFormat(const KeysetHandle& keyset_handle,
+                                   SecretKeyAccessToken token) {
+  std::stringbuf string_buf(std::ios_base::out);
+  crypto::tink::util::StatusOr<std::unique_ptr<BinaryKeysetWriter>>
+      keyset_writer = BinaryKeysetWriter::New(
+          std::make_unique<std::ostream>(&string_buf));
+  if (!keyset_writer.ok()) {
+    return keyset_writer.status();
+  }
+  crypto::tink::util::Status status =
+      CleartextKeysetHandle::Write(keyset_writer->get(), keyset_handle);
+  if (!status.ok()) {
+    return status;
+  }
+  // TODO(tholenst): directly write into a secret data.
+  return util::SecretDataFromStringView(string_buf.str());
+}
+
+crypto::tink::util::StatusOr<KeysetHandle>
+ParseKeysetWithoutSecretFromProtoKeysetFormat(
+    absl::string_view serialized_keyset) {
+  std::string keyset_copy = std::string(serialized_keyset);
+  crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> result =
+    KeysetHandle::ReadNoSecret(keyset_copy);
+  if (!result.ok()) {
+    return result.status();
+  }
+  return std::move(**result);
+}
+
+crypto::tink::util::StatusOr<std::string>
+SerializeKeysetWithoutSecretToProtoKeysetFormat(
+    const KeysetHandle& keyset_handle) {
+  std::stringbuf string_buf(std::ios_base::out);
+  crypto::tink::util::StatusOr<std::unique_ptr<BinaryKeysetWriter>>
+      keyset_writer = BinaryKeysetWriter::New(
+          std::make_unique<std::ostream>(&string_buf));
+  if (!keyset_writer.ok()) {
+    return keyset_writer.status();
+  }
+  crypto::tink::util::Status status =
+      keyset_handle.WriteNoSecret(keyset_writer->get());
+  if (!status.ok()) {
+    return status;
+  }
+  return string_buf.str();
+}
+
+}  // namespace tink
+}  // namespace crypto
+
diff --git a/cc/proto_keyset_format.h b/cc/proto_keyset_format.h
new file mode 100644
index 0000000..cab89eb
--- /dev/null
+++ b/cc/proto_keyset_format.h
@@ -0,0 +1,56 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_PROTO_KEYSET_FORMAT_H_
+#define TINK_PROTO_KEYSET_FORMAT_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "tink/keyset_handle.h"
+#include "tink/secret_key_access_token.h"
+#include "tink/util/secret_data.h"
+
+namespace crypto {
+namespace tink {
+
+// Serializes a keyset into a binary string in "ProtoKeysetFormat".
+// This function can serialize both keyset with or without secret key material.
+crypto::tink::util::StatusOr<util::SecretData>
+SerializeKeysetToProtoKeysetFormat(const KeysetHandle& keyset_handle,
+                                   SecretKeyAccessToken token);
+
+// Parses a keyset from a binary string in "ProtoKeysetFormat".
+// This function can parse both keyset with or without secret key material.
+crypto::tink::util::StatusOr<KeysetHandle> ParseKeysetFromProtoKeysetFormat(
+    absl::string_view serialized_keyset, SecretKeyAccessToken token);
+
+// Serializes a keyset into a binary string in "ProtoKeysetFormat".
+// This function will fail if the keyset contains secret key material.
+crypto::tink::util::StatusOr<std::string>
+SerializeKeysetWithoutSecretToProtoKeysetFormat(
+    const KeysetHandle& keyset_handle);
+
+// Parses a keyset from a binary string in "ProtoKeysetFormat".
+// This function will fail if the keyset contains secret key material.
+crypto::tink::util::StatusOr<KeysetHandle>
+ParseKeysetWithoutSecretFromProtoKeysetFormat(
+    absl::string_view serialized_keyset);
+
+
+}  // namespace tink
+}  // namespace crypto
+#endif  // TINK_PROTO_KEYSET_FORMAT_H_
diff --git a/cc/proto_keyset_format_test.cc b/cc/proto_keyset_format_test.cc
new file mode 100644
index 0000000..5ac23e6
--- /dev/null
+++ b/cc/proto_keyset_format_test.cc
@@ -0,0 +1,279 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//     http://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 "tink/proto_keyset_format.h"
+
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
+#include "tink/config/tink_config.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/legacy_proto_parameters.h"
+#include "tink/internal/proto_parameters_serialization.h"
+#include "tink/keyset_handle_builder.h"
+#include "tink/mac.h"
+#include "tink/mac/mac_key_templates.h"
+#include "tink/signature/signature_key_templates.h"
+#include "tink/util/secret_data.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+using ::crypto::tink::internal::LegacyProtoParameters;
+using ::crypto::tink::internal::ProtoParametersSerialization;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::util::SecretData;
+using ::crypto::tink::util::SecretDataAsStringView;
+using ::testing::Eq;
+using ::testing::Not;
+
+class SerializeKeysetToProtoKeysetFormatTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    auto status = TinkConfig::Register();
+    ASSERT_THAT(status, IsOk());
+  }
+};
+
+util::StatusOr<LegacyProtoParameters> CmacParameters() {
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create(MacKeyTemplates::AesCmac());
+  if (!serialization.ok()) return serialization.status();
+
+  return LegacyProtoParameters(*serialization);
+}
+
+util::StatusOr<LegacyProtoParameters> EcdsaParameters() {
+  util::StatusOr<ProtoParametersSerialization> serialization =
+      ProtoParametersSerialization::Create(SignatureKeyTemplates::EcdsaP256());
+  if (!serialization.ok()) return serialization.status();
+
+  return LegacyProtoParameters(*serialization);
+}
+
+TEST_F(SerializeKeysetToProtoKeysetFormatTest, SerializeAndParseSingleKey) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CmacParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+              /*id=*/123))
+          .Build();
+  ASSERT_THAT(handle, IsOk());
+
+  crypto::tink::util::StatusOr<SecretData> serialization =
+      SerializeKeysetToProtoKeysetFormat(*handle,
+                                         InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<KeysetHandle> parsed_handle = ParseKeysetFromProtoKeysetFormat(
+      SecretDataAsStringView(*serialization), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_handle, IsOk());
+  ASSERT_THAT(handle->size(), Eq(1));
+  ASSERT_THAT(parsed_handle->size(), Eq(1));
+
+  EXPECT_TRUE(*(*handle)[0].GetKey() == *(*parsed_handle)[0].GetKey());
+  EXPECT_TRUE((*handle)[0].GetId() == (*parsed_handle)[0].GetId());
+  EXPECT_TRUE((*handle)[0].GetStatus() == (*parsed_handle)[0].GetStatus());
+}
+
+TEST_F(SerializeKeysetToProtoKeysetFormatTest, SerializeAndParseMultipleKeys) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CmacParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled, /*is_primary=*/false,
+              /*id=*/123))
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+              /*id=*/125))
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kDisabled, /*is_primary=*/true,
+              /*id=*/127))
+          .Build();
+  ASSERT_THAT(handle, IsOk());
+
+  crypto::tink::util::StatusOr<SecretData> serialization =
+      SerializeKeysetToProtoKeysetFormat(*handle,
+                                         InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<KeysetHandle> parsed_handle = ParseKeysetFromProtoKeysetFormat(
+      SecretDataAsStringView(*serialization), InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_handle, IsOk());
+  ASSERT_THAT(handle->size(), Eq(3));
+  ASSERT_THAT(parsed_handle->size(), Eq(3));
+
+  EXPECT_TRUE(*(*handle)[0].GetKey() == *(*parsed_handle)[0].GetKey());
+  EXPECT_TRUE((*handle)[0].GetId() == (*parsed_handle)[0].GetId());
+  EXPECT_TRUE((*handle)[0].GetStatus() == (*parsed_handle)[0].GetStatus());
+
+  EXPECT_TRUE(*(*handle)[1].GetKey() == *(*parsed_handle)[1].GetKey());
+  EXPECT_TRUE((*handle)[1].GetId() == (*parsed_handle)[1].GetId());
+  EXPECT_TRUE((*handle)[1].GetStatus() == (*parsed_handle)[1].GetStatus());
+
+  EXPECT_TRUE(*(*handle)[2].GetKey() == *(*parsed_handle)[2].GetKey());
+  EXPECT_TRUE((*handle)[2].GetId() == (*parsed_handle)[2].GetId());
+  EXPECT_TRUE((*handle)[2].GetStatus() == (*parsed_handle)[2].GetStatus());
+}
+
+TEST_F(SerializeKeysetToProtoKeysetFormatTest, SerializeNoAccessFails) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CmacParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+              /*id=*/123))
+          .Build();
+  ASSERT_THAT(handle, IsOk());
+
+  crypto::tink::util::StatusOr<std::string> serialization =
+      SerializeKeysetWithoutSecretToProtoKeysetFormat(*handle);
+  ASSERT_THAT(serialization, Not(IsOk()));
+}
+
+TEST_F(SerializeKeysetToProtoKeysetFormatTest, ParseNoAccessFails) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      CmacParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+              /*id=*/123))
+          .Build();
+  ASSERT_THAT(handle, IsOk());
+
+  crypto::tink::util::StatusOr<SecretData> serialization =
+      SerializeKeysetToProtoKeysetFormat(*handle,
+                                         InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization, IsOk());
+
+  util::StatusOr<KeysetHandle> parsed_handle =
+      ParseKeysetWithoutSecretFromProtoKeysetFormat(
+          SecretDataAsStringView(*serialization));
+  ASSERT_THAT(parsed_handle, Not(IsOk()));
+}
+
+TEST_F(SerializeKeysetToProtoKeysetFormatTest, TestVector) {
+  std::string serialized_keyset = absl::HexStringToBytes(
+      "0895e59bcc0612680a5c0a2e747970652e676f6f676c65617069732e636f6d2f676f6f67"
+      "6c652e63727970746f2e74696e6b2e486d61634b657912281a20cca20f02278003b3513f"
+      "5d01759ac1302f7d883f2f4a40025532ee1b11f9e587120410100803180110011895e59b"
+      "cc062001");
+  crypto::tink::util::StatusOr<KeysetHandle> keyset_handle =
+      ParseKeysetFromProtoKeysetFormat(serialized_keyset,
+                                       InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(keyset_handle.status(), IsOk());
+  crypto::tink::util::StatusOr<std::unique_ptr<Mac>> mac =
+      (*keyset_handle).GetPrimitive<Mac>();
+  ASSERT_THAT(mac.status(), IsOk());
+  ASSERT_THAT(
+      (*mac)->VerifyMac(
+          absl::HexStringToBytes("016986f2956092d259136923c6f4323557714ec499"),
+          "data"),
+      IsOk());
+}
+
+TEST_F(SerializeKeysetToProtoKeysetFormatTest, SerializeAndParsePublicKey) {
+  util::StatusOr<internal::LegacyProtoParameters> parameters =
+      EcdsaParameters();
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<KeysetHandle> handle =
+      KeysetHandleBuilder()
+          .AddEntry(KeysetHandleBuilder::Entry::CreateFromCopyableParams(
+              *parameters, KeyStatus::kEnabled, /*is_primary=*/true,
+              /*id=*/123))
+          .Build();
+  ASSERT_THAT(handle, IsOk());
+  util::StatusOr<std::unique_ptr<KeysetHandle>> public_handle =
+      handle->GetPublicKeysetHandle();
+  ASSERT_THAT(public_handle, IsOk());
+
+
+  crypto::tink::util::StatusOr<SecretData> serialization1 =
+      SerializeKeysetToProtoKeysetFormat(**public_handle,
+                                         InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(serialization1, IsOk());
+  crypto::tink::util::StatusOr<std::string> serialization2 =
+      SerializeKeysetWithoutSecretToProtoKeysetFormat(**public_handle);
+  ASSERT_THAT(serialization2, IsOk());
+
+  util::StatusOr<KeysetHandle> parsed_handle1 =
+      ParseKeysetFromProtoKeysetFormat(SecretDataAsStringView(*serialization1),
+                                       InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_handle1, IsOk());
+  util::StatusOr<KeysetHandle> parsed_handle2 =
+      ParseKeysetWithoutSecretFromProtoKeysetFormat(
+          SecretDataAsStringView(*serialization1));
+  ASSERT_THAT(parsed_handle2, IsOk());
+  util::StatusOr<KeysetHandle> parsed_handle3 =
+      ParseKeysetFromProtoKeysetFormat(*serialization2,
+                                       InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(parsed_handle3, IsOk());
+  util::StatusOr<KeysetHandle> parsed_handle4 =
+      ParseKeysetWithoutSecretFromProtoKeysetFormat(*serialization2);
+  ASSERT_THAT(parsed_handle4, IsOk());
+
+  ASSERT_THAT((*public_handle)->size(), Eq(1));
+  ASSERT_THAT(parsed_handle1->size(), Eq(1));
+  ASSERT_THAT(parsed_handle2->size(), Eq(1));
+  ASSERT_THAT(parsed_handle3->size(), Eq(1));
+  ASSERT_THAT(parsed_handle4->size(), Eq(1));
+
+  // TODO(b/277791403): Replace with KeysetHandle::Entry equality checks.
+  EXPECT_TRUE(*(**public_handle)[0].GetKey() == *(*parsed_handle1)[0].GetKey());
+  EXPECT_TRUE(*(**public_handle)[0].GetKey() == *(*parsed_handle2)[0].GetKey());
+  EXPECT_TRUE(*(**public_handle)[0].GetKey() == *(*parsed_handle3)[0].GetKey());
+  EXPECT_TRUE(*(**public_handle)[0].GetKey() == *(*parsed_handle4)[0].GetKey());
+
+  EXPECT_TRUE((**public_handle)[0].GetId() == (*parsed_handle1)[0].GetId());
+  EXPECT_TRUE((**public_handle)[0].GetId() == (*parsed_handle2)[0].GetId());
+  EXPECT_TRUE((**public_handle)[0].GetId() == (*parsed_handle3)[0].GetId());
+  EXPECT_TRUE((**public_handle)[0].GetId() == (*parsed_handle4)[0].GetId());
+
+  EXPECT_TRUE((**public_handle)[0].GetStatus() ==
+              (*parsed_handle1)[0].GetStatus());
+  EXPECT_TRUE((**public_handle)[0].GetStatus() ==
+              (*parsed_handle2)[0].GetStatus());
+  EXPECT_TRUE((**public_handle)[0].GetStatus() ==
+              (*parsed_handle3)[0].GetStatus());
+  EXPECT_TRUE((**public_handle)[0].GetStatus() ==
+              (*parsed_handle4)[0].GetStatus());
+}
+
+
+}  // namespace
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/public_key_sign.h b/cc/public_key_sign.h
index 4fdb4b1..7c4b110 100644
--- a/cc/public_key_sign.h
+++ b/cc/public_key_sign.h
@@ -39,7 +39,7 @@
   virtual crypto::tink::util::StatusOr<std::string> Sign(
       absl::string_view data) const = 0;
 
-  virtual ~PublicKeySign() {}
+  virtual ~PublicKeySign() = default;
 };
 
 }  // namespace tink
diff --git a/cc/public_key_verify.h b/cc/public_key_verify.h
index 7883caf..0119d1d 100644
--- a/cc/public_key_verify.h
+++ b/cc/public_key_verify.h
@@ -38,7 +38,7 @@
       absl::string_view signature,
       absl::string_view data) const = 0;
 
-  virtual ~PublicKeyVerify() {}
+  virtual ~PublicKeyVerify() = default;
 };
 
 }  // namespace tink
diff --git a/cc/random_access_stream.h b/cc/random_access_stream.h
index b40445a..c5a4dec 100644
--- a/cc/random_access_stream.h
+++ b/cc/random_access_stream.h
@@ -28,8 +28,8 @@
 // like regular files.
 class RandomAccessStream {
  public:
-  RandomAccessStream() {}
-  virtual ~RandomAccessStream() {}
+  RandomAccessStream() = default;
+  virtual ~RandomAccessStream() = default;
 
   // Reads up to 'count' bytes starting at 'position' and writes them
   // to 'dest_buffer'.  'position' must be within the size of the stream,
diff --git a/cc/registry.h b/cc/registry.h
index 29e38d2..5fb7933 100644
--- a/cc/registry.h
+++ b/cc/registry.h
@@ -50,43 +50,6 @@
 // and KeyManagers.
 class Registry {
  public:
-  // Returns a catalogue with the given name (if any found).
-  // Keeps the ownership of the catalogue.
-  template <class P>
-  ABSL_DEPRECATED("Catalogues are not supported anymore.")
-  static crypto::tink::util::StatusOr<const Catalogue<P>*> get_catalogue(
-      absl::string_view catalogue_name) {
-    return internal::RegistryImpl::GlobalInstance().get_catalogue<P>(
-        catalogue_name);
-  }
-
-  // Adds the given 'catalogue' under the specified 'catalogue_name',
-  // to enable custom configuration of key types and key managers.
-  //
-  // Adding a custom catalogue should be a one-time operation,
-  // and fails if the given 'catalogue' tries to override
-  // an existing, different catalogue for the specified name.
-  template <class ConcreteCatalogue>
-  ABSL_DEPRECATED("Catalogues are not supported anymore.")
-  static crypto::tink::util::Status
-      AddCatalogue(absl::string_view catalogue_name,
-                   std::unique_ptr<ConcreteCatalogue> catalogue) {
-    return internal::RegistryImpl::GlobalInstance().AddCatalogue(
-        catalogue_name, catalogue.release());
-  }
-
-  // AddCatalogue has the same functionality as the overload which uses a
-  // unique_ptr and which should be preferred.
-  //
-  // Takes ownership of 'catalogue', which must be non-nullptr (in case of
-  // failure, 'catalogue' is deleted).
-  template <class P>
-  ABSL_DEPRECATED("Use AddCatalogue with a unique_ptr input instead.")
-  static crypto::tink::util::Status
-      AddCatalogue(absl::string_view catalogue_name, Catalogue<P>* catalogue) {
-    return AddCatalogue(catalogue_name, absl::WrapUnique(catalogue));
-  }
-
   // Registers the given 'manager' for the key type 'manager->get_key_type()'.
   template <class ConcreteKeyManager>
   static crypto::tink::util::Status RegisterKeyManager(
@@ -95,23 +58,6 @@
         manager.release(), new_key_allowed);
   }
 
-  // Same functionality as the overload which takes a unique pointer, for
-  // new_key_allowed = true.
-  template <class P>
-  ABSL_DEPRECATED(
-      "Use RegisterKeyManager with a unique_ptr manager and new_key_allowed = "
-      "true instead.")
-  static crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager) {
-    return RegisterKeyManager(absl::WrapUnique(manager), true);
-  }
-
-  template <class P>
-  ABSL_DEPRECATED("Use RegisterKeyManager with a unique_ptr manager instead.")
-  static crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager,
-                                                       bool new_key_allowed) {
-    return RegisterKeyManager(absl::WrapUnique(manager), new_key_allowed);
-  }
-
   template <class KTManager>
   static crypto::tink::util::Status RegisterKeyTypeManager(
       std::unique_ptr<KTManager> manager, bool new_key_allowed) {
@@ -161,15 +107,6 @@
       const google::crypto::tink::KeyData& key_data) {
     return internal::RegistryImpl::GlobalInstance().GetPrimitive<P>(key_data);
   }
-  // Convenience method for creating a new primitive for the key given
-  // in 'key'.  It looks up a KeyManager identified by type_url,
-  // and calls manager's GetPrimitive(key)-method.
-  template <class P>
-  static crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
-      absl::string_view type_url, const portable_proto::MessageLite& key) {
-    return internal::RegistryImpl::GlobalInstance().GetPrimitive<P>(type_url,
-                                                                    key);
-  }
 
   // Generates a new KeyData for the specified 'key_template'.
   // It looks up a KeyManager identified by key_template.type_url,
diff --git a/cc/restricted_data.h b/cc/restricted_data.h
new file mode 100644
index 0000000..02902b2
--- /dev/null
+++ b/cc/restricted_data.h
@@ -0,0 +1,74 @@
+// Copyright 2022 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_RESTRICTED_DATA_H_
+#define TINK_RESTRICTED_DATA_H_
+
+#include "tink/secret_key_access_token.h"
+#include "tink/util/secret_data.h"
+
+namespace crypto {
+namespace tink {
+
+// Stores secret (sensitive) data that is safely destroyed in the event of
+// core dumps (similar to `util::SecretData`) and access restricted via
+// `SecurityKeyAccessToken`.  This class is particularly useful for
+// encapsulating cryptographic key material.
+//
+// Example:
+//     RestrictedData restricted_data(/*num_random_bytes=*/32);
+//     absl::string_view raw_secret =
+//         restricted_data.GetSecret(InsecureSecretKeyAccess::Get());
+class RestrictedData {
+ public:
+  // Copyable and movable.
+  RestrictedData(const RestrictedData& other) = default;
+  RestrictedData& operator=(const RestrictedData& other) = default;
+  RestrictedData(RestrictedData&& other) = default;
+  RestrictedData& operator=(RestrictedData&& other) = default;
+
+  // Creates a new RestrictedData object that wraps `secret`. Note that creating
+  // a `token` requires access to `InsecureSecretKeyAccess::Get()`.
+  explicit RestrictedData(absl::string_view secret, SecretKeyAccessToken token)
+      : secret_(util::SecretDataFromStringView(secret)) {}
+
+  // Creates a new RestrictedData object that wraps a secret containing
+  // `num_random_bytes`. The program will terminate if `num_random_bytes` is a
+  // negative value.
+  explicit RestrictedData(int64_t num_random_bytes);
+
+  // Returns the secret for this RestrictedData object. Note that creating a
+  // `token` requires access to `InsecureSecretKeyAccess::Get()`.
+  absl::string_view GetSecret(SecretKeyAccessToken token) const {
+    return util::SecretDataAsStringView(secret_);
+  }
+
+  int64_t size() const { return secret_.size(); }
+
+  // Constant-time comparison operators.
+  bool operator==(const RestrictedData& other) const;
+  bool operator!=(const RestrictedData& other) const {
+    return !(*this == other);
+  }
+
+ private:
+  util::SecretData secret_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_RESTRICTED_DATA_H_
diff --git a/cc/secret_key_access.h b/cc/secret_key_access.h
index d51e144..52655ad 100644
--- a/cc/secret_key_access.h
+++ b/cc/secret_key_access.h
@@ -14,8 +14,8 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-#ifndef THIRD_PARTY_TINK_SECRET_KEY_ACCESS_H_
-#define THIRD_PARTY_TINK_SECRET_KEY_ACCESS_H_
+#ifndef TINK_SECRET_KEY_ACCESS_H_
+#define TINK_SECRET_KEY_ACCESS_H_
 
 #include "tink/key_access.h"
 
@@ -30,4 +30,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // THIRD_PARTY_TINK_SECRET_KEY_ACCESS_H_
+#endif  // TINK_SECRET_KEY_ACCESS_H_
diff --git a/cc/signature/BUILD.bazel b/cc/signature/BUILD.bazel
index 2e5af69..b882901 100644
--- a/cc/signature/BUILD.bazel
+++ b/cc/signature/BUILD.bazel
@@ -418,7 +418,6 @@
         ":ecdsa_verify_key_manager",
         ":public_key_verify_factory",
         ":signature_config",
-        "//:config",
         "//:crypto_format",
         "//:keyset_handle",
         "//:public_key_verify",
@@ -462,7 +461,6 @@
         ":ecdsa_sign_key_manager",
         ":public_key_sign_factory",
         ":signature_config",
-        "//:config",
         "//:crypto_format",
         "//:keyset_handle",
         "//:public_key_sign",
@@ -680,12 +678,11 @@
         ":rsa_ssa_pss_verify_key_manager",
         ":signature_config",
         ":signature_key_templates",
-        "//:config",
         "//:keyset_handle",
         "//:public_key_sign",
         "//:public_key_verify",
         "//:registry",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
diff --git a/cc/signature/CMakeLists.txt b/cc/signature/CMakeLists.txt
index 7cbee77..e8db1e7 100644
--- a/cc/signature/CMakeLists.txt
+++ b/cc/signature/CMakeLists.txt
@@ -1,5 +1,7 @@
 tink_module(signature)
 
+add_subdirectory(internal)
+
 tink_cc_library(
   NAME public_key_verify_wrapper
   SRCS
@@ -320,7 +322,6 @@
     signature_config.cc
     signature_config.h
   DEPS
-    tink::signature::ecdsa_sign_key_manager
     tink::signature::ecdsa_verify_key_manager
     tink::signature::ed25519_sign_key_manager
     tink::signature::ed25519_verify_key_manager
@@ -336,6 +337,7 @@
     tink::config::config_util
     tink::config::tink_fips
     tink::util::status
+    tink::signature::ecdsa_sign_key_manager
     tink::proto::config_cc_proto
 )
 
@@ -398,7 +400,6 @@
     tink::signature::public_key_verify_factory
     tink::signature::signature_config
     gmock
-    tink::core::config
     tink::core::crypto_format
     tink::core::keyset_handle
     tink::core::public_key_verify
@@ -440,7 +441,6 @@
     tink::signature::public_key_sign_factory
     tink::signature::signature_config
     gmock
-    tink::core::config
     tink::core::crypto_format
     tink::core::keyset_handle
     tink::core::public_key_sign
@@ -651,12 +651,11 @@
     absl::memory
     absl::status
     crypto
-    tink::core::config
     tink::core::keyset_handle
     tink::core::public_key_sign
     tink::core::public_key_verify
     tink::core::registry
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::status
     tink::util::test_matchers
     tink::util::test_util
diff --git a/cc/signature/ecdsa_sign_key_manager.cc b/cc/signature/ecdsa_sign_key_manager.cc
index bea5312..0165feb 100644
--- a/cc/signature/ecdsa_sign_key_manager.cc
+++ b/cc/signature/ecdsa_sign_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/ecdsa_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -76,6 +77,12 @@
         "Deriving EC keys is not allowed in FIPS mode.");
   }
 
+  util::Status status =
+      ValidateVersion(ecdsa_key_format.version(), get_version());
+  if (!status.ok()) {
+    return status;
+  }
+
   // Extract enough random bytes from the input_stream to match the security
   // level of the EC. Note that the input_stream here must come from a PRF
   // and will not use more bytes than required by the security level of the EC.
diff --git a/cc/signature/ecdsa_sign_key_manager.h b/cc/signature/ecdsa_sign_key_manager.h
index 8f5e195..e618e79 100644
--- a/cc/signature/ecdsa_sign_key_manager.h
+++ b/cc/signature/ecdsa_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_SIGNATURE_ECDSA_SIGN_KEY_MANAGER_H_
 #define TINK_SIGNATURE_ECDSA_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/ecdsa_sign_key_manager_test.cc b/cc/signature/ecdsa_sign_key_manager_test.cc
index 2fe8a97..859925d 100644
--- a/cc/signature/ecdsa_sign_key_manager_test.cc
+++ b/cc/signature/ecdsa_sign_key_manager_test.cc
@@ -316,6 +316,25 @@
               test::StatusIs(absl::StatusCode::kInvalidArgument));
 }
 
+TEST(EcdsaSignKeyManagerTest, DeriveKeyWithInvalidKeyTemplateVersionFails) {
+  if (!internal::IsBoringSsl()) {
+    GTEST_SKIP()
+        << "Key derivation from an input stream is not supported with OpenSSL";
+  }
+  EcdsaKeyFormat format;
+  format.set_version(1);
+  EcdsaParams* params = format.mutable_params();
+  params->set_hash_type(HashType::SHA256);
+  params->set_curve(EllipticCurveType::NIST_P256);
+  params->set_encoding(EcdsaSignatureEncoding::DER);
+
+  util::IstreamInputStream input_stream{
+      absl::make_unique<std::stringstream>("tooshort")};
+
+  ASSERT_THAT(EcdsaSignKeyManager().DeriveKey(format, &input_stream).status(),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
 TEST(EcdsaSignKeyManagerTest, DeriveKeyInvalidCurve) {
   if (!internal::IsBoringSsl()) {
     GTEST_SKIP()
diff --git a/cc/signature/ecdsa_verify_key_manager.cc b/cc/signature/ecdsa_verify_key_manager.cc
index f72c05c..7660fb1 100644
--- a/cc/signature/ecdsa_verify_key_manager.cc
+++ b/cc/signature/ecdsa_verify_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/ecdsa_verify_key_manager.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
diff --git a/cc/signature/ecdsa_verify_key_manager.h b/cc/signature/ecdsa_verify_key_manager.h
index 5445c20..075a691 100644
--- a/cc/signature/ecdsa_verify_key_manager.h
+++ b/cc/signature/ecdsa_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_SIGNATURE_ECDSA_VERIFY_KEY_MANAGER_H_
 #define TINK_SIGNATURE_ECDSA_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/ed25519_sign_key_manager.cc b/cc/signature/ed25519_sign_key_manager.cc
index 661e0e7..962b138 100644
--- a/cc/signature/ed25519_sign_key_manager.cc
+++ b/cc/signature/ed25519_sign_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/signature/ed25519_sign_key_manager.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
diff --git a/cc/signature/ed25519_sign_key_manager.h b/cc/signature/ed25519_sign_key_manager.h
index a02b2ad..12ff8bf 100644
--- a/cc/signature/ed25519_sign_key_manager.h
+++ b/cc/signature/ed25519_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_SIGNATURE_ED25519_SIGN_KEY_MANAGER_H_
 #define TINK_SIGNATURE_ED25519_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/ed25519_sign_key_manager_test.cc b/cc/signature/ed25519_sign_key_manager_test.cc
index 65250de..060042d 100644
--- a/cc/signature/ed25519_sign_key_manager_test.cc
+++ b/cc/signature/ed25519_sign_key_manager_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/ed25519_sign_key_manager.h"
 
+#include <sstream>
 #include <string>
 
 #include "gmock/gmock.h"
diff --git a/cc/signature/ed25519_verify_key_manager.cc b/cc/signature/ed25519_verify_key_manager.cc
index a4ba197..92f52ab 100644
--- a/cc/signature/ed25519_verify_key_manager.cc
+++ b/cc/signature/ed25519_verify_key_manager.cc
@@ -16,6 +16,8 @@
 
 #include "tink/signature/ed25519_verify_key_manager.h"
 
+#include <memory>
+
 #include "absl/status/status.h"
 #include "absl/strings/string_view.h"
 #include "tink/public_key_verify.h"
diff --git a/cc/signature/ed25519_verify_key_manager.h b/cc/signature/ed25519_verify_key_manager.h
index 2d0640f..25b1b2d 100644
--- a/cc/signature/ed25519_verify_key_manager.h
+++ b/cc/signature/ed25519_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_SIGNATURE_ED25519_VERIFY_KEY_MANAGER_H_
 #define TINK_SIGNATURE_ED25519_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/internal/BUILD.bazel b/cc/signature/internal/BUILD.bazel
index 5b01f6e..3d679d8 100644
--- a/cc/signature/internal/BUILD.bazel
+++ b/cc/signature/internal/BUILD.bazel
@@ -1 +1,49 @@
+package(default_visibility = ["//:__subpackages__"])
+
 licenses(["notice"])
+
+cc_library(
+    name = "ecdsa_raw_sign_boringssl",
+    srcs = ["ecdsa_raw_sign_boringssl.cc"],
+    hdrs = ["ecdsa_raw_sign_boringssl.h"],
+    include_prefix = "tink/signature/internal",
+    deps = [
+        "//:public_key_sign",
+        "//internal:bn_util",
+        "//internal:ec_util",
+        "//internal:err_util",
+        "//internal:fips_utils",
+        "//internal:md_util",
+        "//internal:ssl_unique_ptr",
+        "//internal:util",
+        "//subtle:common_enums",
+        "//subtle:subtle_util_boringssl",
+        "//util:errors",
+        "//util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "ecdsa_raw_sign_boringssl_test",
+    size = "small",
+    srcs = ["ecdsa_raw_sign_boringssl_test.cc"],
+    tags = ["fips"],
+    deps = [
+        ":ecdsa_raw_sign_boringssl",
+        "//:public_key_sign",
+        "//:public_key_verify",
+        "//internal:ec_util",
+        "//internal:fips_utils",
+        "//subtle:common_enums",
+        "//subtle:ecdsa_verify_boringssl",
+        "//subtle:subtle_util_boringssl",
+        "//util:status",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_absl//absl/status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/signature/internal/CMakeLists.txt b/cc/signature/internal/CMakeLists.txt
index e69de29..0b898c1 100644
--- a/cc/signature/internal/CMakeLists.txt
+++ b/cc/signature/internal/CMakeLists.txt
@@ -0,0 +1,44 @@
+tink_module(signature::internal)
+
+tink_cc_library(
+  NAME ecdsa_raw_sign_boringssl
+  SRCS
+    ecdsa_raw_sign_boringssl.cc
+    ecdsa_raw_sign_boringssl.h
+  DEPS
+    absl::status
+    absl::strings
+    crypto
+    tink::core::public_key_sign
+    tink::internal::bn_util
+    tink::internal::ec_util
+    tink::internal::err_util
+    tink::internal::fips_utils
+    tink::internal::md_util
+    tink::internal::ssl_unique_ptr
+    tink::internal::util
+    tink::subtle::common_enums
+    tink::subtle::subtle_util_boringssl
+    tink::util::errors
+    tink::util::statusor
+)
+
+tink_cc_test(
+  NAME ecdsa_raw_sign_boringssl_test
+  SRCS
+    ecdsa_raw_sign_boringssl_test.cc
+  DEPS
+    tink::signature::internal::ecdsa_raw_sign_boringssl
+    gmock
+    absl::status
+    tink::core::public_key_sign
+    tink::core::public_key_verify
+    tink::internal::ec_util
+    tink::internal::fips_utils
+    tink::subtle::common_enums
+    tink::subtle::ecdsa_verify_boringssl
+    tink::subtle::subtle_util_boringssl
+    tink::util::status
+    tink::util::statusor
+    tink::util::test_matchers
+)
diff --git a/cc/signature/internal/ecdsa_raw_sign_boringssl.cc b/cc/signature/internal/ecdsa_raw_sign_boringssl.cc
new file mode 100644
index 0000000..366c966
--- /dev/null
+++ b/cc/signature/internal/ecdsa_raw_sign_boringssl.cc
@@ -0,0 +1,159 @@
+// Copyright 2017 Google Inc.
+//
+// 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
+//
+//     http://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 "tink/signature/internal/ecdsa_raw_sign_boringssl.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "openssl/bn.h"
+#include "openssl/ec.h"
+#include "openssl/ecdsa.h"
+#include "openssl/evp.h"
+#include "tink/internal/bn_util.h"
+#include "tink/internal/ec_util.h"
+#include "tink/internal/err_util.h"
+#include "tink/internal/md_util.h"
+#include "tink/internal/ssl_unique_ptr.h"
+#include "tink/internal/util.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/errors.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+// Transforms ECDSA DER signature encoding to IEEE_P1363 encoding.
+//
+// The IEEE_P1363 signature's format is r || s, where r and s are zero-padded
+// and have the same size in bytes as the order of the curve. For example, for
+// NIST P-256 curve, r and s are zero-padded to 32 bytes.
+//
+// The DER signature is encoded using ASN.1
+// (https://tools.ietf.org/html/rfc5480#appendix-A): ECDSA-Sig-Value :: =
+// SEQUENCE { r INTEGER, s INTEGER }. In particular, the encoding is: 0x30 ||
+// totalLength || 0x02 || r's length || r || 0x02 || s's length || s.
+crypto::tink::util::StatusOr<std::string> DerToIeee(absl::string_view der,
+                                                    const EC_KEY* key) {
+  size_t field_size_in_bytes =
+      (EC_GROUP_get_degree(EC_KEY_get0_group(key)) + 7) / 8;
+
+  const uint8_t* der_ptr = reinterpret_cast<const uint8_t*>(der.data());
+  // Note: d2i_ECDSA_SIG is deprecated in BoringSSL, but it isn't in OpenSSL.
+  internal::SslUniquePtr<ECDSA_SIG> ecdsa(
+      d2i_ECDSA_SIG(nullptr, &der_ptr, der.size()));
+  if (ecdsa == nullptr ||
+      der_ptr != reinterpret_cast<const uint8_t*>(der.data() + der.size())) {
+    return util::Status(absl::StatusCode::kInternal, "d2i_ECDSA_SIG failed");
+  }
+
+  const BIGNUM* r_bn;
+  const BIGNUM* s_bn;
+  ECDSA_SIG_get0(ecdsa.get(), &r_bn, &s_bn);
+  util::StatusOr<std::string> r =
+      internal::BignumToString(r_bn, field_size_in_bytes);
+  if (!r.ok()) {
+    return r.status();
+  }
+  util::StatusOr<std::string> s =
+      internal::BignumToString(s_bn, field_size_in_bytes);
+  if (!s.ok()) {
+    return s.status();
+  }
+  return absl::StrCat(*r, *s);
+}
+
+}  // namespace
+
+// static
+util::StatusOr<std::unique_ptr<EcdsaRawSignBoringSsl>>
+EcdsaRawSignBoringSsl::New(const subtle::SubtleUtilBoringSSL::EcKey& ec_key,
+                           subtle::EcdsaSignatureEncoding encoding) {
+  auto status = internal::CheckFipsCompatibility<EcdsaRawSignBoringSsl>();
+  if (!status.ok()) return status;
+
+  // Check curve.
+  util::StatusOr<internal::SslUniquePtr<EC_GROUP>> group =
+      internal::EcGroupFromCurveType(ec_key.curve);
+  if (!group.ok()) {
+    return group.status();
+  }
+  internal::SslUniquePtr<EC_KEY> key(EC_KEY_new());
+  EC_KEY_set_group(key.get(), group->get());
+
+  // Check key.
+  util::StatusOr<internal::SslUniquePtr<EC_POINT>> pub_key =
+      internal::GetEcPoint(ec_key.curve, ec_key.pub_x, ec_key.pub_y);
+  if (!pub_key.ok()) {
+    return pub_key.status();
+  }
+
+  if (!EC_KEY_set_public_key(key.get(), pub_key->get())) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Invalid public key: ", internal::GetSslErrors()));
+  }
+
+  internal::SslUniquePtr<BIGNUM> priv_key(
+      BN_bin2bn(ec_key.priv.data(), ec_key.priv.size(), nullptr));
+  if (!EC_KEY_set_private_key(key.get(), priv_key.get())) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Invalid private key: ", internal::GetSslErrors()));
+  }
+
+  return {
+      absl::WrapUnique(new EcdsaRawSignBoringSsl(std::move(key), encoding))};
+}
+
+util::StatusOr<std::string> EcdsaRawSignBoringSsl::Sign(
+    absl::string_view data) const {
+  // BoringSSL expects a non-null pointer for data,
+  // regardless of whether the size is 0.
+  data = internal::EnsureStringNonNull(data);
+
+  // Compute the raw signature.
+  std::vector<uint8_t> buffer(ECDSA_size(key_.get()));
+  unsigned int sig_length;
+  if (1 != ECDSA_sign(0 /* unused */,
+                      reinterpret_cast<const uint8_t*>(data.data()),
+                      data.size(), buffer.data(), &sig_length, key_.get())) {
+    return util::Status(absl::StatusCode::kInternal, "Signing failed.");
+  }
+
+  if (encoding_ == subtle::EcdsaSignatureEncoding::IEEE_P1363) {
+    auto status_or_sig = DerToIeee(
+        absl::string_view(reinterpret_cast<char*>(buffer.data()), sig_length),
+        key_.get());
+    if (!status_or_sig.ok()) {
+      return status_or_sig.status();
+    }
+    return status_or_sig.value();
+  }
+
+  return std::string(reinterpret_cast<char*>(buffer.data()), sig_length);
+}
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/internal/ecdsa_raw_sign_boringssl.h b/cc/signature/internal/ecdsa_raw_sign_boringssl.h
new file mode 100644
index 0000000..7c917c4
--- /dev/null
+++ b/cc/signature/internal/ecdsa_raw_sign_boringssl.h
@@ -0,0 +1,65 @@
+// Copyright 2017 Google Inc.
+//
+// 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
+//
+//     http://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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SIGNATURE_INTERNAL_ECDSA_RAW_SIGN_BORINGSSL_H_
+#define TINK_SIGNATURE_INTERNAL_ECDSA_RAW_SIGN_BORINGSSL_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "openssl/ec.h"
+#include "openssl/evp.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/internal/ssl_unique_ptr.h"
+#include "tink/public_key_sign.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+
+// ECDSA raw signing using Boring SSL, generating signatures in DER-encoding.
+class EcdsaRawSignBoringSsl : public PublicKeySign {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<EcdsaRawSignBoringSsl>>
+  New(const crypto::tink::internal::EcKey& ec_key,
+      subtle::EcdsaSignatureEncoding encoding);
+
+  // Computes the signature for 'data'.
+  crypto::tink::util::StatusOr<std::string> Sign(
+      absl::string_view data) const override;
+
+  static constexpr crypto::tink::internal::FipsCompatibility kFipsStatus =
+      crypto::tink::internal::FipsCompatibility::kRequiresBoringCrypto;
+
+ private:
+  EcdsaRawSignBoringSsl(internal::SslUniquePtr<EC_KEY> key,
+                        subtle::EcdsaSignatureEncoding encoding)
+      : key_(std::move(key)), encoding_(encoding) {}
+
+  internal::SslUniquePtr<EC_KEY> key_;
+  subtle::EcdsaSignatureEncoding encoding_;
+};
+
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SIGNATURE_INTERNAL_ECDSA_RAW_SIGN_BORINGSSL_H_
diff --git a/cc/signature/internal/ecdsa_raw_sign_boringssl_test.cc b/cc/signature/internal/ecdsa_raw_sign_boringssl_test.cc
new file mode 100644
index 0000000..aaf3221
--- /dev/null
+++ b/cc/signature/internal/ecdsa_raw_sign_boringssl_test.cc
@@ -0,0 +1,296 @@
+// Copyright 2017 Google Inc.
+//
+// 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
+//
+//      http://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 "tink/signature/internal/ecdsa_raw_sign_boringssl.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
+#include "tink/internal/ec_util.h"
+#include "tink/internal/fips_utils.h"
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/ecdsa_verify_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace internal {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Eq;
+using ::testing::Not;
+using ::testing::SizeIs;
+
+util::StatusOr<std::string> ComputeDigest(subtle::HashType hash_type,
+                                          absl::string_view data) {
+  util::StatusOr<const EVP_MD*> hash = internal::EvpHashFromHashType(hash_type);
+  if (!hash.ok()) return hash.status();
+
+  unsigned int digest_size;
+  uint8_t digest[EVP_MAX_MD_SIZE];
+  if (1 != EVP_Digest(data.data(), data.size(), digest, &digest_size, *hash,
+                      nullptr)) {
+    return util::Status(absl::StatusCode::kInternal,
+                        "Could not compute digest.");
+  }
+
+  return std::string(reinterpret_cast<const char*>(digest), digest_size);
+}
+
+TEST(EcdsaRawSignBoringSslTest, VerifySignature) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+  subtle::EcdsaSignatureEncoding encodings[2] = {
+      subtle::EcdsaSignatureEncoding::DER,
+      subtle::EcdsaSignatureEncoding::IEEE_P1363};
+  for (subtle::EcdsaSignatureEncoding encoding : encodings) {
+    util::StatusOr<EcKey> ec_key = subtle::SubtleUtilBoringSSL::GetNewEcKey(
+        subtle::EllipticCurveType::NIST_P256);
+    ASSERT_THAT(ec_key, IsOk());
+
+    util::StatusOr<std::unique_ptr<EcdsaRawSignBoringSsl>> signer =
+        EcdsaRawSignBoringSsl::New(*ec_key, encoding);
+    ASSERT_THAT(signer, IsOk());
+
+    util::StatusOr<std::unique_ptr<subtle::EcdsaVerifyBoringSsl>> verifier =
+        subtle::EcdsaVerifyBoringSsl::New(*ec_key, subtle::HashType::SHA256,
+                                          encoding);
+    ASSERT_THAT(verifier, IsOk());
+
+    std::string message = "some data to be signed";
+    util::StatusOr<std::string> message_digest =
+        ComputeDigest(subtle::HashType::SHA256, message);
+    ASSERT_THAT(message_digest, IsOk());
+    util::StatusOr<std::string> signature = (*signer)->Sign(*message_digest);
+    ASSERT_THAT(signature, IsOkAndHolds(Not(Eq(message))));
+    EXPECT_THAT((*verifier)->Verify(*signature, message), IsOk());
+  }
+}
+
+TEST(EcdsaRawSignBoringSslTest, VerifySignatureWithEmptyMessage) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+  subtle::EcdsaSignatureEncoding encodings[2] = {
+      subtle::EcdsaSignatureEncoding::DER,
+      subtle::EcdsaSignatureEncoding::IEEE_P1363};
+  for (subtle::EcdsaSignatureEncoding encoding : encodings) {
+    util::StatusOr<EcKey> ec_key = subtle::SubtleUtilBoringSSL::GetNewEcKey(
+        subtle::EllipticCurveType::NIST_P256);
+    ASSERT_THAT(ec_key, IsOk());
+
+    util::StatusOr<std::unique_ptr<EcdsaRawSignBoringSsl>> signer =
+        EcdsaRawSignBoringSsl::New(*ec_key, encoding);
+    ASSERT_THAT(signer, IsOk());
+
+    util::StatusOr<std::unique_ptr<subtle::EcdsaVerifyBoringSsl>> verifier =
+        subtle::EcdsaVerifyBoringSsl::New(*ec_key, subtle::HashType::SHA256,
+                                          encoding);
+    ASSERT_THAT(verifier, IsOk());
+
+    // Message is a null string_view.
+    const absl::string_view empty_message;
+    util::StatusOr<std::string> empty_message_digest =
+        ComputeDigest(subtle::HashType::SHA256, empty_message);
+    ASSERT_THAT(empty_message_digest, IsOk());
+    util::StatusOr<std::string> empty_msg_signature =
+        (*signer)->Sign(*empty_message_digest);
+    ASSERT_THAT(empty_msg_signature, IsOkAndHolds(Not(Eq(empty_message))));
+    EXPECT_THAT((*verifier)->Verify(*empty_msg_signature, empty_message),
+                IsOk());
+  }
+}
+
+TEST(EcdsaRawSignBoringSslTest, VerifyFailsWithInvalidMessageOrSignature) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+  subtle::EcdsaSignatureEncoding encodings[2] = {
+      subtle::EcdsaSignatureEncoding::DER,
+      subtle::EcdsaSignatureEncoding::IEEE_P1363};
+  for (subtle::EcdsaSignatureEncoding encoding : encodings) {
+    util::StatusOr<EcKey> ec_key = subtle::SubtleUtilBoringSSL::GetNewEcKey(
+        subtle::EllipticCurveType::NIST_P256);
+    ASSERT_THAT(ec_key, IsOk());
+
+    util::StatusOr<std::unique_ptr<EcdsaRawSignBoringSsl>> signer =
+        EcdsaRawSignBoringSsl::New(*ec_key, encoding);
+    ASSERT_THAT(signer, IsOk());
+
+    util::StatusOr<std::unique_ptr<subtle::EcdsaVerifyBoringSsl>> verifier =
+        subtle::EcdsaVerifyBoringSsl::New(*ec_key, subtle::HashType::SHA256,
+                                          encoding);
+    ASSERT_THAT(verifier, IsOk());
+
+    std::string message = "some data to be signed";
+    util::StatusOr<std::string> message_digest =
+        ComputeDigest(subtle::HashType::SHA256, message);
+    ASSERT_THAT(message_digest, IsOk());
+    util::StatusOr<std::string> signature = (*signer)->Sign(*message_digest);
+    ASSERT_THAT(signature, IsOkAndHolds(Not(Eq(message))));
+    EXPECT_THAT((*verifier)->Verify(*signature, message), IsOk());
+
+    EXPECT_THAT((*verifier)->Verify("some bad signature", message),
+                Not(IsOk()));
+    EXPECT_THAT((*verifier)->Verify(*signature, "some bad message"),
+                Not(IsOk()));
+  }
+}
+
+TEST(EcdsaRawSignBoringSslTest, VerifyFailsWhenEncodingDoesNotMatch) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+  subtle::EcdsaSignatureEncoding encodings[2] = {
+      subtle::EcdsaSignatureEncoding::DER,
+      subtle::EcdsaSignatureEncoding::IEEE_P1363};
+  for (subtle::EcdsaSignatureEncoding encoding : encodings) {
+    util::StatusOr<EcKey> ec_key = subtle::SubtleUtilBoringSSL::GetNewEcKey(
+        subtle::EllipticCurveType::NIST_P256);
+    ASSERT_THAT(ec_key, IsOk());
+
+    util::StatusOr<std::unique_ptr<EcdsaRawSignBoringSsl>> signer =
+        EcdsaRawSignBoringSsl::New(*ec_key, encoding);
+    ASSERT_THAT(signer, IsOk());
+
+    util::StatusOr<std::unique_ptr<subtle::EcdsaVerifyBoringSsl>> verifier =
+        subtle::EcdsaVerifyBoringSsl::New(
+            *ec_key, subtle::HashType::SHA256,
+            encoding == subtle::EcdsaSignatureEncoding::DER
+                ? subtle::EcdsaSignatureEncoding::IEEE_P1363
+                : subtle::EcdsaSignatureEncoding::DER);
+    ASSERT_THAT(verifier, IsOk());
+
+    std::string message = "some data to be signed";
+    util::StatusOr<std::string> message_digest =
+        ComputeDigest(subtle::HashType::SHA256, message);
+    ASSERT_THAT(message_digest, IsOk());
+    util::StatusOr<std::string> signature = (*signer)->Sign(*message_digest);
+    ASSERT_THAT(signature, IsOkAndHolds(Not(Eq(message))));
+    EXPECT_THAT((*verifier)->Verify(*signature, message), Not(IsOk()));
+  }
+}
+
+TEST(EcdsaRawSignBoringSslTest,
+     SignatureSizesAreCorrectWhenUsingIeeeP136Encoding) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+  subtle::EllipticCurveType curves[3] = {subtle::EllipticCurveType::NIST_P256,
+                                         subtle::EllipticCurveType::NIST_P384,
+                                         subtle::EllipticCurveType::NIST_P521};
+  for (subtle::EllipticCurveType curve : curves) {
+    util::StatusOr<EcKey> ec_key =
+        subtle::SubtleUtilBoringSSL::GetNewEcKey(curve);
+    ASSERT_THAT(ec_key, IsOk());
+
+    util::StatusOr<std::unique_ptr<EcdsaRawSignBoringSsl>> signer =
+        EcdsaRawSignBoringSsl::New(*ec_key,
+                                   subtle::EcdsaSignatureEncoding::IEEE_P1363);
+    ASSERT_THAT(signer, IsOk());
+
+    util::StatusOr<std::unique_ptr<subtle::EcdsaVerifyBoringSsl>> verifier =
+        subtle::EcdsaVerifyBoringSsl::New(
+            *ec_key, subtle::HashType::SHA256,
+            subtle::EcdsaSignatureEncoding::IEEE_P1363);
+    ASSERT_THAT(verifier, IsOk());
+
+    std::string message = "some data to be signed";
+    util::StatusOr<std::string> message_digest =
+        ComputeDigest(subtle::HashType::SHA256, message);
+    ASSERT_THAT(message_digest, IsOk());
+    util::StatusOr<std::string> signature = (*signer)->Sign(*message_digest);
+    ASSERT_THAT(signature, IsOkAndHolds(Not(Eq(message))));
+    EXPECT_THAT((*verifier)->Verify(*signature, message), IsOk());
+
+    // Check signature size.
+    util::StatusOr<int32_t> field_size_in_bytes =
+        internal::EcFieldSizeInBytes(curve);
+    ASSERT_THAT(field_size_in_bytes, IsOk());
+    EXPECT_THAT(*signature, SizeIs(2 * (*field_size_in_bytes)));
+  }
+}
+
+TEST(EcdsaRawSignBoringSslTest, CreateFailsWithBadPublicKey) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+  util::StatusOr<EcKey> ec_key = subtle::SubtleUtilBoringSSL::GetNewEcKey(
+      subtle::EllipticCurveType::NIST_P256);
+  ASSERT_THAT(ec_key, IsOk());
+
+  ec_key->pub_x += "corrupted public key x coordinate";
+  EXPECT_THAT(
+      EcdsaRawSignBoringSsl::New(*ec_key, subtle::EcdsaSignatureEncoding::DER),
+      Not(IsOk()));
+}
+
+// TODO(bleichen): add Wycheproof tests.
+
+// FIPS-only mode test
+TEST(EcdsaRawSignBoringSslTest, FipsFailWithoutBoringCrypto) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
+    GTEST_SKIP()
+        << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+
+  util::StatusOr<EcKey> p256_key = subtle::SubtleUtilBoringSSL::GetNewEcKey(
+      subtle::EllipticCurveType::NIST_P256);
+  ASSERT_THAT(p256_key, IsOk());
+  EXPECT_THAT(
+      EcdsaRawSignBoringSsl::New(*p256_key, subtle::EcdsaSignatureEncoding::DER)
+          .status(),
+      StatusIs(absl::StatusCode::kInternal));
+
+  util::StatusOr<EcKey> p384_key = subtle::SubtleUtilBoringSSL::GetNewEcKey(
+      subtle::EllipticCurveType::NIST_P384);
+  ASSERT_THAT(p384_key, IsOk());
+  EXPECT_THAT(
+      EcdsaRawSignBoringSsl::New(*p384_key, subtle::EcdsaSignatureEncoding::DER)
+          .status(),
+      StatusIs(absl::StatusCode::kInternal));
+
+  util::StatusOr<EcKey> p521_key = *subtle::SubtleUtilBoringSSL::GetNewEcKey(
+      subtle::EllipticCurveType::NIST_P521);
+  ASSERT_THAT(p521_key, IsOk());
+  EXPECT_THAT(
+      EcdsaRawSignBoringSsl::New(*p521_key, subtle::EcdsaSignatureEncoding::DER)
+          .status(),
+      StatusIs(absl::StatusCode::kInternal));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/public_key_sign_factory.cc b/cc/signature/public_key_sign_factory.cc
index e0fe718..f44be10 100644
--- a/cc/signature/public_key_sign_factory.cc
+++ b/cc/signature/public_key_sign_factory.cc
@@ -16,6 +16,8 @@
 
 #include "tink/signature/public_key_sign_factory.h"
 
+#include <memory>
+
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/public_key_sign.h"
diff --git a/cc/signature/public_key_sign_factory.h b/cc/signature/public_key_sign_factory.h
index 35e6e78..30c4b82 100644
--- a/cc/signature/public_key_sign_factory.h
+++ b/cc/signature/public_key_sign_factory.h
@@ -17,6 +17,8 @@
 #ifndef TINK_SIGNATURE_PUBLIC_KEY_SIGN_FACTORY_H_
 #define TINK_SIGNATURE_PUBLIC_KEY_SIGN_FACTORY_H_
 
+#include <memory>
+
 #include "absl/base/macros.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
diff --git a/cc/signature/public_key_sign_factory_test.cc b/cc/signature/public_key_sign_factory_test.cc
index 804002a..e5b2eb4 100644
--- a/cc/signature/public_key_sign_factory_test.cc
+++ b/cc/signature/public_key_sign_factory_test.cc
@@ -20,7 +20,6 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "tink/config.h"
 #include "tink/crypto_format.h"
 #include "tink/keyset_handle.h"
 #include "tink/public_key_sign.h"
@@ -33,7 +32,6 @@
 #include "proto/ecdsa.pb.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::TestKeysetHandle;
 using crypto::tink::test::AddTinkKey;
 using google::crypto::tink::EcdsaPrivateKey;
 using google::crypto::tink::EcdsaSignatureEncoding;
diff --git a/cc/signature/public_key_sign_wrapper.cc b/cc/signature/public_key_sign_wrapper.cc
index 8743396..aeab1cf 100644
--- a/cc/signature/public_key_sign_wrapper.cc
+++ b/cc/signature/public_key_sign_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/public_key_sign_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -63,7 +64,7 @@
   crypto::tink::util::StatusOr<std::string> Sign(
       absl::string_view data) const override;
 
-  ~PublicKeySignSetWrapper() override {}
+  ~PublicKeySignSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<PublicKeySign>> public_key_sign_set_;
diff --git a/cc/signature/public_key_sign_wrapper.h b/cc/signature/public_key_sign_wrapper.h
index ec58c48..f258dad 100644
--- a/cc/signature/public_key_sign_wrapper.h
+++ b/cc/signature/public_key_sign_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_SIGNATURE_PUBLIC_KEY_SIGN_WRAPPER_H_
 #define TINK_SIGNATURE_PUBLIC_KEY_SIGN_WRAPPER_H_
 
+#include <memory>
+
 #include "absl/strings/string_view.h"
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
diff --git a/cc/signature/public_key_sign_wrapper_test.cc b/cc/signature/public_key_sign_wrapper_test.cc
index 4059d61..db7f099 100644
--- a/cc/signature/public_key_sign_wrapper_test.cc
+++ b/cc/signature/public_key_sign_wrapper_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/public_key_sign_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/signature/public_key_verify_factory.cc b/cc/signature/public_key_verify_factory.cc
index dffa6d5..8747e44 100644
--- a/cc/signature/public_key_verify_factory.cc
+++ b/cc/signature/public_key_verify_factory.cc
@@ -16,6 +16,8 @@
 
 #include "tink/signature/public_key_verify_factory.h"
 
+#include <memory>
+
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/public_key_verify.h"
diff --git a/cc/signature/public_key_verify_factory.h b/cc/signature/public_key_verify_factory.h
index e039a04..af21525 100644
--- a/cc/signature/public_key_verify_factory.h
+++ b/cc/signature/public_key_verify_factory.h
@@ -17,6 +17,8 @@
 #ifndef TINK_SIGNATURE_PUBLIC_KEY_VERIFY_FACTORY_H_
 #define TINK_SIGNATURE_PUBLIC_KEY_VERIFY_FACTORY_H_
 
+#include <memory>
+
 #include "absl/base/macros.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
diff --git a/cc/signature/public_key_verify_factory_test.cc b/cc/signature/public_key_verify_factory_test.cc
index 94564c6..f302e43 100644
--- a/cc/signature/public_key_verify_factory_test.cc
+++ b/cc/signature/public_key_verify_factory_test.cc
@@ -20,7 +20,6 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "tink/config.h"
 #include "tink/crypto_format.h"
 #include "tink/keyset_handle.h"
 #include "tink/public_key_verify.h"
@@ -33,7 +32,6 @@
 #include "proto/ecdsa.pb.h"
 #include "proto/tink.pb.h"
 
-using crypto::tink::TestKeysetHandle;
 using crypto::tink::test::AddTinkKey;
 using google::crypto::tink::EcdsaPublicKey;
 using google::crypto::tink::EcdsaSignatureEncoding;
diff --git a/cc/signature/public_key_verify_wrapper.cc b/cc/signature/public_key_verify_wrapper.cc
index 840aadd..784c0d3 100644
--- a/cc/signature/public_key_verify_wrapper.cc
+++ b/cc/signature/public_key_verify_wrapper.cc
@@ -16,18 +16,19 @@
 
 #include "tink/signature/public_key_verify_wrapper.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "tink/crypto_format.h"
-#include "tink/internal/util.h"
-#include "tink/primitive_set.h"
-#include "tink/public_key_verify.h"
 #include "tink/internal/monitoring_util.h"
 #include "tink/internal/registry_impl.h"
+#include "tink/internal/util.h"
 #include "tink/monitoring/monitoring.h"
+#include "tink/primitive_set.h"
+#include "tink/public_key_verify.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
@@ -65,7 +66,7 @@
   crypto::tink::util::Status Verify(absl::string_view signature,
                                     absl::string_view data) const override;
 
-  ~PublicKeyVerifySetWrapper() override {}
+  ~PublicKeyVerifySetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set_;
diff --git a/cc/signature/public_key_verify_wrapper.h b/cc/signature/public_key_verify_wrapper.h
index e36d923..3563ec5 100644
--- a/cc/signature/public_key_verify_wrapper.h
+++ b/cc/signature/public_key_verify_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_SIGNATURE_PUBLIC_KEY_VERIFY_WRAPPER_H_
 #define TINK_SIGNATURE_PUBLIC_KEY_VERIFY_WRAPPER_H_
 
+#include <memory>
+
 #include "absl/strings/string_view.h"
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
diff --git a/cc/signature/public_key_verify_wrapper_test.cc b/cc/signature/public_key_verify_wrapper_test.cc
index 7a85945..90f4558 100644
--- a/cc/signature/public_key_verify_wrapper_test.cc
+++ b/cc/signature/public_key_verify_wrapper_test.cc
@@ -115,12 +115,12 @@
                                                     keyset_info.key_info(0));
     ASSERT_TRUE(entry_result.ok());
 
-    pk_verify.reset(new DummyPublicKeyVerify(signature_name_1));
+    pk_verify = std::make_unique<DummyPublicKeyVerify>(signature_name_1);
     entry_result = pk_verify_set->AddPrimitive(std::move(pk_verify),
                                                keyset_info.key_info(1));
     ASSERT_TRUE(entry_result.ok());
 
-    pk_verify.reset(new DummyPublicKeyVerify(signature_name_2));
+    pk_verify = std::make_unique<DummyPublicKeyVerify>(signature_name_2);
     entry_result = pk_verify_set->AddPrimitive(std::move(pk_verify),
                                                keyset_info.key_info(2));
     ASSERT_TRUE(entry_result.ok());
diff --git a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
index cd4c1fa..43c2753 100644
--- a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
+++ b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.h b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.h
index ea42ca7..2448b76 100644
--- a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.h
+++ b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_SIGNATURE_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
 #define TINK_SIGNATURE_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/rsa_ssa_pkcs1_verify_key_manager.cc b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.cc
index 06f4964..8a62115 100644
--- a/cc/signature/rsa_ssa_pkcs1_verify_key_manager.cc
+++ b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/strings/str_cat.h"
diff --git a/cc/signature/rsa_ssa_pkcs1_verify_key_manager.h b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.h
index 8519021..7c1d0cd 100644
--- a/cc/signature/rsa_ssa_pkcs1_verify_key_manager.h
+++ b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.h
@@ -18,6 +18,7 @@
 #define TINK_SIGNATURE_RSA_SSA_PKCS1_VERIFY_KEY_MANAGER_H_
 
 #include <algorithm>
+#include <memory>
 #include <string>
 #include <vector>
 
diff --git a/cc/signature/rsa_ssa_pss_sign_key_manager.cc b/cc/signature/rsa_ssa_pss_sign_key_manager.cc
index 8d6bd25..18058ce 100644
--- a/cc/signature/rsa_ssa_pss_sign_key_manager.cc
+++ b/cc/signature/rsa_ssa_pss_sign_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/signature/rsa_ssa_pss_sign_key_manager.h b/cc/signature/rsa_ssa_pss_sign_key_manager.h
index 14618de..61ad38b 100644
--- a/cc/signature/rsa_ssa_pss_sign_key_manager.h
+++ b/cc/signature/rsa_ssa_pss_sign_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_SIGNATURE_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
 #define TINK_SIGNATURE_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/rsa_ssa_pss_verify_key_manager.cc b/cc/signature/rsa_ssa_pss_verify_key_manager.cc
index 7c7aa8c..abe4806 100644
--- a/cc/signature/rsa_ssa_pss_verify_key_manager.cc
+++ b/cc/signature/rsa_ssa_pss_verify_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
diff --git a/cc/signature/rsa_ssa_pss_verify_key_manager.h b/cc/signature/rsa_ssa_pss_verify_key_manager.h
index 3fc234a..5642dcd 100644
--- a/cc/signature/rsa_ssa_pss_verify_key_manager.h
+++ b/cc/signature/rsa_ssa_pss_verify_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
 #define TINK_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/memory/memory.h"
diff --git a/cc/signature/signature_config.cc b/cc/signature/signature_config.cc
index ec7f920..206b3f1 100644
--- a/cc/signature/signature_config.cc
+++ b/cc/signature/signature_config.cc
@@ -20,7 +20,6 @@
 #include "tink/config/config_util.h"
 #include "tink/config/tink_fips.h"
 #include "tink/registry.h"
-#include "tink/signature/ecdsa_sign_key_manager.h"
 #include "tink/signature/ecdsa_verify_key_manager.h"
 #include "tink/signature/ed25519_sign_key_manager.h"
 #include "tink/signature/ed25519_verify_key_manager.h"
@@ -31,20 +30,13 @@
 #include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
 #include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
 #include "tink/util/status.h"
+#include "tink/signature/ecdsa_sign_key_manager.h"
 #include "proto/config.pb.h"
 
-using google::crypto::tink::RegistryConfig;
-
 namespace crypto {
 namespace tink {
 
 // static
-const google::crypto::tink::RegistryConfig& SignatureConfig::Latest() {
-  static const RegistryConfig* config = new RegistryConfig();
-  return *config;
-}
-
-// static
 util::Status SignatureConfig::Register() {
   // Register primitive wrappers.
   auto status = Registry::RegisterPrimitiveWrapper(
diff --git a/cc/signature/signature_config.h b/cc/signature/signature_config.h
index ca9dab4..d1c333a 100644
--- a/cc/signature/signature_config.h
+++ b/cc/signature/signature_config.h
@@ -37,16 +37,6 @@
 //
 class SignatureConfig {
  public:
-  static constexpr char kPublicKeySignCatalogueName[] = "TinkPublicKeySign";
-  static constexpr char kPublicKeyVerifyCatalogueName[] = "TinkPublicKeyVerify";
-  static constexpr char kPublicKeySignPrimitiveName[] = "PublicKeySign";
-  static constexpr char kPublicKeyVerifyPrimitiveName[] = "PublicKeyVerify";
-
-  // Returns config with implementations of PublicKeySign and PublicKeyVerify
-  // supported in the current Tink release.
-  ABSL_DEPRECATED("This is not supported anymore.")
-  static const google::crypto::tink::RegistryConfig& Latest();
-
   // Registers PublicKeySign and PublicKeyVerify primitive wrappers, and key
   // managers for all implementations of PublicKeySign and PublicKeyVerify from
   // the current Tink release.
diff --git a/cc/signature/signature_config_test.cc b/cc/signature/signature_config_test.cc
index dbeb022..d0f9b02 100644
--- a/cc/signature/signature_config_test.cc
+++ b/cc/signature/signature_config_test.cc
@@ -25,8 +25,7 @@
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "openssl/crypto.h"
-#include "tink/config.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/keyset_handle.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
@@ -54,7 +53,7 @@
 };
 
 TEST_F(SignatureConfigTest, testBasic) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -81,7 +80,7 @@
 // Tests that the PublicKeySignWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(SignatureConfigTest, PublicKeySignWrapperRegistered) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -117,7 +116,7 @@
 // Tests that the PublicKeyVerifyWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(SignatureConfigTest, PublicKeyVerifyWrapperRegistered) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Not supported if FIPS-mode is used and BoringCrypto is "
                     "not available";
   }
@@ -148,7 +147,7 @@
 
 // FIPS-only mode tests
 TEST_F(SignatureConfigTest, RegisterNonFipsTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode with BoringCrypto.";
   }
 
@@ -173,7 +172,7 @@
 }
 
 TEST_F(SignatureConfigTest, RegisterFipsValidTemplates) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Only supported in FIPS-only mode with BoringCrypto.";
   }
 
diff --git a/cc/signature/signature_key_templates.cc b/cc/signature/signature_key_templates.cc
index a95c3bf..e2e33c9 100644
--- a/cc/signature/signature_key_templates.cc
+++ b/cc/signature/signature_key_templates.cc
@@ -16,6 +16,8 @@
 
 #include "tink/signature/signature_key_templates.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "openssl/bn.h"
diff --git a/cc/signature/signature_pem_keyset_reader.cc b/cc/signature/signature_pem_keyset_reader.cc
index 0802e0b..62421be 100644
--- a/cc/signature/signature_pem_keyset_reader.cc
+++ b/cc/signature/signature_pem_keyset_reader.cc
@@ -51,7 +51,6 @@
 
 using ::google::crypto::tink::EcdsaParams;
 using ::google::crypto::tink::EcdsaPublicKey;
-using ::google::crypto::tink::EcdsaSignatureEncoding;
 using ::google::crypto::tink::EllipticCurveType;
 using ::google::crypto::tink::EncryptedKeyset;
 using ::google::crypto::tink::HashType;
diff --git a/cc/signature/signature_pem_keyset_reader.h b/cc/signature/signature_pem_keyset_reader.h
index bc26ac7..d0f47df 100644
--- a/cc/signature/signature_pem_keyset_reader.h
+++ b/cc/signature/signature_pem_keyset_reader.h
@@ -17,6 +17,7 @@
 #ifndef TINK_SIGNATURE_SIGNATURE_PEM_KEYSET_READER_H_
 #define TINK_SIGNATURE_SIGNATURE_PEM_KEYSET_READER_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/cc/signature/signature_pem_keyset_reader_test.cc b/cc/signature/signature_pem_keyset_reader_test.cc
index da8ec90..ff4092b 100644
--- a/cc/signature/signature_pem_keyset_reader_test.cc
+++ b/cc/signature/signature_pem_keyset_reader_test.cc
@@ -227,6 +227,18 @@
   return private_key_proto;
 }
 
+PemKey CreatePemKey(absl::string_view serialized_key,
+                    crypto::tink::PemKeyType key_type,
+                    crypto::tink::PemAlgorithm algorithm,
+                    size_t key_size_in_bits,
+                    google::crypto::tink::HashType hash_type) {
+  PemKey pem_key = {
+      /*serialized_key=*/std::string(serialized_key),
+      /*parameters=*/{key_type, algorithm, key_size_in_bits, hash_type},
+  };
+  return pem_key;
+}
+
 // Verify check on PEM array size not zero before creating a reader.
 TEST(SignaturePemKeysetReaderTest, BuildEmptyPemArray) {
   auto builder = SignaturePemKeysetReaderBuilder(
@@ -240,11 +252,9 @@
 TEST(SignaturePemKeysetReaderTest, ReadEncryptedUnsupported) {
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
-  builder.Add({.serialized_key = std::string(kRsaPublicKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA384}});
+  builder.Add(CreatePemKey(kRsaPublicKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA384));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -260,16 +270,12 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kRsaPublicKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA384}});
-  builder.Add({.serialized_key = std::string(kRsaPublicKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kRsaPublicKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA384));
+  builder.Add(CreatePemKey(kRsaPublicKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA256));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -330,16 +336,12 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_SIGN);
 
-  builder.Add({.serialized_key = std::string(kRsaPrivateKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA256}});
-  builder.Add({.serialized_key = std::string(kRsaPrivateKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA384}});
+  builder.Add(CreatePemKey(kRsaPrivateKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA256));
+  builder.Add(CreatePemKey(kRsaPrivateKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA384));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -399,11 +401,9 @@
 TEST(SignaturePemKeysetReaderTest, ReadRsaPrivateKeyKeyTypeMismatch) {
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_SIGN);
-  builder.Add({.serialized_key = std::string(kRsaPublicKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA384}});
+  builder.Add(CreatePemKey(kRsaPublicKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA384));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -420,11 +420,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kRsaPrivateKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kRsaPrivateKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA256));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -440,11 +438,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kRsaPublicKey1024),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 1024,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kRsaPublicKey1024, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/1024,
+                           HashType::SHA256));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -461,11 +457,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kRsaPublicKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 3072,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kRsaPublicKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/3072,
+                           HashType::SHA256));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -481,11 +475,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kRsaPublicKey2048),
-               .parameters = {.key_type = PemKeyType::PEM_RSA,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 2048,
-                              .hash_type = HashType::SHA1}});
+  builder.Add(CreatePemKey(kRsaPublicKey2048, PemKeyType::PEM_RSA,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/2048,
+                           HashType::SHA1));
 
   auto keyset_reader_or = builder.Build();
   ASSERT_THAT(keyset_reader_or, IsOk());
@@ -500,17 +492,13 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kEcdsaP256PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::ECDSA_IEEE,
-                              .key_size_in_bits = 256,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kEcdsaP256PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::ECDSA_IEEE, /*key_size_in_bits=*/256,
+                           HashType::SHA256));
 
-  builder.Add({.serialized_key = std::string(kEcdsaP256PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::ECDSA_DER,
-                              .key_size_in_bits = 256,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kEcdsaP256PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::ECDSA_DER, /*key_size_in_bits=*/256,
+                           HashType::SHA256));
 
   auto reader = builder.Build();
   ASSERT_THAT(reader, IsOk());
@@ -565,11 +553,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kEcdsaP256PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::ECDSA_IEEE,
-                              .key_size_in_bits = 256,
-                              .hash_type = HashType::SHA512}});
+  builder.Add(CreatePemKey(kEcdsaP256PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::ECDSA_IEEE, /*key_size_in_bits=*/256,
+                           HashType::SHA512));
 
   auto reader = builder.Build();
   ASSERT_THAT(reader, IsOk());
@@ -582,11 +568,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kEcdsaP256PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::ECDSA_IEEE,
-                              .key_size_in_bits = 512,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kEcdsaP256PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::ECDSA_IEEE, /*key_size_in_bits=*/512,
+                           HashType::SHA256));
 
   auto reader = builder.Build();
   ASSERT_THAT(reader, IsOk());
@@ -599,11 +583,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kEcdsaP256PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::RSASSA_PSS,
-                              .key_size_in_bits = 256,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kEcdsaP256PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::RSASSA_PSS, /*key_size_in_bits=*/256,
+                           HashType::SHA256));
 
   auto reader = builder.Build();
   ASSERT_THAT(reader, IsOk());
@@ -616,11 +598,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kEd25519PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::ECDSA_IEEE,
-                              .key_size_in_bits = 256,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kEd25519PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::ECDSA_IEEE, /*key_size_in_bits=*/256,
+                           HashType::SHA256));
 
   auto reader = builder.Build();
   ASSERT_THAT(reader, IsOk());
@@ -633,11 +613,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kSecp256k1PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::ECDSA_IEEE,
-                              .key_size_in_bits = 256,
-                              .hash_type = HashType::SHA256}});
+  builder.Add(CreatePemKey(kSecp256k1PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::ECDSA_IEEE, /*key_size_in_bits=*/256,
+                           HashType::SHA256));
 
   auto reader = builder.Build();
   ASSERT_THAT(reader, IsOk());
@@ -664,11 +642,9 @@
   auto builder = SignaturePemKeysetReaderBuilder(
       SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
 
-  builder.Add({.serialized_key = std::string(kEcdsaP384PublicKey),
-               .parameters = {.key_type = PemKeyType::PEM_EC,
-                              .algorithm = PemAlgorithm::ECDSA_IEEE,
-                              .key_size_in_bits = 384,
-                              .hash_type = HashType::SHA384}});
+  builder.Add(CreatePemKey(kEcdsaP384PublicKey, PemKeyType::PEM_EC,
+                           PemAlgorithm::ECDSA_IEEE, /*key_size_in_bits=*/384,
+                           HashType::SHA384));
 
   auto reader = builder.Build();
   ASSERT_THAT(reader, IsOk());
diff --git a/cc/streaming_aead.h b/cc/streaming_aead.h
index 139bfdb..81b6333 100644
--- a/cc/streaming_aead.h
+++ b/cc/streaming_aead.h
@@ -77,7 +77,7 @@
       std::unique_ptr<crypto::tink::RandomAccessStream> ciphertext_source,
       absl::string_view associated_data) const = 0;
 
-  virtual ~StreamingAead() {}
+  virtual ~StreamingAead() = default;
 };
 
 }  // namespace tink
diff --git a/cc/streaming_mac.h b/cc/streaming_mac.h
index 037b148..3ca4f69 100644
--- a/cc/streaming_mac.h
+++ b/cc/streaming_mac.h
@@ -44,7 +44,7 @@
   virtual util::StatusOr<std::unique_ptr<OutputStreamWithResult<util::Status>>>
   NewVerifyMacOutputStream(const std::string& mac_value) const = 0;
 
-  virtual ~StreamingMac() {}
+  virtual ~StreamingMac() = default;
 };
 
 }  // namespace tink
diff --git a/cc/streamingaead/BUILD.bazel b/cc/streamingaead/BUILD.bazel
index a1d469b..8cf9667 100644
--- a/cc/streamingaead/BUILD.bazel
+++ b/cc/streamingaead/BUILD.bazel
@@ -202,17 +202,24 @@
     size = "small",
     srcs = ["streaming_aead_wrapper_test.cc"],
     deps = [
+        ":aes_gcm_hkdf_streaming_key_manager",
+        ":streaming_aead_config",
         ":streaming_aead_wrapper",
         "//:input_stream",
+        "//:insecure_secret_key_access",
         "//:output_stream",
         "//:primitive_set",
+        "//:proto_keyset_format",
         "//:random_access_stream",
         "//:streaming_aead",
+        "//internal:test_random_access_stream",
+        "//proto:aes_gcm_hkdf_streaming_cc_proto",
+        "//proto:common_cc_proto",
         "//proto:tink_cc_proto",
         "//subtle:random",
+        "//subtle:streaming_aead_test_util",
         "//subtle:test_util",
         "//util:buffer",
-        "//util:file_random_access_stream",
         "//util:istream_input_stream",
         "//util:ostream_output_stream",
         "//util:status",
@@ -308,7 +315,6 @@
         ":aes_gcm_hkdf_streaming_key_manager",
         ":streaming_aead_config",
         ":streaming_aead_key_templates",
-        "//:config",
         "//:keyset_handle",
         "//:registry",
         "//:streaming_aead",
@@ -376,10 +382,10 @@
         "//:primitive_set",
         "//:random_access_stream",
         "//:streaming_aead",
+        "//internal:test_random_access_stream",
         "//proto:tink_cc_proto",
         "//subtle:random",
         "//subtle:test_util",
-        "//util:file_random_access_stream",
         "//util:ostream_output_stream",
         "//util:status",
         "//util:test_matchers",
@@ -418,10 +424,8 @@
     deps = [
         ":shared_random_access_stream",
         "//:random_access_stream",
-        "//util:buffer",
-        "//util:file_random_access_stream",
-        "//util:status",
-        "//util:test_util",
+        "//internal:test_random_access_stream",
+        "//subtle:random",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
diff --git a/cc/streamingaead/CMakeLists.txt b/cc/streamingaead/CMakeLists.txt
index d4d817e..889e017 100644
--- a/cc/streamingaead/CMakeLists.txt
+++ b/cc/streamingaead/CMakeLists.txt
@@ -188,25 +188,32 @@
   SRCS
     streaming_aead_wrapper_test.cc
   DEPS
+    tink::streamingaead::aes_gcm_hkdf_streaming_key_manager
+    tink::streamingaead::streaming_aead_config
     tink::streamingaead::streaming_aead_wrapper
     gmock
     absl::memory
     absl::status
     absl::strings
     tink::core::input_stream
+    tink::core::insecure_secret_key_access
     tink::core::output_stream
     tink::core::primitive_set
+    tink::core::proto_keyset_format
     tink::core::random_access_stream
     tink::core::streaming_aead
+    tink::internal::test_random_access_stream
     tink::subtle::random
+    tink::subtle::streaming_aead_test_util
     tink::subtle::test_util
     tink::util::buffer
-    tink::util::file_random_access_stream
     tink::util::istream_input_stream
     tink::util::ostream_output_stream
     tink::util::status
     tink::util::test_matchers
     tink::util::test_util
+    tink::proto::aes_gcm_hkdf_streaming_cc_proto
+    tink::proto::common_cc_proto
     tink::proto::tink_cc_proto
 )
 
@@ -292,7 +299,6 @@
     gmock
     absl::memory
     absl::status
-    tink::core::config
     tink::core::keyset_handle
     tink::core::registry
     tink::core::streaming_aead
@@ -358,9 +364,9 @@
     tink::core::primitive_set
     tink::core::random_access_stream
     tink::core::streaming_aead
+    tink::internal::test_random_access_stream
     tink::subtle::random
     tink::subtle::test_util
-    tink::util::file_random_access_stream
     tink::util::ostream_output_stream
     tink::util::status
     tink::util::test_matchers
@@ -397,8 +403,6 @@
     absl::memory
     absl::strings
     tink::core::random_access_stream
-    tink::util::buffer
-    tink::util::file_random_access_stream
-    tink::util::status
-    tink::util::test_util
+    tink::internal::test_random_access_stream
+    tink::subtle::random
 )
diff --git a/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.cc b/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.cc
index a5e5eee..020e4ba 100644
--- a/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.cc
+++ b/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.cc
@@ -71,6 +71,10 @@
     return Status(absl::StatusCode::kInvalidArgument,
                   "ciphertext_segment_size too small");
   }
+  if (params.ciphertext_segment_size() > 0x7fffffff) {
+    return Status(absl::StatusCode::kInvalidArgument,
+                  "ciphertext_segment_size too big");
+  }
   return ValidateAesKeySize(params.derived_key_size());
 }
 
diff --git a/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.h b/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.h
index 859dd18..1c0ea91 100644
--- a/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.h
+++ b/cc/streamingaead/aes_ctr_hmac_streaming_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_STREAMINGAEAD_AES_CTR_HMAC_STREAMING_KEY_MANAGER_H_
 #define TINK_STREAMINGAEAD_AES_CTR_HMAC_STREAMING_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -97,7 +98,7 @@
       const google::crypto::tink::AesCtrHmacStreamingKeyFormat& key_format,
       InputStream* input_stream) const override;
 
-  ~AesCtrHmacStreamingKeyManager() override {}
+  ~AesCtrHmacStreamingKeyManager() override = default;
 
  private:
   const std::string key_type_ = absl::StrCat(
diff --git a/cc/streamingaead/aes_ctr_hmac_streaming_key_manager_test.cc b/cc/streamingaead/aes_ctr_hmac_streaming_key_manager_test.cc
index f962c3f..231ea46 100644
--- a/cc/streamingaead/aes_ctr_hmac_streaming_key_manager_test.cc
+++ b/cc/streamingaead/aes_ctr_hmac_streaming_key_manager_test.cc
@@ -48,7 +48,6 @@
 using ::google::crypto::tink::AesCtrHmacStreamingKey;
 using ::google::crypto::tink::AesCtrHmacStreamingKeyFormat;
 using ::google::crypto::tink::HashType;
-using ::google::crypto::tink::KeyData;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
@@ -226,6 +225,20 @@
                        HasSubstr("ciphertext_segment_size")));
 }
 
+TEST(AesCtrHmacStreamingKeyManagerTest, ValidateKeyFormatTooLargeSegment) {
+  AesCtrHmacStreamingKeyFormat key_format;
+  key_format.set_key_size(32);
+  key_format.mutable_params()->set_derived_key_size(32);
+  key_format.mutable_params()->set_hkdf_hash_type(HashType::SHA256);
+  key_format.mutable_params()->set_ciphertext_segment_size(2147483648);
+  key_format.mutable_params()->mutable_hmac_params()->
+      set_hash(HashType::SHA256);
+  key_format.mutable_params()->mutable_hmac_params()->set_tag_size(32);
+  EXPECT_THAT(AesCtrHmacStreamingKeyManager().ValidateKeyFormat(key_format),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("ciphertext_segment_size too big")));
+}
+
 TEST(AesCtrHmacStreamingKeyManagerTest, CreateKey) {
   AesCtrHmacStreamingKeyFormat key_format;
   key_format.set_key_size(32);
diff --git a/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager.h b/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager.h
index 418e1a0..03f5a79 100644
--- a/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager.h
+++ b/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager.h
@@ -16,6 +16,7 @@
 #ifndef TINK_STREAMINGAEAD_AES_GCM_HKDF_STREAMING_KEY_MANAGER_H_
 #define TINK_STREAMINGAEAD_AES_GCM_HKDF_STREAMING_KEY_MANAGER_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -93,7 +94,7 @@
       const google::crypto::tink::AesGcmHkdfStreamingKeyFormat& key_format,
       InputStream* input_stream) const override;
 
-  ~AesGcmHkdfStreamingKeyManager() override {}
+  ~AesGcmHkdfStreamingKeyManager() override = default;
 
  private:
   const std::string key_type_ = absl::StrCat(
diff --git a/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager_test.cc b/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager_test.cc
index 183012c..ea2d10f 100644
--- a/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager_test.cc
+++ b/cc/streamingaead/aes_gcm_hkdf_streaming_key_manager_test.cc
@@ -51,7 +51,6 @@
 using ::google::crypto::tink::AesGcmHkdfStreamingKey;
 using ::google::crypto::tink::AesGcmHkdfStreamingKeyFormat;
 using ::google::crypto::tink::HashType;
-using ::google::crypto::tink::KeyData;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
diff --git a/cc/streamingaead/buffered_input_stream.cc b/cc/streamingaead/buffered_input_stream.cc
index 5654a4f..acbba47 100644
--- a/cc/streamingaead/buffered_input_stream.cc
+++ b/cc/streamingaead/buffered_input_stream.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <cstring>
+#include <memory>
 #include <utility>
 #include <vector>
 
@@ -135,9 +136,7 @@
   return status_;
 }
 
-
-BufferedInputStream::~BufferedInputStream() {
-}
+BufferedInputStream::~BufferedInputStream() = default;
 
 int64_t BufferedInputStream::Position() const {
   if (direct_access_) return input_stream_->Position();
diff --git a/cc/streamingaead/buffered_input_stream_test.cc b/cc/streamingaead/buffered_input_stream_test.cc
index ff80d8b..14792a3 100644
--- a/cc/streamingaead/buffered_input_stream_test.cc
+++ b/cc/streamingaead/buffered_input_stream_test.cc
@@ -17,6 +17,7 @@
 #include "tink/streamingaead/buffered_input_stream.h"
 
 #include <algorithm>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
diff --git a/cc/streamingaead/decrypting_input_stream.cc b/cc/streamingaead/decrypting_input_stream.cc
index d09d998..c5e459d 100644
--- a/cc/streamingaead/decrypting_input_stream.cc
+++ b/cc/streamingaead/decrypting_input_stream.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <cstring>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -42,6 +43,8 @@
 using util::Status;
 using util::StatusOr;
 
+using StreamingAeadEntry = PrimitiveSet<StreamingAead>::Entry<StreamingAead>;
+
 // static
 StatusOr<std::unique_ptr<InputStream>> DecryptingInputStream::New(
     std::shared_ptr<PrimitiveSet<StreamingAead>> primitives,
@@ -68,12 +71,10 @@
   }
   // Matching has not been attempted yet, so try it now.
   attempted_matching_ = true;
-  auto raw_primitives_result = primitives_->get_raw_primitives();
-  if (!raw_primitives_result.ok()) {
-    return Status(absl::StatusCode::kInternal, "No RAW primitives found");
-  }
-  for (auto& primitive : *(raw_primitives_result.value())) {
-    StreamingAead& streaming_aead = primitive->get_primitive();
+  std::vector<StreamingAeadEntry*> all_primitives = primitives_->get_all();
+
+  for (const StreamingAeadEntry* entry : all_primitives) {
+    StreamingAead& streaming_aead = entry->get_primitive();
     auto shared_ct = absl::make_unique<SharedInputStream>(
         buffered_ct_source_.get());
     auto decrypting_stream_result = streaming_aead.NewDecryptingStream(
diff --git a/cc/streamingaead/decrypting_input_stream.h b/cc/streamingaead/decrypting_input_stream.h
index 61bb2e6..ae3072f 100644
--- a/cc/streamingaead/decrypting_input_stream.h
+++ b/cc/streamingaead/decrypting_input_stream.h
@@ -48,7 +48,7 @@
       std::unique_ptr<crypto::tink::InputStream> ciphertext_source,
       absl::string_view associated_data);
 
-  ~DecryptingInputStream() override {}
+  ~DecryptingInputStream() override = default;
   util::StatusOr<int> Next(const void** data) override;
   void BackUp(int count) override;
   int64_t Position() const override;
diff --git a/cc/streamingaead/decrypting_input_stream_test.cc b/cc/streamingaead/decrypting_input_stream_test.cc
index 0918ac8..04368ce 100644
--- a/cc/streamingaead/decrypting_input_stream_test.cc
+++ b/cc/streamingaead/decrypting_input_stream_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/streamingaead/decrypting_input_stream.h"
 
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
diff --git a/cc/streamingaead/decrypting_random_access_stream.cc b/cc/streamingaead/decrypting_random_access_stream.cc
index df7e9a1..6a5e72e 100644
--- a/cc/streamingaead/decrypting_random_access_stream.cc
+++ b/cc/streamingaead/decrypting_random_access_stream.cc
@@ -16,7 +16,9 @@
 
 #include "tink/streamingaead/decrypting_random_access_stream.h"
 
+#include <memory>
 #include <utility>
+#include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
@@ -39,6 +41,8 @@
 using util::Status;
 using util::StatusOr;
 
+using StreamingAeadEntry = PrimitiveSet<StreamingAead>::Entry<StreamingAead>;
+
 // static
 StatusOr<std::unique_ptr<RandomAccessStream>> DecryptingRandomAccessStream::New(
     std::shared_ptr<PrimitiveSet<StreamingAead>> primitives,
@@ -97,12 +101,9 @@
                   "Did not find a decrypter matching the ciphertext stream.");
   }
   attempted_matching_ = true;
-  auto raw_primitives_result = primitives_->get_raw_primitives();
-  if (!raw_primitives_result.ok()) {
-    return Status(absl::StatusCode::kInternal, "No RAW primitives found");
-  }
-  for (auto& primitive : *(raw_primitives_result.value())) {
-    StreamingAead& streaming_aead = primitive->get_primitive();
+  std::vector<StreamingAeadEntry*> all_primitives = primitives_->get_all();
+  for (const StreamingAeadEntry* entry : all_primitives) {
+    StreamingAead& streaming_aead = entry->get_primitive();
     auto shared_ct = absl::make_unique<SharedRandomAccessStream>(
         ciphertext_source_.get());
     auto decrypting_stream_result =
diff --git a/cc/streamingaead/decrypting_random_access_stream.h b/cc/streamingaead/decrypting_random_access_stream.h
index a78a719..ad409a1 100644
--- a/cc/streamingaead/decrypting_random_access_stream.h
+++ b/cc/streamingaead/decrypting_random_access_stream.h
@@ -50,7 +50,7 @@
       std::unique_ptr<crypto::tink::RandomAccessStream> ciphertext_source,
       absl::string_view associated_data);
 
-  ~DecryptingRandomAccessStream() override {}
+  ~DecryptingRandomAccessStream() override = default;
   crypto::tink::util::Status PRead(int64_t position, int count,
       crypto::tink::util::Buffer* dest_buffer) override;
   crypto::tink::util::StatusOr<int64_t> size() override;
diff --git a/cc/streamingaead/decrypting_random_access_stream_test.cc b/cc/streamingaead/decrypting_random_access_stream_test.cc
index 496fbbf..232e2db 100644
--- a/cc/streamingaead/decrypting_random_access_stream_test.cc
+++ b/cc/streamingaead/decrypting_random_access_stream_test.cc
@@ -16,23 +16,25 @@
 
 #include "tink/streamingaead/decrypting_random_access_stream.h"
 
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "tink/internal/test_random_access_stream.h"
 #include "tink/output_stream.h"
 #include "tink/primitive_set.h"
 #include "tink/random_access_stream.h"
 #include "tink/streaming_aead.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/test_util.h"
-#include "tink/util/file_random_access_stream.h"
 #include "tink/util/ostream_output_stream.h"
 #include "tink/util/status.h"
 #include "tink/util/test_matchers.h"
@@ -53,16 +55,6 @@
 using subtle::test::WriteToStream;
 using testing::HasSubstr;
 
-// Creates a RandomAccessStream with the specified contents.
-std::unique_ptr<RandomAccessStream> GetRandomAccessStream(
-    absl::string_view contents) {
-  static int index = 1;
-  std::string filename = absl::StrCat("stream_data_file_", index, ".txt");
-  index++;
-  int input_fd = test::GetTestFileDescriptor(filename, contents);
-  return {absl::make_unique<util::FileRandomAccessStream>(input_fd)};
-}
-
 // Creates an RandomAccessStream that contains ciphertext resulting
 // from encryption of 'pt' with 'aad' as associated data, using 'saead'.
 std::unique_ptr<RandomAccessStream> GetCiphertextSource(
@@ -81,27 +73,7 @@
   EXPECT_THAT(WriteToStream(enc_stream_result.value().get(), pt), IsOk());
 
   // Return the ciphertext as RandomAccessStream.
-  return GetRandomAccessStream(ct_buf->str());
-}
-
-// Reads the entire 'ra_stream', until no more bytes can be read,
-// and puts the read bytes into 'contents'.
-// Returns the status of the last ra_stream->PRead()-operation.
-util::Status ReadAll(RandomAccessStream* ra_stream, std::string* contents) {
-  int chunk_size = 42;
-  contents->clear();
-  auto buffer = std::move(util::Buffer::New(chunk_size).value());
-  int64_t position = 0;
-  auto status = ra_stream->PRead(position, chunk_size, buffer.get());
-  while (status.ok()) {
-    contents->append(buffer->get_mem_block(), buffer->size());
-    position = contents->size();
-    status = ra_stream->PRead(position, chunk_size, buffer.get());
-  }
-  if (status.code() == absl::StatusCode::kOutOfRange) {  // EOF
-    EXPECT_EQ(0, buffer->size());
-  }
-  return status;
+  return std::make_unique<internal::TestRandomAccessStream>(ct_buf->str());
 }
 
 // A container for specification of instances of DummyStreamingAead
@@ -172,7 +144,8 @@
         EXPECT_THAT(dec_stream_result, IsOk());
         auto dec_stream = std::move(dec_stream_result.value());
         std::string decrypted;
-        auto status = ReadAll(dec_stream.get(), &decrypted);
+        auto status = internal::ReadAllFromRandomAccessStream(dec_stream.get(),
+                                                              decrypted);
         EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange,
                                      HasSubstr("EOF")));
         EXPECT_EQ(pt_size, dec_stream->size().value());
@@ -224,8 +197,11 @@
                                       ", position = ", position,
                                       ", chunk_size = ", chunk_size));
             auto buffer = std::move(util::Buffer::New(chunk_size).value());
-            auto status = dec_stream->PRead(position, chunk_size, buffer.get());
-            EXPECT_THAT(status, IsOk());
+            util::Status status =
+                dec_stream->PRead(position, chunk_size, buffer.get());
+            EXPECT_THAT(status,
+                        testing::AnyOf(
+                            IsOk(), StatusIs(absl::StatusCode::kOutOfRange)));
             EXPECT_EQ(std::min(chunk_size, pt_size - position), buffer->size());
             EXPECT_EQ(0, std::memcmp(plaintext.data() + position,
                                      buffer->get_mem_block(), buffer->size()));
@@ -320,7 +296,8 @@
           saead_set, std::move(ct), "wrong aad");
       EXPECT_THAT(dec_stream_result, IsOk());
       std::string decrypted;
-      auto status = ReadAll(dec_stream_result.value().get(), &decrypted);
+      auto status = internal::ReadAllFromRandomAccessStream(
+          dec_stream_result.value().get(), decrypted);
       EXPECT_THAT(status, StatusIs(absl::StatusCode::kInvalidArgument));
     }
   }
@@ -344,20 +321,22 @@
       SCOPED_TRACE(absl::StrCat("pt_size = ", pt_size,
                                 ", aad = '", aad, "'"));
       // Try decrypting a wrong ciphertext.
-      auto wrong_ct =
-          GetRandomAccessStream(subtle::Random::GetRandomBytes(pt_size));
+      auto wrong_ct = std::make_unique<internal::TestRandomAccessStream>(
+          subtle::Random::GetRandomBytes(pt_size));
       auto dec_stream_result = DecryptingRandomAccessStream::New(
           saead_set, std::move(wrong_ct), aad);
       EXPECT_THAT(dec_stream_result, IsOk());
       std::string decrypted;
-      auto status = ReadAll(dec_stream_result.value().get(), &decrypted);
+      auto status = internal::ReadAllFromRandomAccessStream(
+          dec_stream_result.value().get(), decrypted);
       EXPECT_THAT(status, StatusIs(absl::StatusCode::kInvalidArgument));
     }
   }
 }
 
 TEST(DecryptingRandomAccessStreamTest, NullPrimitiveSet) {
-  auto ct_stream = GetRandomAccessStream("some ciphertext contents");
+  auto ct_stream = std::make_unique<internal::TestRandomAccessStream>(
+      "some ciphertext contents");
   auto dec_stream_result = DecryptingRandomAccessStream::New(
           nullptr, std::move(ct_stream), "some aad");
   EXPECT_THAT(dec_stream_result.status(),
diff --git a/cc/streamingaead/shared_input_stream.h b/cc/streamingaead/shared_input_stream.h
index 32c9755..597d4a8 100644
--- a/cc/streamingaead/shared_input_stream.h
+++ b/cc/streamingaead/shared_input_stream.h
@@ -35,7 +35,7 @@
       crypto::tink::InputStream* input_stream)
       : input_stream_(input_stream) {}
 
-  ~SharedInputStream() override {}
+  ~SharedInputStream() override = default;
 
   crypto::tink::util::StatusOr<int> Next(const void** data) override {
     return input_stream_->Next(data);
diff --git a/cc/streamingaead/shared_input_stream_test.cc b/cc/streamingaead/shared_input_stream_test.cc
index 27a0f19..aef6638 100644
--- a/cc/streamingaead/shared_input_stream_test.cc
+++ b/cc/streamingaead/shared_input_stream_test.cc
@@ -17,6 +17,7 @@
 #include "tink/streamingaead/shared_input_stream.h"
 
 #include <algorithm>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
diff --git a/cc/streamingaead/shared_random_access_stream.h b/cc/streamingaead/shared_random_access_stream.h
index df84d0f..701be1a 100644
--- a/cc/streamingaead/shared_random_access_stream.h
+++ b/cc/streamingaead/shared_random_access_stream.h
@@ -38,7 +38,7 @@
       crypto::tink::RandomAccessStream* random_access_stream)
       : random_access_stream_(random_access_stream) {}
 
-  ~SharedRandomAccessStream() override {}
+  ~SharedRandomAccessStream() override = default;
 
   crypto::tink::util::Status PRead(
       int64_t position, int count,
diff --git a/cc/streamingaead/shared_random_access_stream_test.cc b/cc/streamingaead/shared_random_access_stream_test.cc
index f0b3269..ddbb5b3 100644
--- a/cc/streamingaead/shared_random_access_stream_test.cc
+++ b/cc/streamingaead/shared_random_access_stream_test.cc
@@ -22,53 +22,28 @@
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
+#include "tink/internal/test_random_access_stream.h"
 #include "tink/random_access_stream.h"
-#include "tink/util/buffer.h"
-#include "tink/util/file_random_access_stream.h"
-#include "tink/util/status.h"
-#include "tink/util/test_util.h"
+#include "tink/subtle/random.h"
 
 namespace crypto {
 namespace tink {
 namespace streamingaead {
 namespace {
 
-// Reads the entire 'ra_stream' in chunks of size 'chunk_size',
-// until no more bytes can be read, and puts the read bytes into 'contents'.
-// Returns the status of the last ra_stream->Next()-operation.
-util::Status ReadAll(RandomAccessStream* ra_stream, int chunk_size,
-                     std::string* contents) {
-  contents->clear();
-  auto buffer = std::move(util::Buffer::New(chunk_size).value());
-  int64_t position = 0;
-  auto status = ra_stream->PRead(position, chunk_size, buffer.get());
-  while (status.ok()) {
-    contents->append(buffer->get_mem_block(), buffer->size());
-    position = contents->size();
-    status = ra_stream->PRead(position, chunk_size, buffer.get());
-  }
-  if (status.code() == absl::StatusCode::kOutOfRange) {  // EOF
-    EXPECT_EQ(0, buffer->size());
-  }
-  return status;
-}
-
 TEST(SharedRandomAccessStreamTest, ReadingStreams) {
   for (auto stream_size : {0, 10, 100, 1000, 10000, 1000000}) {
     SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd = test::GetTestFileDescriptor(
-        filename, stream_size, &file_contents);
-    EXPECT_EQ(stream_size, file_contents.size());
-    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(input_fd);
+    std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+    auto ra_stream =
+        absl::make_unique<internal::TestRandomAccessStream>(stream_content);
     SharedRandomAccessStream shared_stream(ra_stream.get());
     std::string stream_contents;
-    auto status = ReadAll(&shared_stream, 1 + (stream_size / 10),
-                          &stream_contents);
+    auto status = internal::ReadAllFromRandomAccessStream(
+        &shared_stream, stream_contents, /*chunk_size=*/1 + (stream_size / 10));
     EXPECT_EQ(absl::StatusCode::kOutOfRange, status.code());
     EXPECT_EQ("EOF", status.message());
-    EXPECT_EQ(file_contents, stream_contents);
+    EXPECT_EQ(stream_content, stream_contents);
     EXPECT_EQ(stream_size, shared_stream.size().value());
   }
 }
diff --git a/cc/streamingaead/streaming_aead_config.cc b/cc/streamingaead/streaming_aead_config.cc
index 69ad21e..0ce14fd 100644
--- a/cc/streamingaead/streaming_aead_config.cc
+++ b/cc/streamingaead/streaming_aead_config.cc
@@ -25,18 +25,10 @@
 #include "tink/streamingaead/streaming_aead_wrapper.h"
 #include "tink/util/status.h"
 
-using google::crypto::tink::RegistryConfig;
-
 namespace crypto {
 namespace tink {
 
 // static
-const RegistryConfig& StreamingAeadConfig::Latest() {
-  static const RegistryConfig* config = new RegistryConfig();
-  return *config;
-}
-
-// static
 util::Status StreamingAeadConfig::Register() {
   // Register primitive wrapper.
   auto status = Registry::RegisterPrimitiveWrapper(
diff --git a/cc/streamingaead/streaming_aead_config.h b/cc/streamingaead/streaming_aead_config.h
index 179647d..0ed5ea5 100644
--- a/cc/streamingaead/streaming_aead_config.h
+++ b/cc/streamingaead/streaming_aead_config.h
@@ -35,14 +35,6 @@
 //
 class StreamingAeadConfig {
  public:
-  static constexpr char kCatalogueName[] = "TinkStreamingAead";
-  static constexpr char kPrimitiveName[] = "StreamingAead";
-
-  // Returns config of StreamingAead implementations supported
-  // in the current Tink release.
-  ABSL_DEPRECATED("This is not supported anymore.")
-  static const google::crypto::tink::RegistryConfig& Latest();
-
   // Registers StreamingAead primitive wrapper and key managers for all
   // StreamingAead key types from the current Tink release.
   static crypto::tink::util::Status Register();
diff --git a/cc/streamingaead/streaming_aead_config_test.cc b/cc/streamingaead/streaming_aead_config_test.cc
index d50018f..c3401e5 100644
--- a/cc/streamingaead/streaming_aead_config_test.cc
+++ b/cc/streamingaead/streaming_aead_config_test.cc
@@ -24,7 +24,6 @@
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
-#include "tink/config.h"
 #include "tink/config/tink_fips.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
diff --git a/cc/streamingaead/streaming_aead_key_templates.cc b/cc/streamingaead/streaming_aead_key_templates.cc
index 7605392..ca67d6b 100644
--- a/cc/streamingaead/streaming_aead_key_templates.cc
+++ b/cc/streamingaead/streaming_aead_key_templates.cc
@@ -49,7 +49,8 @@
   return key_template;
 }
 
-KeyTemplate* NewAesCtrHmacStreamingKeyTemplate(int ikm_size_in_bytes) {
+KeyTemplate* NewAesCtrHmacStreamingKeyTemplate(int ikm_size_in_bytes,
+                                               int segment_size_in_bytes) {
   KeyTemplate* key_template = new KeyTemplate;
   key_template->set_type_url(
       "type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey");
@@ -57,7 +58,7 @@
   AesCtrHmacStreamingKeyFormat key_format;
   key_format.set_key_size(ikm_size_in_bytes);
   auto params = key_format.mutable_params();
-  params->set_ciphertext_segment_size(4096);
+  params->set_ciphertext_segment_size(segment_size_in_bytes);
   params->set_derived_key_size(ikm_size_in_bytes);
   params->set_hkdf_hash_type(HashType::SHA256);
   auto hmac_params = params->mutable_hmac_params();
@@ -92,15 +93,29 @@
 
 // static
 const KeyTemplate& StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment4KB() {
-  static const KeyTemplate* key_template =
-      NewAesCtrHmacStreamingKeyTemplate(/* ikm_size_in_bytes= */ 16);
+  static const KeyTemplate* key_template = NewAesCtrHmacStreamingKeyTemplate(
+      /* ikm_size_in_bytes= */ 16, /* segment_size_in_bytes= */ 4096);
+  return *key_template;
+}
+
+// static
+const KeyTemplate& StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB() {
+  static const KeyTemplate* key_template = NewAesCtrHmacStreamingKeyTemplate(
+      /* ikm_size_in_bytes= */ 16, /* segment_size_in_bytes= */ 1048576);
   return *key_template;
 }
 
 // static
 const KeyTemplate& StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment4KB() {
-  static const KeyTemplate* key_template =
-      NewAesCtrHmacStreamingKeyTemplate(/* ikm_size_in_bytes= */ 32);
+  static const KeyTemplate* key_template = NewAesCtrHmacStreamingKeyTemplate(
+      /* ikm_size_in_bytes= */ 32, /* segment_size_in_bytes= */ 4096);
+  return *key_template;
+}
+
+// static
+const KeyTemplate& StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB() {
+  static const KeyTemplate* key_template = NewAesCtrHmacStreamingKeyTemplate(
+      /* ikm_size_in_bytes= */ 32, /* segment_size_in_bytes= */ 1048576);
   return *key_template;
 }
 
diff --git a/cc/streamingaead/streaming_aead_key_templates.h b/cc/streamingaead/streaming_aead_key_templates.h
index 5725fdb..bc7aded 100644
--- a/cc/streamingaead/streaming_aead_key_templates.h
+++ b/cc/streamingaead/streaming_aead_key_templates.h
@@ -76,6 +76,18 @@
 
   // Returns a KeyTemplate that generates new instances of
   // AesCtrHmacStreamingKey with the following parameters:
+  //   - main key (ikm) size: 16 bytes
+  //   - HKDF algorithm: HMAC-SHA256
+  //   - size of derived AES-CTR keys: 16 bytes
+  //   - tag algorithm: HMAC-SHA256
+  //   - tag size: 32 bytes
+  //   - ciphertext segment size: 1048576 bytes (1 MB)
+  //   - OutputPrefixType: RAW
+  static const google::crypto::tink::KeyTemplate&
+  Aes128CtrHmacSha256Segment1MB();
+
+  // Returns a KeyTemplate that generates new instances of
+  // AesCtrHmacStreamingKey with the following parameters:
   //   - main key (ikm) size: 32 bytes
   //   - HKDF algorithm: HMAC-SHA256
   //   - size of derived AES-CTR keys: 32 bytes
@@ -85,6 +97,18 @@
   //   - OutputPrefixType: RAW
   static const google::crypto::tink::KeyTemplate&
   Aes256CtrHmacSha256Segment4KB();
+
+  // Returns a KeyTemplate that generates new instances of
+  // AesCtrHmacStreamingKey with the following parameters:
+  //   - main key (ikm) size: 32 bytes
+  //   - HKDF algorithm: HMAC-SHA256
+  //   - size of derived AES-CTR keys: 32 bytes
+  //   - tag algorithm: HMAC-SHA256
+  //   - tag size: 32 bytes
+  //   - ciphertext segment size: 1048576 bytes (1 MB)
+  //   - OutputPrefixType: RAW
+  static const google::crypto::tink::KeyTemplate&
+  Aes256CtrHmacSha256Segment1MB();
 };
 
 }  // namespace tink
diff --git a/cc/streamingaead/streaming_aead_key_templates_test.cc b/cc/streamingaead/streaming_aead_key_templates_test.cc
index 31098b8..e0c9f91 100644
--- a/cc/streamingaead/streaming_aead_key_templates_test.cc
+++ b/cc/streamingaead/streaming_aead_key_templates_test.cc
@@ -203,6 +203,49 @@
   EXPECT_THAT(key_format.params().hmac_params().tag_size(), Eq(32));
 }
 
+TEST(Aes128CtrHmacSha256Segment1MBTest, TypeUrl) {
+  EXPECT_THAT(
+      StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB().type_url(),
+      Eq("type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey"));
+  EXPECT_THAT(
+      StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB().type_url(),
+      Eq(AesCtrHmacStreamingKeyManager().get_key_type()));
+}
+
+TEST(Aes128CtrHmacSha256Segment1MBTest, OutputPrefixType) {
+  EXPECT_THAT(StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB()
+                  .output_prefix_type(),
+              Eq(OutputPrefixType::RAW));
+}
+
+TEST(Aes128CtrHmacSha256Segment1MBTest, SameReference) {
+  // Check that reference to the same object is returned.
+  EXPECT_THAT(StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB(),
+              Ref(StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB()));
+}
+
+TEST(Aes128CtrHmacSha256Segment1MBTest, WorksWithKeyTypeManager) {
+  const KeyTemplate& key_template =
+      StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB();
+  AesCtrHmacStreamingKeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_THAT(AesCtrHmacStreamingKeyManager().ValidateKeyFormat(key_format),
+              IsOk());
+}
+
+TEST(Aes128CtrHmacSha256Segment1MBTest, CheckValues) {
+  const KeyTemplate& key_template =
+      StreamingAeadKeyTemplates::Aes128CtrHmacSha256Segment1MB();
+  AesCtrHmacStreamingKeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_THAT(key_format.key_size(), Eq(16));
+  EXPECT_THAT(key_format.params().ciphertext_segment_size(), Eq(1048576));
+  EXPECT_THAT(key_format.params().derived_key_size(), Eq(16));
+  EXPECT_THAT(key_format.params().hkdf_hash_type(), Eq(HashType::SHA256));
+  EXPECT_THAT(key_format.params().hmac_params().hash(), Eq(HashType::SHA256));
+  EXPECT_THAT(key_format.params().hmac_params().tag_size(), Eq(32));
+}
+
 TEST(Aes256CtrHmacSha256Segment4KBTest, TypeUrl) {
   EXPECT_THAT(
       StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment4KB().type_url(),
@@ -246,6 +289,49 @@
   EXPECT_THAT(key_format.params().hmac_params().tag_size(), Eq(32));
 }
 
+TEST(Aes256CtrHmacSha256Segment1MBTest, TypeUrl) {
+  EXPECT_THAT(
+      StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB().type_url(),
+      Eq("type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey"));
+  EXPECT_THAT(
+      StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB().type_url(),
+      Eq(AesCtrHmacStreamingKeyManager().get_key_type()));
+}
+
+TEST(Aes256CtrHmacSha256Segment1MBTest, OutputPrefixType) {
+  EXPECT_THAT(StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB()
+                  .output_prefix_type(),
+              Eq(OutputPrefixType::RAW));
+}
+
+TEST(Aes256CtrHmacSha256Segment1MBTest, SameReference) {
+  // Check that reference to the same object is returned.
+  EXPECT_THAT(StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB(),
+              Ref(StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB()));
+}
+
+TEST(Aes256CtrHmacSha256Segment1MBTest, WorksWithKeyTypeManager) {
+  const KeyTemplate& key_template =
+      StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB();
+  AesCtrHmacStreamingKeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_THAT(AesCtrHmacStreamingKeyManager().ValidateKeyFormat(key_format),
+              IsOk());
+}
+
+TEST(Aes256CtrHmacSha256Segment1MBTest, CheckValues) {
+  const KeyTemplate& key_template =
+      StreamingAeadKeyTemplates::Aes256CtrHmacSha256Segment1MB();
+  AesCtrHmacStreamingKeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_THAT(key_format.key_size(), Eq(32));
+  EXPECT_THAT(key_format.params().ciphertext_segment_size(), Eq(1048576));
+  EXPECT_THAT(key_format.params().derived_key_size(), Eq(32));
+  EXPECT_THAT(key_format.params().hkdf_hash_type(), Eq(HashType::SHA256));
+  EXPECT_THAT(key_format.params().hmac_params().hash(), Eq(HashType::SHA256));
+  EXPECT_THAT(key_format.params().hmac_params().tag_size(), Eq(32));
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/streamingaead/streaming_aead_wrapper.cc b/cc/streamingaead/streaming_aead_wrapper.cc
index 6f59f45..ae49413 100644
--- a/cc/streamingaead/streaming_aead_wrapper.cc
+++ b/cc/streamingaead/streaming_aead_wrapper.cc
@@ -16,7 +16,9 @@
 
 #include "tink/streamingaead/streaming_aead_wrapper.h"
 
+#include <memory>
 #include <utility>
+#include <vector>
 
 #include "absl/status/status.h"
 #include "tink/crypto_format.h"
@@ -47,12 +49,6 @@
     return Status(absl::StatusCode::kInvalidArgument,
                   "primitive set has no primary");
   }
-  auto raw_primitives_result = primitives->get_raw_primitives();
-  if (!raw_primitives_result.ok()) {
-    return Status(absl::StatusCode::kInvalidArgument,
-                  "primitive set has no raw primitives");
-  }
-  // TODO(b/129044084)
   return util::OkStatus();
 }
 
@@ -78,7 +74,7 @@
       std::unique_ptr<crypto::tink::RandomAccessStream> ciphertext_source,
       absl::string_view associated_data) const override;
 
-  ~StreamingAeadSetWrapper() override {}
+  ~StreamingAeadSetWrapper() override = default;
 
  private:
   // We use a shared_ptr here to ensure that primitives_ stays alive
diff --git a/cc/streamingaead/streaming_aead_wrapper.h b/cc/streamingaead/streaming_aead_wrapper.h
index 578be85..2084bb3 100644
--- a/cc/streamingaead/streaming_aead_wrapper.h
+++ b/cc/streamingaead/streaming_aead_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_STREAMINGAEAD_STREAMING_AEAD_WRAPPER_H_
 #define TINK_STREAMINGAEAD_STREAMING_AEAD_WRAPPER_H_
 
+#include <memory>
+
 #include "absl/strings/string_view.h"
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
diff --git a/cc/streamingaead/streaming_aead_wrapper_test.cc b/cc/streamingaead/streaming_aead_wrapper_test.cc
index e262c92..aa6aa72 100644
--- a/cc/streamingaead/streaming_aead_wrapper_test.cc
+++ b/cc/streamingaead/streaming_aead_wrapper_test.cc
@@ -16,9 +16,11 @@
 
 #include "tink/streamingaead/streaming_aead_wrapper.h"
 
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
@@ -26,64 +28,41 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "tink/input_stream.h"
+#include "tink/insecure_secret_key_access.h"
+#include "tink/internal/test_random_access_stream.h"
 #include "tink/output_stream.h"
 #include "tink/primitive_set.h"
+#include "tink/proto_keyset_format.h"
 #include "tink/random_access_stream.h"
 #include "tink/streaming_aead.h"
+#include "tink/streamingaead/aes_gcm_hkdf_streaming_key_manager.h"
+#include "tink/streamingaead/streaming_aead_config.h"
 #include "tink/subtle/random.h"
+#include "tink/subtle/streaming_aead_test_util.h"
 #include "tink/subtle/test_util.h"
 #include "tink/util/buffer.h"
-#include "tink/util/file_random_access_stream.h"
 #include "tink/util/istream_input_stream.h"
 #include "tink/util/ostream_output_stream.h"
 #include "tink/util/status.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
+#include "proto/aes_gcm_hkdf_streaming.pb.h"
+#include "proto/common.pb.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
-using crypto::tink::test::DummyStreamingAead;
-using crypto::tink::test::IsOk;
-using crypto::tink::test::StatusIs;
-using google::crypto::tink::KeysetInfo;
-using google::crypto::tink::KeyStatusType;
-using google::crypto::tink::OutputPrefixType;
-using subtle::test::ReadFromStream;
-using subtle::test::WriteToStream;
-using testing::HasSubstr;
-
-// Creates a RandomAccessStream with the specified contents.
-std::unique_ptr<RandomAccessStream> GetRandomAccessStream(
-    absl::string_view contents) {
-  static int index = 1;
-  std::string filename = absl::StrCat("stream_data_file_", index, ".txt");
-  index++;
-  int input_fd = test::GetTestFileDescriptor(filename, contents);
-  return {absl::make_unique<util::FileRandomAccessStream>(input_fd)};
-}
-
-// Reads the entire 'ra_stream', until no more bytes can be read,
-// and puts the read bytes into 'contents'.
-// Returns the status of the last ra_stream->PRead()-operation.
-util::Status ReadAll(RandomAccessStream* ra_stream, std::string* contents) {
-  int chunk_size = 42;
-  contents->clear();
-  auto buffer = std::move(util::Buffer::New(chunk_size).value());
-  int64_t position = 0;
-  auto status = ra_stream->PRead(position, chunk_size, buffer.get());
-  while (status.ok()) {
-    contents->append(buffer->get_mem_block(), buffer->size());
-    position = contents->size();
-    status = ra_stream->PRead(position, chunk_size, buffer.get());
-  }
-  if (status.code() == absl::StatusCode::kOutOfRange) {  // EOF
-    EXPECT_EQ(0, buffer->size());
-  }
-  return status;
-}
+using ::crypto::tink::test::DummyStreamingAead;
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::google::crypto::tink::KeysetInfo;
+using ::google::crypto::tink::KeyStatusType;
+using ::google::crypto::tink::OutputPrefixType;
+using ::crypto::tink::subtle::test::ReadFromStream;
+using ::crypto::tink::subtle::test::WriteToStream;
+using ::testing::HasSubstr;
 
 // A container for specification of instances of DummyStreamingAead
 // to be created for testing.
@@ -232,12 +211,14 @@
       EXPECT_EQ(absl::StrCat(saead_name_2, aad, plaintext), ct_buf->str());
 
       // Decrypt the ciphertext.
-      auto ct_source = GetRandomAccessStream(ct_buf->str());
+      auto ct_source =
+          std::make_unique<internal::TestRandomAccessStream>(ct_buf->str());
       auto dec_stream_result =
           saead->NewDecryptingRandomAccessStream(std::move(ct_source), aad);
       EXPECT_THAT(dec_stream_result, IsOk());
       std::string decrypted;
-      status = ReadAll(dec_stream_result.value().get(), &decrypted);
+      status = internal::ReadAllFromRandomAccessStream(
+          dec_stream_result.value().get(), decrypted);
       EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange,
                                    HasSubstr("EOF")));
       EXPECT_EQ(plaintext, decrypted);
@@ -303,24 +284,63 @@
   EXPECT_EQ(plaintext, decrypted);
 }
 
-TEST(StreamingAeadSetWrapperTest, MissingRawPrimitives) {
-  uint32_t key_id_0 = 1234543;
-  uint32_t key_id_1 = 726329;
-  uint32_t key_id_2 = 7213743;
-  std::string saead_name_0 = "streaming_aead0";
-  std::string saead_name_1 = "streaming_aead1";
-  std::string saead_name_2 = "streaming_aead2";
+TEST(StreamingAeadSetWrapperTest, EncryptWithTink) {
+  ASSERT_THAT(StreamingAeadConfig::Register(), IsOk());
 
-  auto saead_set = GetTestStreamingAeadSet(
-      {{key_id_0, saead_name_0, OutputPrefixType::TINK},
-       {key_id_1, saead_name_1, OutputPrefixType::LEGACY},
-       {key_id_2, saead_name_2, OutputPrefixType::TINK}});
+  google::crypto::tink::AesGcmHkdfStreamingKey key;
+  key.set_key_value("0123456789012345");
+  google::crypto::tink::AesGcmHkdfStreamingParams& params =
+      *key.mutable_params();
+  params.set_hkdf_hash_type(google::crypto::tink::HashType::SHA1);
+  params.set_derived_key_size(16);
+  params.set_ciphertext_segment_size(1024);
 
-  // Wrap saead_set and test the resulting StreamingAead.
-  StreamingAeadWrapper wrapper;
-  auto wrap_result = wrapper.Wrap(std::move(saead_set));
-  EXPECT_THAT(wrap_result.status(), StatusIs(absl::StatusCode::kInvalidArgument,
-                                             HasSubstr("no raw primitives")));
+  std::string serialized_key_1 = key.SerializeAsString();
+
+  key.set_key_value("0123456789abcdef");
+  std::string serialized_key_2 = key.SerializeAsString();
+
+  google::crypto::tink::Keyset keyset;
+  {
+    google::crypto::tink::Keyset::Key& keyset_key = *keyset.add_key();
+    google::crypto::tink::KeyData& key_data = *keyset_key.mutable_key_data();
+    key_data.set_type_url(AesGcmHkdfStreamingKeyManager().get_key_type());
+    key_data.set_value(serialized_key_1);
+    key_data.set_key_material_type(google::crypto::tink::KeyData::SYMMETRIC);
+    keyset_key.set_key_id(1);
+    keyset_key.set_output_prefix_type(
+        google::crypto::tink::OutputPrefixType::TINK);
+    keyset_key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+
+    keyset.set_primary_key_id(1);
+  }
+  {
+    google::crypto::tink::Keyset::Key& keyset_key = *keyset.add_key();
+    google::crypto::tink::KeyData& key_data = *keyset_key.mutable_key_data();
+    key_data.set_type_url(AesGcmHkdfStreamingKeyManager().get_key_type());
+    key_data.set_value(serialized_key_2);
+    key_data.set_key_material_type(google::crypto::tink::KeyData::SYMMETRIC);
+    keyset_key.set_key_id(2);
+    keyset_key.set_output_prefix_type(
+        google::crypto::tink::OutputPrefixType::RAW);
+    keyset_key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  }
+
+  crypto::tink::util::StatusOr<KeysetHandle> handle =
+      ParseKeysetFromProtoKeysetFormat(keyset.SerializeAsString(),
+                                       InsecureSecretKeyAccess::Get());
+  ASSERT_THAT(handle.status(), IsOk());
+
+  crypto::tink::util::StatusOr<std::unique_ptr<StreamingAead>> streaming_aead =
+      handle->GetPrimitive<StreamingAead>();
+
+  ASSERT_THAT(streaming_aead.status(), IsOk());
+
+  EXPECT_THAT(EncryptThenDecrypt(streaming_aead.value().get(),
+                                 streaming_aead.value().get(),
+                                 subtle::Random::GetRandomBytes(10000),
+                                 "some associated data", 0),
+              IsOk());
 }
 
 }  // namespace
diff --git a/cc/subtle/BUILD.bazel b/cc/subtle/BUILD.bazel
index 4998753..caa9741 100644
--- a/cc/subtle/BUILD.bazel
+++ b/cc/subtle/BUILD.bazel
@@ -228,14 +228,10 @@
         ":common_enums",
         ":subtle_util_boringssl",
         "//:public_key_sign",
-        "//internal:bn_util",
-        "//internal:ec_util",
-        "//internal:err_util",
         "//internal:fips_utils",
         "//internal:md_util",
-        "//internal:ssl_unique_ptr",
         "//internal:util",
-        "//util:errors",
+        "//signature/internal:ecdsa_raw_sign_boringssl",
         "//util:statusor",
         "@boringssl//:crypto",
         "@com_google_absl//absl/status",
@@ -797,12 +793,11 @@
         ":test_util",
         "//:random_access_stream",
         "//:streaming_aead",
+        "//internal:test_random_access_stream",
         "//util:buffer",
-        "//util:file_random_access_stream",
         "//util:istream_input_stream",
         "//util:ostream_output_stream",
         "//util:status",
-        "//util:test_util",
         "@com_google_absl//absl/strings",
     ],
 )
@@ -1004,7 +999,7 @@
         ":common_enums",
         ":hmac_boringssl",
         "//:mac",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
@@ -1019,14 +1014,13 @@
     name = "aes_gcm_boringssl_test",
     size = "small",
     srcs = ["aes_gcm_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:aes_gcm"],
+    data = ["//testvectors:aes_gcm"],
     tags = ["fips"],
     deps = [
         ":aes_gcm_boringssl",
         "//aead/internal:wycheproof_aead",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//util:secret_data",
-        "//util:status",
         "//util:statusor",
         "//util:test_matchers",
         "@com_google_absl//absl/status",
@@ -1122,7 +1116,7 @@
     name = "aes_eax_boringssl_test",
     size = "small",
     srcs = ["aes_eax_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:aes_eax"],
+    data = ["//testvectors:aes_eax"],
     tags = ["fips"],
     deps = [
         ":aes_eax_boringssl",
@@ -1142,7 +1136,6 @@
 
 cc_test(
     name = "encrypt_then_authenticate_test",
-    size = "small",
     srcs = ["encrypt_then_authenticate_test.cc"],
     deps = [
         ":aes_ctr_boringssl",
@@ -1167,7 +1160,7 @@
     deps = [
         ":aes_ctr_boringssl",
         ":random",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
@@ -1182,7 +1175,7 @@
     name = "aes_siv_boringssl_test",
     size = "small",
     srcs = ["aes_siv_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:aes_siv_cmac"],
+    data = ["//testvectors:aes_siv_cmac"],
     tags = ["fips"],
     deps = [
         ":aes_siv_boringssl",
@@ -1210,8 +1203,8 @@
         ":subtle_util_boringssl",
         "//:public_key_sign",
         "//:public_key_verify",
-        "//config:tink_fips",
         "//internal:ec_util",
+        "//internal:fips_utils",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -1225,8 +1218,8 @@
     size = "small",
     srcs = ["ecdsa_verify_boringssl_test.cc"],
     data = [
-        "@wycheproof//testvectors:ecdsa",
-        "@wycheproof//testvectors:ecdsa_webcrypto",
+        "//testvectors:ecdsa",
+        "//testvectors:ecdsa_webcrypto",
     ],
     tags = ["fips"],
     deps = [
@@ -1237,7 +1230,7 @@
         ":wycheproof_util",
         "//:public_key_sign",
         "//:public_key_verify",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -1277,7 +1270,7 @@
     name = "ed25519_verify_boringssl_test",
     size = "small",
     srcs = ["ed25519_verify_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:eddsa"],
+    data = ["//testvectors:eddsa"],
     tags = ["fips"],
     deps = [
         ":ed25519_verify_boringssl",
@@ -1300,7 +1293,7 @@
     name = "rsa_ssa_pss_verify_boringssl_test",
     size = "small",
     srcs = ["rsa_ssa_pss_verify_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:rsa_pss"],
+    data = ["//testvectors:rsa_pss"],
     tags = ["fips"],
     deps = [
         ":common_enums",
@@ -1308,8 +1301,8 @@
         ":wycheproof_util",
         "//:public_key_sign",
         "//:public_key_verify",
-        "//config:tink_fips",
         "//internal:err_util",
+        "//internal:fips_utils",
         "//internal:rsa_util",
         "//internal:ssl_unique_ptr",
         "//util:status",
@@ -1330,7 +1323,7 @@
     deps = [
         ":rsa_ssa_pss_sign_boringssl",
         ":rsa_ssa_pss_verify_boringssl",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//internal:rsa_util",
         "//internal:ssl_unique_ptr",
         "//util:test_matchers",
@@ -1345,7 +1338,7 @@
     name = "rsa_ssa_pkcs1_verify_boringssl_test",
     size = "small",
     srcs = ["rsa_ssa_pkcs1_verify_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:rsa_signature"],
+    data = ["//testvectors:rsa_signature"],
     tags = ["fips"],
     deps = [
         ":common_enums",
@@ -1353,8 +1346,8 @@
         ":wycheproof_util",
         "//:public_key_sign",
         "//:public_key_verify",
-        "//config:tink_fips",
         "//internal:err_util",
+        "//internal:fips_utils",
         "//internal:rsa_util",
         "//internal:ssl_unique_ptr",
         "//util:status",
@@ -1375,7 +1368,7 @@
     deps = [
         ":rsa_ssa_pkcs1_sign_boringssl",
         ":rsa_ssa_pkcs1_verify_boringssl",
-        "//config:tink_fips",
+        "//internal:fips_utils",
         "//internal:rsa_util",
         "//internal:ssl_unique_ptr",
         "//util:test_matchers",
@@ -1390,7 +1383,7 @@
     name = "aes_gcm_siv_boringssl_test",
     size = "small",
     srcs = ["aes_gcm_siv_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:aes_gcm_siv"],
+    data = ["//testvectors:aes_gcm_siv"],
     tags = ["fips"],
     deps = [
         ":aes_gcm_siv_boringssl",
@@ -1446,7 +1439,7 @@
     name = "xchacha20_poly1305_boringssl_test",
     size = "small",
     srcs = ["xchacha20_poly1305_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:chacha20_poly1305"],
+    data = ["//testvectors:chacha20_poly1305"],
     tags = ["fips"],
     deps = [
         ":subtle_util",
@@ -1545,11 +1538,10 @@
         "//:output_stream",
         "//:random_access_stream",
         "//:streaming_aead",
-        "//util:file_random_access_stream",
+        "//internal:test_random_access_stream",
         "//util:ostream_output_stream",
         "//util:status",
         "//util:test_matchers",
-        "//util:test_util",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
@@ -1578,7 +1570,7 @@
     name = "stateful_hmac_boringssl_test",
     size = "small",
     srcs = ["stateful_hmac_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:hmac"],
+    data = ["//testvectors:hmac"],
     deps = [
         ":common_enums",
         ":stateful_hmac_boringssl",
@@ -1598,7 +1590,7 @@
     name = "stateful_cmac_boringssl_test",
     size = "small",
     srcs = ["stateful_cmac_boringssl_test.cc"],
-    data = ["@wycheproof//testvectors:aes_cmac"],
+    data = ["//testvectors:aes_cmac"],
     deps = [
         ":common_enums",
         ":stateful_cmac_boringssl",
diff --git a/cc/subtle/CMakeLists.txt b/cc/subtle/CMakeLists.txt
index 7ac3580..ff38349 100644
--- a/cc/subtle/CMakeLists.txt
+++ b/cc/subtle/CMakeLists.txt
@@ -216,14 +216,10 @@
     absl::strings
     crypto
     tink::core::public_key_sign
-    tink::internal::bn_util
-    tink::internal::ec_util
-    tink::internal::err_util
     tink::internal::fips_utils
     tink::internal::md_util
-    tink::internal::ssl_unique_ptr
     tink::internal::util
-    tink::util::errors
+    tink::signature::internal::ecdsa_raw_sign_boringssl
     tink::util::statusor
 )
 
@@ -648,6 +644,7 @@
     tink::internal::test_file_util
     tink::util::status
     tink::util::statusor
+  TESTONLY
 )
 
 tink_cc_library(
@@ -728,6 +725,7 @@
     tink::core::output_stream
     tink::util::status
     tink::util::statusor
+  TESTONLY
 )
 
 tink_cc_library(
@@ -741,6 +739,7 @@
     tink::core::aead
     tink::aead::cord_aead
     tink::util::status
+  TESTONLY
 )
 
 tink_cc_library(
@@ -753,12 +752,12 @@
     absl::strings
     tink::core::random_access_stream
     tink::core::streaming_aead
+    tink::internal::test_random_access_stream
     tink::util::buffer
-    tink::util::file_random_access_stream
     tink::util::istream_input_stream
     tink::util::ostream_output_stream
     tink::util::status
-    tink::util::test_util
+  TESTONLY
 )
 
 tink_cc_library(
@@ -771,6 +770,7 @@
     tink::core::hybrid_decrypt
     tink::core::hybrid_encrypt
     tink::util::status
+  TESTONLY
 )
 
 tink_cc_library(
@@ -946,7 +946,7 @@
     absl::status
     absl::strings
     tink::core::mac
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
@@ -965,9 +965,8 @@
     absl::status
     absl::strings
     tink::aead::internal::wycheproof_aead
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::secret_data
-    tink::util::status
     tink::util::statusor
     tink::util::test_matchers
 )
@@ -1097,7 +1096,7 @@
     tink::subtle::random
     gmock
     absl::status
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
@@ -1137,8 +1136,8 @@
     absl::status
     tink::core::public_key_sign
     tink::core::public_key_verify
-    tink::config::tink_fips
     tink::internal::ec_util
+    tink::internal::fips_utils
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -1162,7 +1161,7 @@
     rapidjson
     tink::core::public_key_sign
     tink::core::public_key_verify
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -1230,8 +1229,8 @@
     rapidjson
     tink::core::public_key_sign
     tink::core::public_key_verify
-    tink::config::tink_fips
     tink::internal::err_util
+    tink::internal::fips_utils
     tink::internal::rsa_util
     tink::internal::ssl_unique_ptr
     tink::util::status
@@ -1250,7 +1249,7 @@
     absl::status
     absl::strings
     crypto
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::internal::rsa_util
     tink::internal::ssl_unique_ptr
     tink::util::test_matchers
@@ -1273,8 +1272,8 @@
     rapidjson
     tink::core::public_key_sign
     tink::core::public_key_verify
-    tink::config::tink_fips
     tink::internal::err_util
+    tink::internal::fips_utils
     tink::internal::rsa_util
     tink::internal::ssl_unique_ptr
     tink::util::status
@@ -1293,7 +1292,7 @@
     absl::status
     absl::strings
     crypto
-    tink::config::tink_fips
+    tink::internal::fips_utils
     tink::internal::rsa_util
     tink::internal::ssl_unique_ptr
     tink::util::test_matchers
@@ -1457,11 +1456,10 @@
     tink::core::output_stream
     tink::core::random_access_stream
     tink::core::streaming_aead
-    tink::util::file_random_access_stream
+    tink::internal::test_random_access_stream
     tink::util::ostream_output_stream
     tink::util::status
     tink::util::test_matchers
-    tink::util::test_util
 )
 
 tink_cc_test(
diff --git a/cc/subtle/aes_cmac_boringssl.cc b/cc/subtle/aes_cmac_boringssl.cc
index 9e8cc9c..4afbba1 100644
--- a/cc/subtle/aes_cmac_boringssl.cc
+++ b/cc/subtle/aes_cmac_boringssl.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/aes_cmac_boringssl.h"
 
 #include <cstdint>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/aes_cmac_boringssl_test.cc b/cc/subtle/aes_cmac_boringssl_test.cc
index d1a6f60..a836211 100644
--- a/cc/subtle/aes_cmac_boringssl_test.cc
+++ b/cc/subtle/aes_cmac_boringssl_test.cc
@@ -16,7 +16,9 @@
 
 #include "tink/subtle/aes_cmac_boringssl.h"
 
+#include <memory>
 #include <string>
+#include <utility>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/subtle/aes_ctr_boringssl.cc b/cc/subtle/aes_ctr_boringssl.cc
index 25d1418..3f6160c 100644
--- a/cc/subtle/aes_ctr_boringssl.cc
+++ b/cc/subtle/aes_ctr_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/aes_ctr_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/aes_ctr_boringssl_test.cc b/cc/subtle/aes_ctr_boringssl_test.cc
index c812c30..e1d57db 100644
--- a/cc/subtle/aes_ctr_boringssl_test.cc
+++ b/cc/subtle/aes_ctr_boringssl_test.cc
@@ -22,7 +22,7 @@
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/subtle/random.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
@@ -39,7 +39,7 @@
 using ::crypto::tink::test::StatusIs;
 
 TEST(AesCtrBoringSslTest, TestEncryptDecrypt) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -60,7 +60,7 @@
 }
 
 TEST(AesCtrBoringSslTest, TestEncryptDecrypt_randomMessage) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -83,7 +83,7 @@
 }
 
 TEST(AesCtrBoringSslTest, TestEncryptDecrypt_randomKey_randomMessage) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -105,7 +105,7 @@
 }
 
 TEST(AesCtrBoringSslTest, TestEncryptDecrypt_invalidIvSize) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -122,7 +122,7 @@
 }
 
 TEST(AesCtrBoringSslTest, TestNistTestVector) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -143,7 +143,7 @@
 }
 
 TEST(AesCtrBoringSslTest, TestMultipleEncrypt) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -160,7 +160,7 @@
 }
 
 TEST(AesCtrBoringSslTest, TestFipsOnly) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -175,7 +175,7 @@
 }
 
 TEST(AesCtrBoringSslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
diff --git a/cc/subtle/aes_ctr_hmac_streaming.cc b/cc/subtle/aes_ctr_hmac_streaming.cc
index 597efd3..891ff4c 100644
--- a/cc/subtle/aes_ctr_hmac_streaming.cc
+++ b/cc/subtle/aes_ctr_hmac_streaming.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <limits>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/cc/subtle/aes_ctr_hmac_streaming.h b/cc/subtle/aes_ctr_hmac_streaming.h
index 4276650..572e5de 100644
--- a/cc/subtle/aes_ctr_hmac_streaming.h
+++ b/cc/subtle/aes_ctr_hmac_streaming.h
@@ -183,7 +183,7 @@
     return ciphertext_segment_size_;
   }
   int get_ciphertext_offset() const override { return ciphertext_offset_; }
-  ~AesCtrHmacStreamSegmentDecrypter() override {}
+  ~AesCtrHmacStreamSegmentDecrypter() override = default;
 
  private:
   AesCtrHmacStreamSegmentDecrypter(util::SecretData ikm, HashType hkdf_algo,
diff --git a/cc/subtle/aes_eax_aesni.cc b/cc/subtle/aes_eax_aesni.cc
deleted file mode 100644
index 5ad53fb..0000000
--- a/cc/subtle/aes_eax_aesni.cc
+++ /dev/null
@@ -1,582 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// 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
-//
-//     http://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 <utility>
-#ifdef __SSE4_1__
-#ifdef __AES__
-
-#include "tink/subtle/aes_eax_aesni.h"
-
-#include <emmintrin.h>  // SSE2: used for _mm_sub_epi64 _mm_unpacklo_epi64 etc.
-#include <smmintrin.h>  // SSE4: used for _mm_cmpeq_epi64
-#include <tmmintrin.h>  // SSE3: used for _mm_shuffle_epi8
-#include <wmmintrin.h>  // AES_NI instructions.
-#include <xmmintrin.h>  // Datatype _mm128i
-
-#include <algorithm>
-#include <array>
-#include <memory>
-#include <string>
-
-#include "absl/algorithm/container.h"
-#include "absl/status/status.h"
-#include "tink/internal/util.h"
-#include "tink/subtle/random.h"
-#include "tink/subtle/subtle_util.h"
-
-namespace crypto {
-namespace tink {
-namespace subtle {
-
-namespace {
-inline bool EqualBlocks(__m128i x, __m128i y) {
-  // Compare byte wise.
-  // A byte in eq is 0xff if the corresponding byte in x and y are equal
-  // and 0x00 if the corresponding byte in x and y are not equal.
-  __m128i eq = _mm_cmpeq_epi8(x, y);
-  // Extract the 16 most significant bits of each byte in eq.
-  int bits = _mm_movemask_epi8(eq);
-  return 0xFFFF == bits;
-}
-
-// Reverse the order of the bytes in x.
-inline __m128i Reverse(__m128i x) {
-  const __m128i reverse_order =
-      _mm_set_epi32(0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f);
-  return _mm_shuffle_epi8(x, reverse_order);
-}
-
-// Increment x by 1.
-// This function assumes that the bytes of x are in little endian order.
-// Hence before using the result in EAX the bytes must be reversed, since EAX
-// requires a counter value in big endian order.
-inline __m128i Increment(__m128i x) {
-  const __m128i mask =
-      _mm_set_epi32(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff);
-  // Determine which of the two 64-bit parts of x overflow.
-  // The result is 0xff..ff if the corresponding integers overflows and 0
-  // otherwise.
-  __m128i carries = _mm_cmpeq_epi64(x, mask);  // SSE4
-  // Move the least significant 64 carry bits into the most significant 64 bits
-  // of diff and fill the least significant bits of diff with 0xff..ff.
-  __m128i diff = _mm_unpacklo_epi64(mask, carries);
-  // Use subtraction since the 64-bit parts that must be incremented contain
-  // the value -1.
-  return _mm_sub_epi64(x, diff);
-}
-
-// Add y to x.
-// This assumes that x is in little endian order.
-// So far I've not found a simple way to compute and add the carry using
-// xmm instructions. However, optimizing this function is not important,
-// since it is used just once during decryption.
-inline __m128i Add(__m128i x, uint64 y) {
-  // Convert to a vector of two uint64.
-  uint64 vec[2];
-  _mm_storeu_si128(reinterpret_cast<__m128i*>(vec), x);
-  // Perform the addition on the vector.
-  vec[0] += y;
-  if (y > vec[0]) {
-    vec[1]++;
-  }
-  // Convert back to xmm.
-  return _mm_loadu_si128(reinterpret_cast<__m128i*>(vec));
-}
-
-// Decrement x by 1.
-// This function assumes that the bytes of x are in little endian order.
-// Hence before using the result in EAX the bytes must be reversed, since EAX
-// requires a counter value in big endian order.
-inline __m128i Decrement(__m128i x) {
-  const __m128i zero = _mm_setzero_si128();
-  // Moves lower 64 bit of x into higher 64 bits and set the lower 64 bits to 0.
-  __m128i shifted = _mm_slli_si128(x, 8);
-  // Determines whether the lower and upper parts must be decremented.
-  // I.e. the lower 64 bits must always be decremented.
-  // The upper 64 bits must be decremented if the lower 64 bits of x are 0.
-  __m128i carries = _mm_cmpeq_epi64(shifted, zero);  // SSE4
-  // Use add since _mm_cmpeq_epi64 returns -1 for 64-bit parts that are equal.
-  return _mm_add_epi64(x, carries);
-}
-
-// Rotate a value by 32 bit to the left (assuming little endian order).
-inline __m128i RotLeft32(__m128i value) {
-  return _mm_shuffle_epi32(value, _MM_SHUFFLE(2, 1, 0, 3));
-}
-
-// Multiply a binary polynomial given in big endian order by x
-// and reduce modulo x^128 + x^7 + x^2 + x + 1
-inline __m128i MultiplyByX(__m128i value) {
-  // Convert big endian to little endian.,
-  value = Reverse(value);
-  // Sets each dword to 0xffffffff if the most significant bit of the same
-  // dword in value is set.
-  __m128i msb = _mm_srai_epi32(value, 31);
-  __m128i msb_rotated = RotLeft32(msb);
-  // Determines the carries. If the most signigicant bit in value is set,
-  // then this bit is reduced to x^7 + x^2 + x + 1
-  // (which corresponds to the constant 0x87).
-  __m128i carry = _mm_and_si128(msb_rotated, _mm_set_epi32(1, 1, 1, 0x87));
-  __m128i res = _mm_xor_si128(_mm_slli_epi32(value, 1), carry);
-  // Converts the result back to big endian order.
-  return Reverse(res);
-}
-
-// Load block[0]..block[block_size-1] into the least significant bytes of
-// a register and set the remaining bytes to 0. The efficiency of this function
-// is not critical.
-__m128i LoadPartialBlock(const uint8_t* block, size_t block_size) {
-  std::array<uint8_t, 16> tmp;
-  tmp.fill(0);
-  std::copy_n(block, block_size, tmp.begin());
-  return _mm_loadu_si128(reinterpret_cast<__m128i*>(tmp.data()));
-}
-
-// Store the block_size least significant bytes from value in
-// block[0] .. block[block_size - 1]. The efficiency of this procedure is not
-// critical.
-void StorePartialBlock(uint8_t* block, size_t block_size, __m128i value) {
-  std::array<uint8_t, 16> tmp;
-  _mm_storeu_si128(reinterpret_cast<__m128i*>(tmp.data()), value);
-  std::copy_n(tmp.begin(), block_size, block);
-}
-
-static const uint8_t kRoundConstant[11] =
-    {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
-// Returns the round constant for round i.
-uint8_t Rcon(int round) {
-  return kRoundConstant[round];
-}
-
-// Call the AESKEYGENASSIST operation on a 32-bit input.
-// This performs a rotation and a substitution with an S-box.
-// This implementation uses AESKEYGENASSIST to compute the result twice
-// and checks that the two results match.
-inline uint32 SubRot(uint32 tmp) {
-  __m128i inp = _mm_set_epi32(0, 0, tmp, 0);
-  __m128i out = _mm_aeskeygenassist_si128(inp, 0x00);
-  return _mm_extract_epi32(out, 1);
-}
-
-// Apply the S-box to the 4 bytes in a word.
-// This operation is used in the key expansion of 256-bit keys.
-// This implementation computes the result twice and checks equality.
-inline uint32 SubWord(uint32 tmp) {
-  __m128i inp = _mm_set_epi32(0, 0, tmp, 0);
-  __m128i out = _mm_aeskeygenassist_si128(inp, 0x00);
-  return _mm_extract_epi32(out, 0);
-}
-
-// The following code uses a key expansion that closely follows FIPS 197.
-// If necessary it is possible to unroll the loops.
-void Aes128KeyExpansion(const uint8_t* key, __m128i *round_key) {
-  const int Nk = 4;  // Number of words in the key
-  const int Nb = 4;  // Number of words per round key
-  const int Nr = 10;  // Number or rounds
-  uint32 *w = reinterpret_cast<uint32*>(round_key);
-  const uint32 *keywords = reinterpret_cast<const uint32*>(key);
-  for (int i = 0; i < Nk; i++) {
-    w[i] = keywords[i];
-  }
-  uint32 tmp = w[Nk - 1];
-  for (int i = Nk; i < Nb * (Nr + 1); i++) {
-    if (i % Nk == 0) {
-      tmp = SubRot(tmp) ^ Rcon(i / Nk);
-    }
-    tmp ^= w[i - Nk];
-    w[i] = tmp;
-  }
-}
-
-void Aes256KeyExpansion(const uint8_t* key, __m128i *round_key) {
-  const int Nk = 8;  // Number of words in the key
-  const int Nb = 4;  // Number of words per round key
-  const int Nr = 14;  // Number or rounds
-  uint32 *w = reinterpret_cast<uint32*>(round_key);
-  const uint32 *keywords = reinterpret_cast<const uint32*>(key);
-  for (int i = 0; i < Nk; i++) {
-    w[i] = keywords[i];
-  }
-  uint32 tmp = w[Nk - 1];
-  for (int i = Nk; i < Nb * (Nr + 1); i++) {
-    if (i % Nk == 0) {
-      tmp = SubRot(tmp) ^ Rcon(i / Nk);
-    } else if (i % 4 == 0) {
-      tmp = SubWord(tmp);
-    }
-    tmp ^= w[i - Nk];
-    w[i] = tmp;
-  }
-}
-
-bool IsValidNonceSize(size_t nonce_size) {
-  return nonce_size == 12 || nonce_size == 16;
-}
-
-bool IsValidKeySize(size_t key_size) {
-  return key_size == 16 || key_size == 32;
-}
-
-}  // namespace
-
-crypto::tink::util::StatusOr<std::unique_ptr<Aead>> AesEaxAesni::New(
-    const util::SecretData& key, size_t nonce_size_in_bytes) {
-  if (!IsValidKeySize(key.size())) {
-    return util::Status(absl::StatusCode::kInvalidArgument, "Invalid key size");
-  }
-  if (!IsValidNonceSize(nonce_size_in_bytes)) {
-    return util::Status(absl::StatusCode::kInvalidArgument,
-                        "Invalid nonce size");
-  }
-  auto eax = absl::WrapUnique(new AesEaxAesni(nonce_size_in_bytes));
-  if (!eax->SetKey(key)) {
-    return util::Status(absl::StatusCode::kInternal, "Setting AES key failed");
-  }
-  return {std::move(eax)};
-}
-
-bool AesEaxAesni::SetKey(const util::SecretData& key) {
-  size_t key_size = key.size();
-  if (key_size == 16) {
-    rounds_ = 10;
-    Aes128KeyExpansion(key.data(), round_key_->data());
-  } else if (key_size == 32) {
-    rounds_ = 14;
-    Aes256KeyExpansion(key.data(), round_key_->data());
-  } else {
-    return false;
-  }
-  // Determine the round keys for decryption.
-  (*round_dec_key_)[0] = (*round_key_)[rounds_];
-  (*round_dec_key_)[rounds_] = (*round_key_)[0];
-  for (int i = 1; i < rounds_; i++) {
-    (*round_dec_key_)[i] = _mm_aesimc_si128((*round_key_)[rounds_ - i]);
-  }
-
-  // Derive the paddings from the key.
-  __m128i zero = _mm_setzero_si128();
-  __m128i zero_encrypted = EncryptBlock(zero);
-  *B_ = MultiplyByX(zero_encrypted);
-  *P_ = MultiplyByX(*B_);
-  return true;
-}
-
-inline void AesEaxAesni::Encrypt3Decrypt1(
-    const __m128i in0,
-    const __m128i in1,
-    const __m128i in2,
-    const __m128i in_dec,
-    __m128i* out0,
-    __m128i* out1,
-    __m128i* out2,
-    __m128i* out_dec) const {
-  __m128i first_round = (*round_key_)[0];
-  __m128i tmp0 = _mm_xor_si128(in0, first_round);
-  __m128i tmp1 = _mm_xor_si128(in1, first_round);
-  __m128i tmp2 = _mm_xor_si128(in2, first_round);
-  __m128i tmp3 = _mm_xor_si128(in_dec, (*round_dec_key_)[0]);
-  for (int i = 1; i < rounds_; i++){
-    __m128i round_key = (*round_key_)[i];
-    tmp0 = _mm_aesenc_si128(tmp0, round_key);
-    tmp1 = _mm_aesenc_si128(tmp1, round_key);
-    tmp2 = _mm_aesenc_si128(tmp2, round_key);
-    tmp3 = _mm_aesdec_si128(tmp3, (*round_dec_key_)[i]);
-  }
-  __m128i last_round = (*round_key_)[rounds_];
-  *out0 = _mm_aesenclast_si128(tmp0, last_round);
-  *out1 = _mm_aesenclast_si128(tmp1, last_round);
-  *out2 = _mm_aesenclast_si128(tmp2, last_round);
-  *out_dec = _mm_aesdeclast_si128(tmp3, (*round_dec_key_)[rounds_]);
-}
-
-inline __m128i AesEaxAesni::EncryptBlock(__m128i block) const {
-  __m128i tmp = _mm_xor_si128(block, (*round_key_)[0]);
-  for (int i = 1; i < rounds_; i++){
-    tmp = _mm_aesenc_si128(tmp, (*round_key_)[i]);
-  }
-  return _mm_aesenclast_si128(tmp, (*round_key_)[rounds_]);
-}
-
-inline void AesEaxAesni::Encrypt2Blocks(
-    const __m128i in0, const __m128i in1, __m128i *out0, __m128i *out1) const {
-  __m128i tmp0 = _mm_xor_si128(in0, (*round_key_)[0]);
-  __m128i tmp1 = _mm_xor_si128(in1, (*round_key_)[0]);
-  for (int i = 1; i < rounds_; i++){
-    __m128i round_key = (*round_key_)[i];
-    tmp0 = _mm_aesenc_si128(tmp0, round_key);
-    tmp1 = _mm_aesenc_si128(tmp1, round_key);
-  }
-  __m128i last_round = (*round_key_)[rounds_];
-  *out0 = _mm_aesenclast_si128(tmp0, last_round);
-  *out1 = _mm_aesenclast_si128(tmp1, last_round);
-}
-
-__m128i AesEaxAesni::Pad(const uint8_t* data, int len) const {
-  // CHECK(0 <= len && len <= kBlockSize);
-  // TODO(bleichen): Is there a better way to load n bytes into a register
-  std::array<uint8_t, kBlockSize> tmp;
-  tmp.fill(0);
-  std::copy_n(data, len, tmp.begin());
-  if (len == kBlockSize) {
-    __m128i block = _mm_loadu_si128(reinterpret_cast<__m128i*>(tmp.data()));
-    return _mm_xor_si128(block, *B_);
-  } else {
-    tmp[len] = 0x80;
-    __m128i block = _mm_loadu_si128(reinterpret_cast<__m128i*>(tmp.data()));
-    return _mm_xor_si128(block, *P_);
-  }
-}
-
-__m128i AesEaxAesni::OMAC(absl::string_view blob, int tag) const {
-  const uint8_t* data = reinterpret_cast<const uint8_t*>(blob.data());
-  size_t len = blob.size();
-  __m128i state = _mm_set_epi32(tag << 24, 0, 0, 0);
-  if (len == 0) {
-    state = _mm_xor_si128(state, *B_);
-  } else {
-    state = EncryptBlock(state);
-    size_t idx = 0;
-    while (len - idx > kBlockSize) {
-      __m128i in = _mm_loadu_si128((__m128i*) (data + idx));
-      state = _mm_xor_si128(in, state);
-      state = EncryptBlock(state);
-      idx += kBlockSize;
-    }
-    state = _mm_xor_si128(state, Pad(data + idx, len - idx));
-  }
-  return EncryptBlock(state);
-}
-
-bool AesEaxAesni::RawEncrypt(absl::string_view nonce, absl::string_view in,
-                             absl::string_view associated_data,
-                             absl::Span<uint8_t> ciphertext) const {
-  // Sanity check
-  if (in.size() + kTagSize != ciphertext.size()) {
-    return false;
-  }
-  const uint8_t* plaintext = reinterpret_cast<const uint8_t*>(in.data());
-
-  // NOTE(bleichen): The author of EAX designed this mode, so that
-  //   it would be possible to compute N and H independently of the encryption.
-  //   So far this possiblity is not used in this implementation.
-  const __m128i N = OMAC(nonce, 0);
-  const __m128i H = OMAC(associated_data, 1);
-
-  // Compute the initial counter in little endian order.
-  // EAX uses big endian order, but it is easier to increment
-  // a counter if it is in little endian order.
-  __m128i ctr = Reverse(N);
-
-  // Initialize mac with the header of the input for the MAC.
-  __m128i mac = _mm_set_epi32(0x2000000, 0, 0, 0);
-
-  uint8_t* out = ciphertext.data();
-  size_t idx = 0;
-  __m128i key_stream;
-  while (idx + kBlockSize < in.size()) {
-    __m128i ctr_big_endian = Reverse(ctr);
-    // Get the key stream for one message block and compute
-    // the MAC for the previous ciphertext block or header.
-    Encrypt2Blocks(mac, ctr_big_endian, &mac, &key_stream);
-    __m128i pt = _mm_loadu_si128(reinterpret_cast<const __m128i*>(plaintext));
-    __m128i ct = _mm_xor_si128(pt, key_stream);
-    mac = _mm_xor_si128(mac, ct);
-    ctr = Increment(ctr);
-    _mm_storeu_si128(reinterpret_cast<__m128i*>(out), ct);
-    plaintext += kBlockSize;
-    out += kBlockSize;
-    idx += kBlockSize;
-  }
-
-  // Last block
-  size_t last_block_size = in.size() - idx;
-  if (last_block_size > 0) {
-    __m128i ctr_big_endian = Reverse(ctr);
-    Encrypt2Blocks(mac, ctr_big_endian, &mac, &key_stream);
-    __m128i pt = LoadPartialBlock(plaintext, last_block_size);
-    __m128i ct = _mm_xor_si128(pt, key_stream);
-    StorePartialBlock(out, last_block_size, ct);
-    __m128i padded_last_block = Pad(out, last_block_size);
-    out += last_block_size;
-    mac = _mm_xor_si128(mac, padded_last_block);
-  } else {
-    // Special code for plaintexts of size 0.
-    mac = _mm_xor_si128(mac, *B_);
-  }
-  mac = EncryptBlock(mac);
-  __m128i tag = _mm_xor_si128(mac, N);
-  tag = _mm_xor_si128(tag, H);
-  StorePartialBlock(out, kTagSize, tag);
-  return true;
-}
-
-bool AesEaxAesni::RawDecrypt(absl::string_view nonce, absl::string_view in,
-                             absl::string_view associated_data,
-                             absl::Span<uint8_t> plaintext) const {
-  __m128i N = OMAC(nonce, 0);
-  __m128i H = OMAC(associated_data, 1);
-
-  const uint8_t* ciphertext = reinterpret_cast<const uint8_t*>(in.data());
-  const size_t ciphertext_size = in.size();
-
-  // Sanity checks: RawDecrypt should always be called with valid sizes.
-  if (ciphertext_size < kTagSize) {
-    return false;
-  }
-  if (ciphertext_size - kTagSize != plaintext.size()) {
-    return false;
-  }
-
-  // Get the tag from the ciphertext.
-  const __m128i tag = _mm_loadu_si128(
-      reinterpret_cast<const __m128i*>(&ciphertext[plaintext.size()]));
-
-  // A CBC-MAC is reversible. This allows to pipeline the MAC verification
-  // by recomputing the MAC for the first half of the ciphertext and
-  // reversion the MAC for the second half.
-  __m128i mac_forward = _mm_set_epi32(0x2000000, 0, 0, 0);
-  __m128i mac_backward = _mm_xor_si128(tag, N);
-  mac_backward = _mm_xor_si128(mac_backward, H);
-
-  // Special case code for empty messages of size 0.
-  if (plaintext.empty()) {
-    mac_forward = _mm_xor_si128(mac_forward, *B_);
-    mac_forward = EncryptBlock(mac_forward);
-    return EqualBlocks(mac_forward, mac_backward);
-  }
-
-  const size_t last_block = (plaintext.size() - 1) / kBlockSize;
-  const size_t last_block_size = ((plaintext.size() - 1) % kBlockSize) + 1;
-  const __m128i* ciphertext_blocks =
-      reinterpret_cast<const __m128i*>(ciphertext);
-  __m128i* plaintext_blocks = reinterpret_cast<__m128i*>(plaintext.data());
-  __m128i ctr_forward = Reverse(N);
-  __m128i ctr_backward = Add(ctr_forward, last_block);
-  __m128i unused = _mm_setzero_si128();
-  __m128i stream_forward;
-  __m128i stream_backward;
-  Encrypt3Decrypt1(
-      Reverse(ctr_backward), mac_forward, unused, mac_backward,
-      &stream_backward, &mac_forward, &unused, &mac_backward);
-  __m128i ct = LoadPartialBlock(&ciphertext[plaintext.size() - last_block_size],
-                                last_block_size);
-  __m128i pt = _mm_xor_si128(ct, stream_backward);
-  StorePartialBlock(&plaintext[plaintext.size() - last_block_size],
-                    last_block_size, pt);
-  __m128i padded_last_block =
-      Pad(&ciphertext[plaintext.size() - last_block_size], last_block_size);
-  mac_backward = _mm_xor_si128(mac_backward, padded_last_block);
-  const size_t mid_block = last_block / 2;
-  // Decrypts two blocks concurrently as long as there are at least two
-  // blocks to decrypt. The two blocks are the first block not yet decrypted
-  // and the last block not yet decrypted. The reason for this is that the
-  // OMAC can be verified at the same time. mac_forward is the OMAC of leading
-  // ciphertext blocks that have already been decrypted. mac_backward is the
-  // partial result for the OMAC up to block last_block - i - 1 that is
-  // necessary so OMAC of the full encryption results in the tag received from
-  // the ciphertext.
-  for (size_t i = 0; i < mid_block; i++) {
-    ctr_backward = Decrement(ctr_backward);
-    __m128i ct_forward = _mm_loadu_si128(&ciphertext_blocks[i]);
-    __m128i ct_backward =
-        _mm_loadu_si128(&ciphertext_blocks[last_block - i - 1]);
-    mac_forward = _mm_xor_si128(mac_forward, ct_forward);
-    Encrypt3Decrypt1(
-       Reverse(ctr_forward), Reverse(ctr_backward), mac_forward, mac_backward,
-        &stream_forward, &stream_backward, &mac_forward, &mac_backward);
-    __m128i plaintext_forward = _mm_xor_si128(ct_forward, stream_forward);
-    __m128i plaintext_backward = _mm_xor_si128(ct_backward, stream_backward);
-    _mm_storeu_si128(&plaintext_blocks[i], plaintext_forward);
-    _mm_storeu_si128(&plaintext_blocks[last_block - i - 1], plaintext_backward);
-    mac_backward = _mm_xor_si128(mac_backward, ct_backward);
-    ctr_forward = Increment(ctr_forward);
-  }
-  // Decrypts and MACs another block, if there is a single block in the middle.
-  if (last_block & 1) {
-    __m128i ct = _mm_loadu_si128(&ciphertext_blocks[mid_block]);
-    mac_forward = _mm_xor_si128(mac_forward, ct);
-    Encrypt2Blocks(
-        Reverse(ctr_forward), mac_forward, &stream_forward, &mac_forward);
-    __m128i pt = _mm_xor_si128(ct, stream_forward);
-    _mm_storeu_si128(&plaintext_blocks[mid_block], pt);
-  }
-  if (!EqualBlocks(mac_forward, mac_backward)) {
-    absl::c_fill(plaintext, 0);
-    return false;
-  }
-  return true;
-}
-
-crypto::tink::util::StatusOr<std::string> AesEaxAesni::Encrypt(
-    absl::string_view plaintext, absl::string_view associated_data) const {
-  // BoringSSL expects a non-null pointer for plaintext and associated_data,
-  // regardless of whether the size is 0.
-  plaintext = internal::EnsureStringNonNull(plaintext);
-  associated_data = internal::EnsureStringNonNull(associated_data);
-
-  if (SIZE_MAX - nonce_size_ - kTagSize <= plaintext.size()) {
-    return util::Status(absl::StatusCode::kInvalidArgument,
-                        "Plaintext too long");
-  }
-  size_t ciphertext_size = plaintext.size() + nonce_size_ + kTagSize;
-  std::string ciphertext;
-  ResizeStringUninitialized(&ciphertext, ciphertext_size);
-  const std::string nonce = Random::GetRandomBytes(nonce_size_);
-  absl::c_copy(nonce, ciphertext.begin());
-  bool result = RawEncrypt(
-      nonce, plaintext, associated_data,
-      absl::MakeSpan(reinterpret_cast<uint8_t*>(&ciphertext[nonce_size_]),
-                     ciphertext_size - nonce_size_));
-  if (!result) {
-    return util::Status(absl::StatusCode::kInternal, "Encryption failed");
-  }
-  return ciphertext;
-}
-
-crypto::tink::util::StatusOr<std::string> AesEaxAesni::Decrypt(
-    absl::string_view ciphertext, absl::string_view associated_data) const {
-  // BoringSSL expects a non-null pointer for associated_data,
-  // regardless of whether the size is 0.
-  associated_data = internal::EnsureStringNonNull(associated_data);
-
-  size_t ct_size = ciphertext.size();
-  if (ct_size < nonce_size_ + kTagSize) {
-    return util::Status(absl::StatusCode::kInvalidArgument,
-                        "Ciphertext too short");
-  }
-  size_t out_size = ct_size - kTagSize - nonce_size_;
-  absl::string_view nonce = ciphertext.substr(0, nonce_size_);
-  absl::string_view encrypted =
-      ciphertext.substr(nonce_size_, ct_size - nonce_size_);
-  std::string res;
-  ResizeStringUninitialized(&res, out_size);
-  bool result = RawDecrypt(
-      nonce, encrypted, associated_data,
-      absl::MakeSpan(reinterpret_cast<uint8_t*>(&res[0]), res.size()));
-  if (!result) {
-    return util::Status(absl::StatusCode::kInternal, "Decryption failed");
-  }
-  return res;
-}
-
-}  // namespace subtle
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // __AES__
-#endif  // __SSE4_1__
-
-
diff --git a/cc/subtle/aes_eax_aesni.h b/cc/subtle/aes_eax_aesni.h
deleted file mode 100644
index 1a0021e..0000000
--- a/cc/subtle/aes_eax_aesni.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// 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
-//
-//     http://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.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_SUBTLE_AES_EAX_AESNI_H_
-#define TINK_SUBTLE_AES_EAX_AESNI_H_
-
-#ifdef __SSE4_1__
-#ifdef __AES__
-
-#include <xmmintrin.h>
-
-#include <array>
-#include <memory>
-#include <string>
-
-#include "absl/strings/string_view.h"
-#include "absl/types/span.h"
-#include "tink/aead.h"
-#include "tink/util/secret_data.h"
-#include "tink/util/status.h"
-#include "tink/util/statusor.h"
-
-namespace crypto {
-namespace tink {
-namespace subtle {
-
-// This class implements AES-EAX on CPUs that support the AESNI instruction set
-// (as well as SSE 4.1).
-// Currently the implementation supports 128 and 256 bit keys and 96 or 128 bit
-// nonces. AES-EAX allows arbitrary nonce sizes. Allowing only 96 or 128 bits
-// is a tink specific restriction.
-class AesEaxAesni : public Aead {
- public:
-  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>> New(
-      const util::SecretData& key, size_t nonce_size_in_bytes);
-
-  crypto::tink::util::StatusOr<std::string> Encrypt(
-      absl::string_view plaintext,
-      absl::string_view associated_data) const override;
-
-  crypto::tink::util::StatusOr<std::string> Decrypt(
-      absl::string_view ciphertext,
-      absl::string_view associated_data) const override;
-
- protected:
-  // The tag size is fixed for this implementation.
-  // Using the full 128-bits of the tag allows an efficient verification.
-  static constexpr size_t kTagSize = 16;
-  static constexpr size_t kBlockSize = 16;
-
-  virtual bool RawEncrypt(absl::string_view nonce, absl::string_view in,
-                          absl::string_view associated_data,
-                          absl::Span<uint8_t> ciphertext) const;
-
-  virtual bool RawDecrypt(absl::string_view nonce, absl::string_view in,
-                          absl::string_view associated_data,
-                          absl::Span<uint8_t> plaintext) const;
-
- private:
-  explicit AesEaxAesni(size_t nonce_size) : nonce_size_(nonce_size) {}
-
-  // AesEaxAesni instances are immutable objects.
-  // Therefore, the only place where SetKey should be called is in the
-  // construction, i.e. in New().
-  bool SetKey(const util::SecretData& key);
-
-  // Encrypt a single block.
-  __m128i EncryptBlock(const __m128i block) const;
-
-  // Encrypt 2 blocks with plain AES.
-  void Encrypt2Blocks(
-      const __m128i in0,
-      const __m128i in1,
-      __m128i *out0,
-      __m128i *out1) const;
-
-  // Encrypt 3 blocks and decrypts 1 block.
-  // This is used to decrypt a ciphertext and verify the MAC concurrently.
-  void Encrypt3Decrypt1(
-      const __m128i in0,
-      const __m128i in1,
-      const __m128i in2,
-      const __m128i in_dec,
-      __m128i* out0,
-      __m128i* out1,
-      __m128i* out2,
-      __m128i* out_dec) const;
-
-  // Pads a partial block of size 1 .. 16.
-  __m128i Pad(const uint8_t* data, int len) const;
-
-  // Computes an OMAC.
-  __m128i OMAC(absl::string_view blob, int tag) const;
-
-  static constexpr int kMaxRounds = 14;  // maximal number of rounds
-  static constexpr int kMaxRoundKeys =
-      kMaxRounds + 1;  // max number of round keys
-  using RoundKeys = std::array<__m128i, kMaxRoundKeys>;
-  util::SecretUniquePtr<RoundKeys> round_key_ =
-      util::MakeSecretUniquePtr<RoundKeys>();
-  util::SecretUniquePtr<RoundKeys> round_dec_key_ =
-      util::MakeSecretUniquePtr<RoundKeys>();
-  util::SecretUniquePtr<__m128i> B_ =
-      util::MakeSecretUniquePtr<__m128i>();  // Used for padding
-  util::SecretUniquePtr<__m128i> P_ =
-      util::MakeSecretUniquePtr<__m128i>();  // Used for padding
-  int rounds_;
-  const size_t nonce_size_;
-};
-
-}  // namespace subtle
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // __AES__
-#endif  // __SSE4_1__
-#endif  // TINK_SUBTLE_AES_EAX_AESNI_H_
-
diff --git a/cc/subtle/aes_eax_aesni_test.cc b/cc/subtle/aes_eax_aesni_test.cc
deleted file mode 100644
index 9f4f627..0000000
--- a/cc/subtle/aes_eax_aesni_test.cc
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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 <utility>
-#ifdef __SSE4_1__
-#ifdef __AES__
-
-#include "tink/subtle/aes_eax_aesni.h"
-
-#include <string>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "tink/subtle/wycheproof_util.h"
-#include "tink/util/secret_data.h"
-#include "tink/util/statusor.h"
-#include "tink/util/test_util.h"
-
-namespace crypto {
-namespace tink {
-namespace subtle {
-namespace {
-
-TEST(AesEaxAesniTest, testBasic) {
-  util::SecretData key = util::SecretDataFromStringView(
-      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
-  size_t nonce_size = 12;
-  auto res = AesEaxAesni::New(key, nonce_size);
-  EXPECT_TRUE(res.ok()) << res.status();
-  auto cipher = std::move(res.value());
-  std::string message = "Some data to encrypt.";
-  std::string aad = "Some data to authenticate.";
-  auto ct = cipher->Encrypt(message, aad);
-  EXPECT_TRUE(ct.ok()) << ct.status();
-  EXPECT_EQ(ct.value().size(), message.size() + nonce_size + 16);
-  auto pt = cipher->Decrypt(ct.value(), aad);
-  EXPECT_TRUE(pt.ok()) << pt.status();
-  EXPECT_EQ(pt.value(), message);
-}
-
-TEST(AesEaxAesniTest, testMessageSize) {
-  util::SecretData key = util::SecretDataFromStringView(
-      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
-  size_t nonce_size = 12;
-  auto res = AesEaxAesni::New(key, nonce_size);
-  EXPECT_TRUE(res.ok()) << res.status();
-  auto cipher = std::move(res.value());
-  for (size_t size = 0; size < 260; size++) {
-    std::string message(size, 'x');
-    std::string aad = "";
-    auto ct = cipher->Encrypt(message, aad);
-    EXPECT_TRUE(ct.ok()) << ct.status();
-    EXPECT_EQ(ct.value().size(), message.size() + nonce_size + 16);
-    auto pt = cipher->Decrypt(ct.value(), aad);
-    EXPECT_TRUE(pt.ok()) << pt.status();
-    EXPECT_EQ(pt.value(), message);
-  }
-}
-
-TEST(AesEaxAesniTest, testAadSize) {
-  util::SecretData key = util::SecretDataFromStringView(
-      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
-  size_t nonce_size = 12;
-  auto res = AesEaxAesni::New(key, nonce_size);
-  EXPECT_TRUE(res.ok()) << res.status();
-  auto cipher = std::move(res.value());
-  for (size_t size = 0; size < 260; size++) {
-    std::string message("Some message");
-    std::string aad(size, 'x');
-    auto ct = cipher->Encrypt(message, aad);
-    EXPECT_TRUE(ct.ok()) << ct.status();
-    EXPECT_EQ(ct.value().size(), message.size() + nonce_size + 16);
-    auto pt = cipher->Decrypt(ct.value(), aad);
-    EXPECT_TRUE(pt.ok()) << pt.status();
-    EXPECT_EQ(pt.value(), message);
-  }
-}
-
-TEST(AesEaxAesniTest, testLongNonce) {
-  util::SecretData key = util::SecretDataFromStringView(
-      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
-  size_t nonce_size = 16;
-  auto res = AesEaxAesni::New(key, nonce_size);
-  EXPECT_TRUE(res.ok()) << res.status();
-  auto cipher = std::move(res.value());
-  std::string message = "Some data to encrypt.";
-  std::string aad = "Some data to authenticate.";
-  auto ct = cipher->Encrypt(message, aad);
-  EXPECT_TRUE(ct.ok()) << ct.status();
-  EXPECT_EQ(ct.value().size(), message.size() + nonce_size + 16);
-  auto pt = cipher->Decrypt(ct.value(), aad);
-  EXPECT_TRUE(pt.ok()) << pt.status();
-  EXPECT_EQ(pt.value(), message);
-}
-
-TEST(AesEaxAesniTest, testModification) {
-  size_t nonce_size = 12;
-  util::SecretData key = util::SecretDataFromStringView(
-      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
-  auto cipher = std::move(AesEaxAesni::New(key, nonce_size).value());
-  std::string message = "Some data to encrypt.";
-  std::string associated_data = "Some data to authenticate.";
-  std::string ct = cipher->Encrypt(message, associated_data).value();
-  EXPECT_TRUE(cipher->Decrypt(ct, associated_data).ok());
-  // Modify the ciphertext
-  for (size_t i = 0; i < ct.size() * 8; i++) {
-    std::string modified_ct = ct;
-    modified_ct[i / 8] ^= 1 << (i % 8);
-    EXPECT_FALSE(cipher->Decrypt(modified_ct, associated_data).ok()) << i;
-  }
-  // Modify the associated data
-  for (size_t i = 0; i < associated_data.size() * 8; i++) {
-    std::string modified_associated_data = associated_data;
-    modified_associated_data[i / 8] ^= 1 << (i % 8);
-    auto decrypted = cipher->Decrypt(ct, modified_associated_data);
-    EXPECT_FALSE(decrypted.ok()) << i << " pt:" << decrypted.value();
-  }
-  // Truncate the ciphertext
-  for (size_t i = 0; i < ct.size(); i++) {
-    std::string truncated_ct(ct, 0, i);
-    EXPECT_FALSE(cipher->Decrypt(truncated_ct, associated_data).ok()) << i;
-  }
-}
-
-TEST(AesEaxAesniTest, testInvalidKeySizes) {
-  size_t nonce_size = 12;
-  for (int keysize = 0; keysize < 65; keysize++) {
-    if (keysize == 16 || keysize == 32) {
-      continue;
-    }
-    util::SecretData key(keysize, 'x');
-    auto cipher = AesEaxAesni::New(key, nonce_size);
-    EXPECT_FALSE(cipher.ok());
-  }
-}
-
-TEST(AesEaxAesniTest, testEmpty) {
-  size_t nonce_size = 12;
-  util::SecretData key = util::SecretDataFromStringView(
-      test::HexDecodeOrDie("bedcfb5a011ebc84600fcb296c15af0d"));
-  std::string nonce(test::HexDecodeOrDie("438a547a94ea88dce46c6c85"));
-  // Expected tag is an empty string with an empty tag is encrypted with
-  // the nonce above;
-  std::string tag(test::HexDecodeOrDie("9607977cd7556b1dfedf0c73a35a5197"));
-  std::string ciphertext = nonce + tag;
-  auto res = AesEaxAesni::New(key, nonce_size);
-  EXPECT_TRUE(res.ok()) << res.status();
-  auto cipher = std::move(res.value());
-
-  // Test decryption of the arguments above.
-  std::string empty_string("");
-  absl::string_view empty_string_view("");
-  absl::string_view null_string_view;
-
-  auto pt = cipher->Decrypt(ciphertext, empty_string);
-  EXPECT_TRUE(pt.ok());
-  EXPECT_EQ(0, pt.value().size());
-
-  pt = cipher->Decrypt(ciphertext, empty_string_view);
-  EXPECT_TRUE(pt.ok());
-  EXPECT_EQ(0, pt.value().size());
-
-  pt = cipher->Decrypt(ciphertext, null_string_view);
-  EXPECT_TRUE(pt.ok());
-  EXPECT_EQ(0, pt.value().size());
-
-  // Test encryption.
-  auto ct = cipher->Encrypt(empty_string, empty_string);
-  EXPECT_TRUE(ct.ok());
-  pt = cipher->Decrypt(ct.value(), empty_string);
-  EXPECT_TRUE(pt.ok());
-  EXPECT_EQ(0, pt.value().size());
-
-  ct = cipher->Encrypt(empty_string_view, empty_string_view);
-  EXPECT_TRUE(ct.ok());
-  pt = cipher->Decrypt(ct.value(), empty_string);
-  EXPECT_TRUE(pt.ok());
-  EXPECT_EQ(0, pt.value().size());
-
-  ct = cipher->Encrypt(empty_string_view, empty_string_view);
-  EXPECT_TRUE(ct.ok());
-  pt = cipher->Decrypt(ct.value(), empty_string);
-  EXPECT_TRUE(pt.ok());
-  EXPECT_EQ(0, pt.value().size());
-
-  ct = cipher->Encrypt(null_string_view, null_string_view);
-  EXPECT_TRUE(ct.ok());
-  pt = cipher->Decrypt(ct.value(), empty_string);
-  EXPECT_TRUE(pt.ok());
-  EXPECT_EQ(0, pt.value().size());
-}
-
-// Test with test vectors from project Wycheproof.
-// AesEaxAesni does not allow to pass in IVs. Therefore this test
-// can only test decryption.
-// Currently AesEaxAesni is restricted to encryption with 12 byte
-// IVs and 16 byte tags. Therefore it is necessary to skip tests with
-// other parameter sizes.
-bool WycheproofTest(const rapidjson::Document &root) {
-  int errors = 0;
-  for (const rapidjson::Value& test_group : root["testGroups"].GetArray()) {
-    const size_t iv_size = test_group["ivSize"].GetInt();
-    const size_t key_size = test_group["keySize"].GetInt();
-    const size_t tag_size = test_group["tagSize"].GetInt();
-    if (key_size != 128 && key_size != 256) {
-      // Not supported
-      continue;
-    }
-    if (iv_size != 128 && iv_size != 96) {
-      // Not supported
-      continue;
-    }
-    if (tag_size != 128) {
-      // Not supported
-      continue;
-    }
-    for (const rapidjson::Value& test : test_group["tests"].GetArray()) {
-      std::string comment = test["comment"].GetString();
-      util::SecretData key =
-          util::SecretDataFromStringView(WycheproofUtil::GetBytes(test["key"]));
-      std::string iv = WycheproofUtil::GetBytes(test["iv"]);
-      std::string msg = WycheproofUtil::GetBytes(test["msg"]);
-      std::string ct = WycheproofUtil::GetBytes(test["ct"]);
-      std::string aad = WycheproofUtil::GetBytes(test["aad"]);
-      std::string tag = WycheproofUtil::GetBytes(test["tag"]);
-      int id = test["tcId"].GetInt();
-      std::string expected = test["result"].GetString();
-      auto cipher = std::move(AesEaxAesni::New(key, iv_size / 8).value());
-      auto result = cipher->Decrypt(iv + ct + tag, aad);
-      bool success = result.ok();
-      if (success) {
-        std::string decrypted = result.value();
-        if (expected == "invalid") {
-          ADD_FAILURE() << "decrypted invalid ciphertext:" << id;
-          errors++;
-        } else if (msg != decrypted) {
-          ADD_FAILURE() << "Incorrect decryption:" << id;
-          errors++;
-        }
-      } else {
-        if (expected == "valid" || expected == "acceptable") {
-          ADD_FAILURE()
-              << "Could not decrypt test with tcId:" << id
-              << " iv_size:" << iv_size
-              << " tag_size:" << tag_size
-              << " key_size:" << key_size;
-          errors++;
-        }
-      }
-    }
-  }
-  return errors == 0;
-}
-
-TEST(AesEaxAesniTest, TestVectors) {
-  std::unique_ptr<rapidjson::Document> root =
-      WycheproofUtil::ReadTestVectors("aes_eax_test.json");
-  ASSERT_TRUE(WycheproofTest(*root));
-}
-
-}  // namespace
-}  // namespace subtle
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // __AES__
-#endif  // __SSE4_1__
diff --git a/cc/subtle/aes_eax_boringssl.cc b/cc/subtle/aes_eax_boringssl.cc
index 11eafa0..3805cab 100644
--- a/cc/subtle/aes_eax_boringssl.cc
+++ b/cc/subtle/aes_eax_boringssl.cc
@@ -66,7 +66,7 @@
 #if defined(ABSL_IS_LITTLE_ENDIAN)
   return ByteSwap(Load64(src));
 #elif defined(ABSL_IS_BIG_ENDIAN)
-  return val;
+  return Load64(src);
 #else
 #error Unknown endianness
 #endif
diff --git a/cc/subtle/aes_eax_boringssl_test.cc b/cc/subtle/aes_eax_boringssl_test.cc
index 82c5067..9e94fe3 100644
--- a/cc/subtle/aes_eax_boringssl_test.cc
+++ b/cc/subtle/aes_eax_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/aes_eax_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/cc/subtle/aes_gcm_boringssl_test.cc b/cc/subtle/aes_gcm_boringssl_test.cc
index dd1ad04..b18294a 100644
--- a/cc/subtle/aes_gcm_boringssl_test.cc
+++ b/cc/subtle/aes_gcm_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/aes_gcm_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -26,7 +27,7 @@
 #include "absl/strings/escaping.h"
 #include "absl/strings/str_cat.h"
 #include "tink/aead/internal/wycheproof_aead.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
@@ -54,7 +55,7 @@
 class AesGcmBoringSslTest : public Test {
  protected:
   void SetUp() override {
-    if (IsFipsModeEnabled() && !FIPS_mode()) {
+    if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
       GTEST_SKIP() << "Test should not run in FIPS mode when BoringCrypto is "
                       "unavailable.";
     }
@@ -224,7 +225,7 @@
 }
 
 TEST(AesGcmBoringSslFipsTest, FipsOnly) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -239,7 +240,7 @@
 }
 
 TEST(AesGcmBoringSslFipsTest, FipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -258,7 +259,7 @@
 class AesGcmBoringSslWycheproofTest
     : public TestWithParam<internal::WycheproofTestVector> {
   void SetUp() override {
-    if (IsFipsModeEnabled() && !FIPS_mode()) {
+    if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
       GTEST_SKIP() << "Test should not run in FIPS mode when BoringCrypto is "
                       "unavailable.";
     }
diff --git a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc
index 1d570f4..7f14243 100644
--- a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc
+++ b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc
@@ -21,6 +21,7 @@
 #include <limits>
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "absl/algorithm/container.h"
 #include "absl/base/config.h"
diff --git a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter_test.cc b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter_test.cc
index 8c3ed14..41910fe 100644
--- a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter_test.cc
+++ b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/aes_gcm_hkdf_stream_segment_decrypter.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/cc/subtle/aes_gcm_hkdf_stream_segment_encrypter.cc b/cc/subtle/aes_gcm_hkdf_stream_segment_encrypter.cc
index aef5e5e..970918e 100644
--- a/cc/subtle/aes_gcm_hkdf_stream_segment_encrypter.cc
+++ b/cc/subtle/aes_gcm_hkdf_stream_segment_encrypter.cc
@@ -22,6 +22,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/algorithm/container.h"
 #include "absl/base/config.h"
diff --git a/cc/subtle/aes_gcm_hkdf_streaming.cc b/cc/subtle/aes_gcm_hkdf_streaming.cc
index cd6fbfd..ab59d5d 100644
--- a/cc/subtle/aes_gcm_hkdf_streaming.cc
+++ b/cc/subtle/aes_gcm_hkdf_streaming.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/aes_gcm_hkdf_streaming.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/aes_gcm_siv_boringssl_test.cc b/cc/subtle/aes_gcm_siv_boringssl_test.cc
index 836ac24..2b39f2b 100644
--- a/cc/subtle/aes_gcm_siv_boringssl_test.cc
+++ b/cc/subtle/aes_gcm_siv_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/aes_gcm_siv_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <vector>
 
diff --git a/cc/subtle/aes_siv_boringssl.cc b/cc/subtle/aes_siv_boringssl.cc
index 6a2ff7e..3d9ac86 100644
--- a/cc/subtle/aes_siv_boringssl.cc
+++ b/cc/subtle/aes_siv_boringssl.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <iterator>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/aes_siv_boringssl_test.cc b/cc/subtle/aes_siv_boringssl_test.cc
index 6b4d081..fd91061 100644
--- a/cc/subtle/aes_siv_boringssl_test.cc
+++ b/cc/subtle/aes_siv_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/aes_siv_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -115,7 +116,7 @@
           "812731321de508761437195ff231765aa4913219873ac6918639816312130011"
           "abc900bba11400187984719827431246bbab1231eb4145215ff7141436616beb"
           "9817298148712fed3aab61000ff123313e"));
-  for (int keysize = 0; keysize <= keymaterial.size(); ++keysize){
+  for (int keysize = 0; keysize < keymaterial.size(); ++keysize){
     util::SecretData key(&keymaterial[0], &keymaterial[keysize]);
     auto cipher = AesSivBoringSsl::New(key);
     if (keysize == 64) {
diff --git a/cc/subtle/decrypting_random_access_stream.cc b/cc/subtle/decrypting_random_access_stream.cc
index 0b6d33e..5a99a53 100644
--- a/cc/subtle/decrypting_random_access_stream.cc
+++ b/cc/subtle/decrypting_random_access_stream.cc
@@ -18,6 +18,8 @@
 
 #include <algorithm>
 #include <cstring>
+#include <limits>
+#include <memory>
 #include <utility>
 #include <vector>
 
diff --git a/cc/subtle/decrypting_random_access_stream_test.cc b/cc/subtle/decrypting_random_access_stream_test.cc
index 1d404f3..8c67e2e 100644
--- a/cc/subtle/decrypting_random_access_stream_test.cc
+++ b/cc/subtle/decrypting_random_access_stream_test.cc
@@ -17,6 +17,9 @@
 #include "tink/subtle/decrypting_random_access_stream.h"
 
 #include <algorithm>
+#include <cstring>
+#include <limits>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
@@ -27,25 +30,24 @@
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "tink/internal/test_random_access_stream.h"
 #include "tink/output_stream.h"
 #include "tink/random_access_stream.h"
 #include "tink/streaming_aead.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/test_util.h"
-#include "tink/util/file_random_access_stream.h"
 #include "tink/util/ostream_output_stream.h"
 #include "tink/util/status.h"
 #include "tink/util/test_matchers.h"
-#include "tink/util/test_util.h"
 
 namespace crypto {
 namespace tink {
 namespace subtle {
 namespace {
 
+using ::crypto::tink::internal::TestRandomAccessStream;
 using crypto::tink::subtle::test::DummyStreamingAead;
 using crypto::tink::subtle::test::DummyStreamSegmentDecrypter;
-using crypto::tink::test::GetTestFileDescriptor;
 using crypto::tink::test::IsOk;
 using crypto::tink::test::StatusIs;
 using subtle::test::WriteToStream;
@@ -77,16 +79,6 @@
   int ct_offset_;
 };
 
-// Creates a RandomAccessStream with the specified contents.
-std::unique_ptr<RandomAccessStream> GetRandomAccessStream(
-    absl::string_view contents) {
-  static int index = 1;
-  std::string filename = absl::StrCat("stream_data_file_", index, ".txt");
-  index++;
-  int input_fd = GetTestFileDescriptor(filename, contents);
-  return {absl::make_unique<util::FileRandomAccessStream>(input_fd)};
-}
-
 // Returns a ciphertext resulting from encryption of 'pt' with 'aad' as
 // associated data, using 'saead'.
 std::string GetCiphertext(StreamingAead* saead, absl::string_view pt,
@@ -115,24 +107,8 @@
                                                         absl::string_view pt,
                                                         absl::string_view aad,
                                                         int ct_offset) {
-  return GetRandomAccessStream(GetCiphertext(saead, pt, aad, ct_offset));
-}
-
-// Reads the entire 'ra_stream', until no more bytes can be read,
-// and puts the read bytes into 'contents'.
-// Returns the status of the last ra_stream->PRead()-operation.
-util::Status ReadAll(RandomAccessStream* ra_stream, std::string* contents) {
-  int chunk_size = 42;
-  contents->clear();
-  auto buffer = std::move(util::Buffer::New(chunk_size).value());
-  int64_t position = 0;
-  auto status = util::OkStatus();
-  while (status.ok()) {
-    status = ra_stream->PRead(position, chunk_size, buffer.get());
-    contents->append(buffer->get_mem_block(), buffer->size());
-    position = contents->size();
-  }
-  return status;
+  return std::make_unique<TestRandomAccessStream>(
+      GetCiphertext(saead, pt, aad, ct_offset));
 }
 
 TEST(DecryptingRandomAccessStreamTest, NegativeCiphertextOffset) {
@@ -222,7 +198,8 @@
           auto dec_stream = std::move(dec_stream_result.value());
           EXPECT_EQ(pt_size, dec_stream->size().value());
           std::string decrypted;
-          auto status = ReadAll(dec_stream.get(), &decrypted);
+          auto status = internal::ReadAllFromRandomAccessStream(
+              dec_stream.get(), decrypted);
           EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange,
                                        HasSubstr("EOF")));
           EXPECT_EQ(plaintext, decrypted);
@@ -304,8 +281,8 @@
               SCOPED_TRACE(absl::StrCat("ct_size = ", ct.size(),
                                         ", trunc_ct_size = ", trunc_ct_size,
                                         ", chunk_size = ", chunk_size));
-              auto trunc_ct =
-                  GetRandomAccessStream(ct.substr(0, trunc_ct_size));
+              auto trunc_ct = std::make_unique<TestRandomAccessStream>(
+                  ct.substr(0, trunc_ct_size));
               int position = 0;
               auto per_stream_seg_decrypter =
                   absl::make_unique<DummyStreamSegmentDecrypter>(
@@ -376,8 +353,8 @@
   for (int ct_size : {0, 10, 100}) {
     SCOPED_TRACE(absl::StrCat("ct_size = ", ct_size));
     // Try decrypting a wrong ciphertext.
-    auto wrong_ct =
-        GetRandomAccessStream(subtle::Random::GetRandomBytes(ct_size));
+    auto wrong_ct = std::make_unique<TestRandomAccessStream>(
+        subtle::Random::GetRandomBytes(ct_size));
     auto seg_decrypter = absl::make_unique<DummyStreamSegmentDecrypter>(
         pt_segment_size, header_size, ct_offset);
     auto dec_stream_result = DecryptingRandomAccessStream::New(
@@ -394,7 +371,8 @@
 }
 
 TEST(DecryptingRandomAccessStreamTest, NullSegmentDecrypter) {
-  auto ct_stream = GetRandomAccessStream("some ciphertext contents");
+  auto ct_stream =
+      std::make_unique<TestRandomAccessStream>("some ciphertext contents");
   auto dec_stream_result =
       DecryptingRandomAccessStream::New(nullptr, std::move(ct_stream));
   EXPECT_THAT(dec_stream_result.status(),
diff --git a/cc/subtle/ecdsa_sign_boringssl.cc b/cc/subtle/ecdsa_sign_boringssl.cc
index e524033..a14346a 100644
--- a/cc/subtle/ecdsa_sign_boringssl.cc
+++ b/cc/subtle/ecdsa_sign_boringssl.cc
@@ -16,76 +16,24 @@
 
 #include "tink/subtle/ecdsa_sign_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
-#include "openssl/bn.h"
-#include "openssl/ec.h"
-#include "openssl/ecdsa.h"
 #include "openssl/evp.h"
-#include "tink/internal/bn_util.h"
-#include "tink/internal/ec_util.h"
-#include "tink/internal/err_util.h"
 #include "tink/internal/md_util.h"
-#include "tink/internal/ssl_unique_ptr.h"
 #include "tink/internal/util.h"
+#include "tink/signature/internal/ecdsa_raw_sign_boringssl.h"
 #include "tink/subtle/common_enums.h"
 #include "tink/subtle/subtle_util_boringssl.h"
-#include "tink/util/errors.h"
 #include "tink/util/statusor.h"
 
 namespace crypto {
 namespace tink {
 namespace subtle {
 
-namespace {
-
-// Transforms ECDSA DER signature encoding to IEEE_P1363 encoding.
-//
-// The IEEE_P1363 signature's format is r || s, where r and s are zero-padded
-// and have the same size in bytes as the order of the curve. For example, for
-// NIST P-256 curve, r and s are zero-padded to 32 bytes.
-//
-// The DER signature is encoded using ASN.1
-// (https://tools.ietf.org/html/rfc5480#appendix-A): ECDSA-Sig-Value :: =
-// SEQUENCE { r INTEGER, s INTEGER }. In particular, the encoding is: 0x30 ||
-// totalLength || 0x02 || r's length || r || 0x02 || s's length || s.
-crypto::tink::util::StatusOr<std::string> DerToIeee(absl::string_view der,
-                                                    const EC_KEY* key) {
-  size_t field_size_in_bytes =
-      (EC_GROUP_get_degree(EC_KEY_get0_group(key)) + 7) / 8;
-
-  ECDSA_SIG* ecdsa_ptr = nullptr;
-  const uint8_t* der_ptr = reinterpret_cast<const uint8_t*>(der.data());
-  // Note: d2i_ECDSA_SIG is deprecated in BoringSSL, but it isn't in OpenSSL.
-  internal::SslUniquePtr<ECDSA_SIG> ecdsa(
-      d2i_ECDSA_SIG(&ecdsa_ptr, &der_ptr, der.size()));
-  if (ecdsa == nullptr) {
-    return util::Status(absl::StatusCode::kInternal, "d2i_ECDSA_SIG failed");
-  }
-
-  const BIGNUM* r_bn;
-  const BIGNUM* s_bn;
-  ECDSA_SIG_get0(ecdsa.get(), &r_bn, &s_bn);
-  util::StatusOr<std::string> r =
-      internal::BignumToString(r_bn, field_size_in_bytes);
-  if (!r.ok()) {
-    return r.status();
-  }
-  util::StatusOr<std::string> s =
-      internal::BignumToString(s_bn, field_size_in_bytes);
-  if (!s.ok()) {
-    return s.status();
-  }
-  return absl::StrCat(*r, *s);
-}
-
-}  // namespace
-
-// static
 util::StatusOr<std::unique_ptr<EcdsaSignBoringSsl>> EcdsaSignBoringSsl::New(
     const SubtleUtilBoringSSL::EcKey& ec_key, HashType hash_type,
     EcdsaSignatureEncoding encoding) {
@@ -102,47 +50,14 @@
     return hash.status();
   }
 
-  // Check curve.
-  util::StatusOr<internal::SslUniquePtr<EC_GROUP>> group =
-      internal::EcGroupFromCurveType(ec_key.curve);
-  if (!group.ok()) {
-    return group.status();
-  }
-  internal::SslUniquePtr<EC_KEY> key(EC_KEY_new());
-  EC_KEY_set_group(key.get(), group->get());
+  util::StatusOr<std::unique_ptr<internal::EcdsaRawSignBoringSsl>> raw_sign =
+      internal::EcdsaRawSignBoringSsl::New(ec_key, encoding);
+  if (!raw_sign.ok()) return raw_sign.status();
 
-  // Check key.
-  util::StatusOr<internal::SslUniquePtr<EC_POINT>> pub_key =
-      internal::GetEcPoint(ec_key.curve, ec_key.pub_x, ec_key.pub_y);
-  if (!pub_key.ok()) {
-    return pub_key.status();
-  }
-
-  if (!EC_KEY_set_public_key(key.get(), pub_key->get())) {
-    return util::Status(
-        absl::StatusCode::kInvalidArgument,
-        absl::StrCat("Invalid public key: ", internal::GetSslErrors()));
-  }
-
-  internal::SslUniquePtr<BIGNUM> priv_key(
-      BN_bin2bn(ec_key.priv.data(), ec_key.priv.size(), nullptr));
-  if (!EC_KEY_set_private_key(key.get(), priv_key.get())) {
-    return util::Status(
-        absl::StatusCode::kInvalidArgument,
-        absl::StrCat("Invalid private key: ", internal::GetSslErrors()));
-  }
-
-  // Sign.
-  std::unique_ptr<EcdsaSignBoringSsl> sign(
-      new EcdsaSignBoringSsl(std::move(key), *hash, encoding));
-  return std::move(sign);
+  return {
+      absl::WrapUnique(new EcdsaSignBoringSsl(*hash, std::move(*raw_sign)))};
 }
 
-EcdsaSignBoringSsl::EcdsaSignBoringSsl(internal::SslUniquePtr<EC_KEY> key,
-                                       const EVP_MD* hash,
-                                       EcdsaSignatureEncoding encoding)
-    : key_(std::move(key)), hash_(hash), encoding_(encoding) {}
-
 util::StatusOr<std::string> EcdsaSignBoringSsl::Sign(
     absl::string_view data) const {
   // BoringSSL expects a non-null pointer for data,
@@ -159,24 +74,8 @@
   }
 
   // Compute the signature.
-  std::vector<uint8_t> buffer(ECDSA_size(key_.get()));
-  unsigned int sig_length;
-  if (1 != ECDSA_sign(0 /* unused */, digest, digest_size, buffer.data(),
-                      &sig_length, key_.get())) {
-    return util::Status(absl::StatusCode::kInternal, "Signing failed.");
-  }
-
-  if (encoding_ == subtle::EcdsaSignatureEncoding::IEEE_P1363) {
-    auto status_or_sig = DerToIeee(
-        std::string(reinterpret_cast<char*>(buffer.data()), sig_length),
-        key_.get());
-    if (!status_or_sig.ok()) {
-      return status_or_sig.status();
-    }
-    return status_or_sig.value();
-  }
-
-  return std::string(reinterpret_cast<char*>(buffer.data()), sig_length);
+  return raw_signer_->Sign(
+      absl::string_view(reinterpret_cast<char*>(digest), digest_size));
 }
 
 }  // namespace subtle
diff --git a/cc/subtle/ecdsa_sign_boringssl.h b/cc/subtle/ecdsa_sign_boringssl.h
index c3323ca..a05d39e 100644
--- a/cc/subtle/ecdsa_sign_boringssl.h
+++ b/cc/subtle/ecdsa_sign_boringssl.h
@@ -19,13 +19,13 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "absl/strings/string_view.h"
-#include "openssl/ec.h"
 #include "openssl/evp.h"
 #include "tink/internal/fips_utils.h"
-#include "tink/internal/ssl_unique_ptr.h"
 #include "tink/public_key_sign.h"
+#include "tink/signature/internal/ecdsa_raw_sign_boringssl.h"
 #include "tink/subtle/common_enums.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/statusor.h"
@@ -49,12 +49,13 @@
       crypto::tink::internal::FipsCompatibility::kRequiresBoringCrypto;
 
  private:
-  EcdsaSignBoringSsl(internal::SslUniquePtr<EC_KEY> key, const EVP_MD* hash,
-                     EcdsaSignatureEncoding encoding);
+  explicit EcdsaSignBoringSsl(
+      const EVP_MD* hash,
+      std::unique_ptr<internal::EcdsaRawSignBoringSsl> raw_signer)
+      : hash_(hash), raw_signer_(std::move(raw_signer)) {}
 
-  internal::SslUniquePtr<EC_KEY> key_;
   const EVP_MD* hash_;  // Owned by BoringSSL.
-  EcdsaSignatureEncoding encoding_;
+  std::unique_ptr<internal::EcdsaRawSignBoringSsl> raw_signer_;
 };
 
 }  // namespace subtle
diff --git a/cc/subtle/ecdsa_sign_boringssl_test.cc b/cc/subtle/ecdsa_sign_boringssl_test.cc
index d2a236b..666b8db 100644
--- a/cc/subtle/ecdsa_sign_boringssl_test.cc
+++ b/cc/subtle/ecdsa_sign_boringssl_test.cc
@@ -21,8 +21,8 @@
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
-#include "tink/config/tink_fips.h"
 #include "tink/internal/ec_util.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
 #include "tink/subtle/common_enums.h"
@@ -43,7 +43,7 @@
 class EcdsaSignBoringSslTest : public ::testing::Test {};
 
 TEST_F(EcdsaSignBoringSslTest, testBasicSigning) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -84,7 +84,7 @@
 }
 
 TEST_F(EcdsaSignBoringSslTest, testEncodingsMismatch) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -115,7 +115,7 @@
 }
 
 TEST_F(EcdsaSignBoringSslTest, testSignatureSizesWithIEEE_P1364Encoding) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -149,7 +149,7 @@
 }
 
 TEST_F(EcdsaSignBoringSslTest, testNewErrors) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -164,7 +164,7 @@
 
 // FIPS-only mode test
 TEST_F(EcdsaSignBoringSslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
diff --git a/cc/subtle/ecdsa_verify_boringssl.cc b/cc/subtle/ecdsa_verify_boringssl.cc
index 962d6d4..b14e475 100644
--- a/cc/subtle/ecdsa_verify_boringssl.cc
+++ b/cc/subtle/ecdsa_verify_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/ecdsa_verify_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/ecdsa_verify_boringssl_test.cc b/cc/subtle/ecdsa_verify_boringssl_test.cc
index b82dadd..292218f 100644
--- a/cc/subtle/ecdsa_verify_boringssl_test.cc
+++ b/cc/subtle/ecdsa_verify_boringssl_test.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/ecdsa_verify_boringssl.h"
 
 #include <iostream>
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -24,7 +25,7 @@
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "include/rapidjson/document.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
 #include "tink/subtle/common_enums.h"
@@ -46,7 +47,7 @@
 class EcdsaVerifyBoringSslTest : public ::testing::Test {};
 
 TEST_F(EcdsaVerifyBoringSslTest, BasicSigning) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -88,7 +89,7 @@
 }
 
 TEST_F(EcdsaVerifyBoringSslTest, EncodingsMismatch) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -124,7 +125,7 @@
 }
 
 TEST_F(EcdsaVerifyBoringSslTest, NewErrors) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -221,7 +222,7 @@
 }
 
 TEST_F(EcdsaVerifyBoringSslTest, WycheproofCurveP256) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -230,7 +231,7 @@
 }
 
 TEST_F(EcdsaVerifyBoringSslTest, WycheproofCurveP384) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -239,7 +240,7 @@
 }
 
 TEST_F(EcdsaVerifyBoringSslTest, WycheproofCurveP521) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -248,7 +249,7 @@
 }
 
 TEST_F(EcdsaVerifyBoringSslTest, WycheproofWithIeeeP1363Encoding) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -258,7 +259,7 @@
 
 // FIPS-only mode test
 TEST_F(EcdsaVerifyBoringSslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
diff --git a/cc/subtle/ecies_hkdf_recipient_kem_boringssl.cc b/cc/subtle/ecies_hkdf_recipient_kem_boringssl.cc
index 105672f..0415a99 100644
--- a/cc/subtle/ecies_hkdf_recipient_kem_boringssl.cc
+++ b/cc/subtle/ecies_hkdf_recipient_kem_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/ecies_hkdf_recipient_kem_boringssl.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/memory/memory.h"
diff --git a/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc b/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc
index d957a5f..3733920 100644
--- a/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc
+++ b/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc
@@ -18,6 +18,7 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
diff --git a/cc/subtle/ecies_hkdf_sender_kem_boringssl.cc b/cc/subtle/ecies_hkdf_sender_kem_boringssl.cc
index 86f505c..8815a06 100644
--- a/cc/subtle/ecies_hkdf_sender_kem_boringssl.cc
+++ b/cc/subtle/ecies_hkdf_sender_kem_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/ecies_hkdf_sender_kem_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc b/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
index 01df86f..0e9c815 100644
--- a/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
+++ b/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
@@ -17,8 +17,10 @@
 #include "tink/subtle/ecies_hkdf_sender_kem_boringssl.h"
 
 #include <iostream>
+#include <ostream>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
diff --git a/cc/subtle/ed25519_sign_boringssl.cc b/cc/subtle/ed25519_sign_boringssl.cc
index f8b32d4..2b715e2 100644
--- a/cc/subtle/ed25519_sign_boringssl.cc
+++ b/cc/subtle/ed25519_sign_boringssl.cc
@@ -18,6 +18,7 @@
 
 #include <algorithm>
 #include <iterator>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/ed25519_sign_boringssl_test.cc b/cc/subtle/ed25519_sign_boringssl_test.cc
index c432afc..16e34ee 100644
--- a/cc/subtle/ed25519_sign_boringssl_test.cc
+++ b/cc/subtle/ed25519_sign_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/ed25519_sign_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/ed25519_verify_boringssl.cc b/cc/subtle/ed25519_verify_boringssl.cc
index 750c603..853bb22 100644
--- a/cc/subtle/ed25519_verify_boringssl.cc
+++ b/cc/subtle/ed25519_verify_boringssl.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/ed25519_verify_boringssl.h"
 
 #include <cstring>
+#include <memory>
 #include <utility>
 
 #include "absl/memory/memory.h"
diff --git a/cc/subtle/ed25519_verify_boringssl_test.cc b/cc/subtle/ed25519_verify_boringssl_test.cc
index b3ed053..a086cc4 100644
--- a/cc/subtle/ed25519_verify_boringssl_test.cc
+++ b/cc/subtle/ed25519_verify_boringssl_test.cc
@@ -16,9 +16,11 @@
 
 #include "tink/subtle/ed25519_verify_boringssl.h"
 
+#include <iostream>
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
diff --git a/cc/subtle/encrypt_then_authenticate.cc b/cc/subtle/encrypt_then_authenticate.cc
index 9d84dce..a499a77 100644
--- a/cc/subtle/encrypt_then_authenticate.cc
+++ b/cc/subtle/encrypt_then_authenticate.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/encrypt_then_authenticate.h"
 
 #include <cstdint>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -36,7 +37,7 @@
 namespace tink {
 namespace subtle {
 
-static const std::string longToBigEndianStr(uint64_t value) {
+static std::string longToBigEndianStr(uint64_t value) {
   uint8_t bytes[8];
   for (int i = sizeof(bytes) - 1; i >= 0; i--) {
     bytes[i] = value & 0xff;
@@ -72,23 +73,20 @@
                         "associated data too long");
   }
 
-  auto ct = ind_cpa_cipher_->Encrypt(plaintext);
-  if (!ct.ok()) {
-    return ct.status();
+  auto ciphertext = ind_cpa_cipher_->Encrypt(plaintext);
+  if (!ciphertext.ok()) {
+    return ciphertext.status();
   }
-  std::string ciphertext(ct.value());
-  std::string toAuthData =
-      absl::StrCat(associated_data, ciphertext,
-                   longToBigEndianStr(associated_data_size_in_bits));
-
-  auto tag = mac_->ComputeMac(toAuthData);
+  auto tag = mac_->ComputeMac(
+      absl::StrCat(associated_data, *ciphertext,
+                   longToBigEndianStr(associated_data_size_in_bits)));
   if (!tag.ok()) {
     return tag.status();
   }
-  if (tag.value().size() != tag_size_) {
+  if (tag->size() != tag_size_) {
     return util::Status(absl::StatusCode::kInternal, "invalid tag size");
   }
-  return ciphertext.append(tag.value());
+  return ciphertext->append(tag.value());
 }
 
 util::StatusOr<std::string> EncryptThenAuthenticate::Decrypt(
@@ -112,11 +110,10 @@
 
   auto payload = ciphertext.substr(0, ciphertext.size() - tag_size_);
   auto tag = ciphertext.substr(ciphertext.size() - tag_size_, tag_size_);
-  std::string toAuthData =
-      absl::StrCat(associated_data, payload,
-                   longToBigEndianStr(associated_data_size_in_bits));
 
-  auto verified = mac_->VerifyMac(tag, toAuthData);
+  auto verified = mac_->VerifyMac(
+      tag, absl::StrCat(associated_data, payload,
+                        longToBigEndianStr(associated_data_size_in_bits)));
   if (!verified.ok()) {
     return verified;
   }
diff --git a/cc/subtle/encrypt_then_authenticate_test.cc b/cc/subtle/encrypt_then_authenticate_test.cc
index d0dfc2a..fcaa3d3 100644
--- a/cc/subtle/encrypt_then_authenticate_test.cc
+++ b/cc/subtle/encrypt_then_authenticate_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/encrypt_then_authenticate.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -274,9 +275,6 @@
 // test ensures that the overflow issue and the auth bypass vulnerability are
 // fixed.
 TEST(EncryptThenAuthenticateTest, testAuthBypassShouldNotWork) {
-// Disable this test when running with ASYLO, because it allocates more memory
-// than ASYLO can handle.
-#ifndef __ASYLO__
   int encryption_key_size = 16;
   int iv_size = 12;
   int mac_key_size = 16;
@@ -289,7 +287,11 @@
   const std::string message = "Some data to encrypt.";
   // ...with a long associated_data whose size in bits converted to an unsigned
   // 32-bit integer is 0.
-  const std::string associated_data = std::string(1 << 29, 'a');
+  std::string associated_data;
+  constexpr size_t kAssociatedDataSize = 1 << 29;
+  constexpr size_t kCiphertextSpace = 1000;
+  associated_data.reserve(kAssociatedDataSize + kCiphertextSpace);
+  associated_data.resize(kAssociatedDataSize, 'a');
   auto encrypted = cipher->Encrypt(message, associated_data);
   EXPECT_TRUE(encrypted.ok()) << encrypted.status();
   auto ct = encrypted.value();
@@ -299,10 +301,9 @@
   // Test that the 2^29-byte associated_data is NOT considered equal to an empty
   // associated_data. That is, test that a valid tag for (ciphertext,
   // associated_data) is INVALID for (associated_data + ciphertext, "").
-  ct = associated_data + ct;
+  ct = std::move(associated_data) + ct;
   decrypted = cipher->Decrypt(ct, "");
   EXPECT_FALSE(decrypted.ok());
-#endif  // __ASYLO__
 }
 
 }  // namespace
diff --git a/cc/subtle/hkdf.cc b/cc/subtle/hkdf.cc
index ef96eb7..b62da4a 100644
--- a/cc/subtle/hkdf.cc
+++ b/cc/subtle/hkdf.cc
@@ -16,20 +16,21 @@
 
 #include "tink/subtle/hkdf.h"
 
+#include <cstdint>
 #include <string>
 
 #include "absl/algorithm/container.h"
 #include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "openssl/evp.h"
-// BoringSSL and OpenSSL have incompatible ways to compute HDKF: BoringSSL
+// BoringSSL and OpenSSL have incompatible ways to compute HKDF: BoringSSL
 // provides a one-shot API HKDF, while OpenSSL doesn't make that API public, but
 // instead provides this functionality over the EVP interface, which in turn
 // doesn't provide means to compute HKDF in BoringSSL. As a consequence, we need
 // to selectively include the correct header and use different implementations.
 #ifdef OPENSSL_IS_BORINGSSL
+#include "openssl/base.h"
 #include "openssl/hkdf.h"
 #else
 #include "openssl/kdf.h"
@@ -52,11 +53,12 @@
 util::Status SslHkdf(const EVP_MD *evp_md, absl::string_view ikm,
                      absl::string_view salt, absl::string_view info,
                      absl::Span<uint8_t> out_key) {
+  const uint8_t *ikm_ptr = reinterpret_cast<const uint8_t *>(ikm.data());
+  const uint8_t *salt_ptr = reinterpret_cast<const uint8_t *>(salt.data());
+  const uint8_t *info_ptr = reinterpret_cast<const uint8_t *>(info.data());
 #ifdef OPENSSL_IS_BORINGSSL
-  if (HKDF(out_key.data(), out_key.size(), evp_md,
-           reinterpret_cast<const uint8_t *>(ikm.data()), ikm.size(),
-           reinterpret_cast<const uint8_t *>(salt.data()), salt.size(),
-           reinterpret_cast<const uint8_t *>(info.data()), info.size()) != 1) {
+  if (HKDF(out_key.data(), out_key.size(), evp_md, ikm_ptr, ikm.size(),
+           salt_ptr, salt.size(), info_ptr, info.size()) != 1) {
     return util::Status(absl::StatusCode::kInternal, "HKDF failed");
   }
   return util::OkStatus();
@@ -65,11 +67,9 @@
       EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, /*e=*/nullptr));
   if (pctx == nullptr || EVP_PKEY_derive_init(pctx.get()) <= 0 ||
       EVP_PKEY_CTX_set_hkdf_md(pctx.get(), evp_md) <= 0 ||
-      EVP_PKEY_CTX_set1_hkdf_salt(pctx.get(), salt.data(), salt.size()) <= 0 ||
-      EVP_PKEY_CTX_set1_hkdf_key(pctx.get(),
-                                 reinterpret_cast<const uint8_t *>(ikm.data()),
-                                 ikm.size()) <= 0 ||
-      EVP_PKEY_CTX_add1_hkdf_info(pctx.get(), info.data(), info.size()) <= 0) {
+      EVP_PKEY_CTX_set1_hkdf_salt(pctx.get(), salt_ptr, salt.size()) <= 0 ||
+      EVP_PKEY_CTX_set1_hkdf_key(pctx.get(), ikm_ptr, ikm.size()) <= 0 ||
+      EVP_PKEY_CTX_add1_hkdf_info(pctx.get(), info_ptr, info.size()) <= 0) {
     return util::Status(absl::StatusCode::kInternal,
                         "EVP_PKEY_CTX setup failed");
   }
diff --git a/cc/subtle/hkdf_test.cc b/cc/subtle/hkdf_test.cc
index 77d272c..1cf665c 100644
--- a/cc/subtle/hkdf_test.cc
+++ b/cc/subtle/hkdf_test.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/hkdf.h"
 
 #include <string>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/strings/escaping.h"
diff --git a/cc/subtle/hmac_boringssl.cc b/cc/subtle/hmac_boringssl.cc
index 9cd609b..da2bed3 100644
--- a/cc/subtle/hmac_boringssl.cc
+++ b/cc/subtle/hmac_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/hmac_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/hmac_boringssl_test.cc b/cc/subtle/hmac_boringssl_test.cc
index 79a45d4..5e4b905 100644
--- a/cc/subtle/hmac_boringssl_test.cc
+++ b/cc/subtle/hmac_boringssl_test.cc
@@ -22,7 +22,7 @@
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "absl/strings/escaping.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/mac.h"
 #include "tink/subtle/common_enums.h"
 #include "tink/util/secret_data.h"
@@ -57,7 +57,7 @@
 };
 
 TEST_F(HmacBoringSslTest, testBasic) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -71,29 +71,29 @@
   { // Test with some example data.
     std::string data = "Some data to test.";
     auto res = hmac->ComputeMac(data);
-    EXPECT_TRUE(res.ok()) << res.status().ToString();
+    EXPECT_TRUE(res.ok()) << res.status();
     std::string tag = res.value();
     EXPECT_EQ(tag_size, tag.size());
     EXPECT_EQ(tag, absl::HexStringToBytes("9ccdca5b7fffb690df396e4ac49b9cd4"));
     auto status = hmac->VerifyMac(tag, data);
-    EXPECT_TRUE(status.ok()) << "tag:" << absl::BytesToHexString(tag)
-                             << " status:" << status.ToString();
+    EXPECT_TRUE(status.ok())
+        << "tag:" << absl::BytesToHexString(tag) << " status:" << status;
   }
   { // Test with empty example data.
     absl::string_view data;
     auto res = hmac->ComputeMac(data);
-    EXPECT_TRUE(res.ok()) << res.status().ToString();
+    EXPECT_TRUE(res.ok()) << res.status();
     std::string tag = res.value();
     EXPECT_EQ(tag_size, tag.size());
     EXPECT_EQ(tag, absl::HexStringToBytes("5433122f77bcf8a4d9b874b4149823ef"));
     auto status = hmac->VerifyMac(tag, data);
-    EXPECT_TRUE(status.ok()) << "tag:" << absl::BytesToHexString(tag)
-                             << " status:" << status.ToString();
+    EXPECT_TRUE(status.ok())
+        << "tag:" << absl::BytesToHexString(tag) << " status:" << status;
   }
 }
 
 TEST_F(HmacBoringSslTest, testModification) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -106,7 +106,7 @@
   std::string data = "Some data to test";
   std::string tag = hmac->ComputeMac(data).value();
   auto status = hmac->VerifyMac(tag, data);
-  EXPECT_TRUE(status.ok()) << status.ToString();
+  EXPECT_TRUE(status.ok()) << status;
   size_t bits = tag.size() * 8;
   for (size_t i = 0; i < bits; i++) {
     std::string modified_tag = tag;
@@ -118,7 +118,7 @@
 }
 
 TEST_F(HmacBoringSslTest, testTruncation) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -131,7 +131,7 @@
   std::string data = "Some data to test";
   std::string tag = hmac->ComputeMac(data).value();
   auto status = hmac->VerifyMac(tag, data);
-  EXPECT_TRUE(status.ok()) << status.ToString();
+  EXPECT_TRUE(status.ok()) << status;
   for (size_t i = 0; i < tag.size(); i++) {
     std::string modified_tag(tag, 0, i);
     EXPECT_FALSE(hmac->VerifyMac(modified_tag, data).ok())
@@ -141,7 +141,7 @@
 }
 
 TEST_F(HmacBoringSslTest, testInvalidKeySizes) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
   }
@@ -160,7 +160,7 @@
 }
 
 TEST_F(HmacBoringSslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
diff --git a/cc/subtle/ind_cpa_cipher.h b/cc/subtle/ind_cpa_cipher.h
index 6a6fc77..373f6b8 100644
--- a/cc/subtle/ind_cpa_cipher.h
+++ b/cc/subtle/ind_cpa_cipher.h
@@ -42,7 +42,7 @@
   virtual crypto::tink::util::StatusOr<std::string> Decrypt(
       absl::string_view ciphertext) const = 0;
 
-  virtual ~IndCpaCipher() {}
+  virtual ~IndCpaCipher() = default;
 };
 
 }  // namespace subtle
diff --git a/cc/subtle/mac/stateful_mac.h b/cc/subtle/mac/stateful_mac.h
index e150a3a..d0a9b4e 100644
--- a/cc/subtle/mac/stateful_mac.h
+++ b/cc/subtle/mac/stateful_mac.h
@@ -27,6 +27,7 @@
 #ifndef TINK_SUBTLE_MAC_STATEFUL_MAC_H_
 #define TINK_SUBTLE_MAC_STATEFUL_MAC_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
@@ -39,8 +40,8 @@
 
 class StatefulMac {
  public:
-  StatefulMac() {}
-  virtual ~StatefulMac() {}
+  StatefulMac() = default;
+  virtual ~StatefulMac() = default;
 
   virtual util::Status Update(absl::string_view data) = 0;
   virtual util::StatusOr<std::string> Finalize() = 0;
@@ -48,7 +49,7 @@
 
 class StatefulMacFactory {
  public:
-  virtual ~StatefulMacFactory() {}
+  virtual ~StatefulMacFactory() = default;
 
   virtual util::StatusOr<std::unique_ptr<StatefulMac>> Create() const = 0;
 };
diff --git a/cc/subtle/nonce_based_streaming_aead.cc b/cc/subtle/nonce_based_streaming_aead.cc
index 15e9768..4a346d3 100644
--- a/cc/subtle/nonce_based_streaming_aead.cc
+++ b/cc/subtle/nonce_based_streaming_aead.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/nonce_based_streaming_aead.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/strings/string_view.h"
diff --git a/cc/subtle/pem_parser_boringssl.cc b/cc/subtle/pem_parser_boringssl.cc
index bebc04e..202e7c1 100644
--- a/cc/subtle/pem_parser_boringssl.cc
+++ b/cc/subtle/pem_parser_boringssl.cc
@@ -358,7 +358,7 @@
                         "PEM Public Key parsing failed");
   }
   // No need to free bssl_ecdsa_key after use.
-  EC_KEY* bssl_ecdsa_key = EVP_PKEY_get0_EC_KEY(evp_ecdsa_key.get());
+  const EC_KEY* bssl_ecdsa_key = EVP_PKEY_get0_EC_KEY(evp_ecdsa_key.get());
   auto is_valid = VerifyEcdsaKey(bssl_ecdsa_key);
   if (!is_valid.ok()) {
     return is_valid;
@@ -413,7 +413,7 @@
   }
 
   // No need to free bssl_ecdsa_key after use.
-  EC_KEY* bssl_ecdsa_key = EVP_PKEY_get0_EC_KEY(evp_ecdsa_key.get());
+  const EC_KEY* bssl_ecdsa_key = EVP_PKEY_get0_EC_KEY(evp_ecdsa_key.get());
   util::Status verify_result = VerifyEcdsaKey(bssl_ecdsa_key);
   if (!verify_result.ok()) {
     return verify_result;
diff --git a/cc/subtle/prf/hkdf_streaming_prf.cc b/cc/subtle/prf/hkdf_streaming_prf.cc
index 63410f7..e39e745 100644
--- a/cc/subtle/prf/hkdf_streaming_prf.cc
+++ b/cc/subtle/prf/hkdf_streaming_prf.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/prf/hkdf_streaming_prf.h"
 
 #include <algorithm>
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -90,8 +91,8 @@
     }
     ti_.resize(digest_size);
 
-    // BoringSSL's `HDKF_extract` function is implemented as an HMAC [1]. We
-    // replace calls to `HDKF_extract` with a direct call to `HMAC` to make this
+    // BoringSSL's `HKDF_extract` function is implemented as an HMAC [1]. We
+    // replace calls to `HKDF_extract` with a direct call to `HMAC` to make this
     // compatible to OpenSSL, which doesn't expose `HKDF*` functions.
     //
     // [1] https://github.com/google/boringssl/blob/master/crypto/hkdf/hkdf.c#L42
diff --git a/cc/subtle/prf/hkdf_streaming_prf_test.cc b/cc/subtle/prf/hkdf_streaming_prf_test.cc
index 867bd20..fb2e45f 100644
--- a/cc/subtle/prf/hkdf_streaming_prf_test.cc
+++ b/cc/subtle/prf/hkdf_streaming_prf_test.cc
@@ -16,6 +16,9 @@
 
 #include "tink/subtle/prf/hkdf_streaming_prf.h"
 
+#include <algorithm>
+#include <iterator>
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -23,6 +26,7 @@
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
 #include "tink/config/tink_fips.h"
+#include "tink/subtle/common_enums.h"
 #include "tink/subtle/hkdf.h"
 #include "tink/subtle/random.h"
 #include "tink/util/input_stream_util.h"
@@ -65,6 +69,43 @@
   EXPECT_THAT(result_or.value(), SizeIs(10));
 }
 
+TEST(HkdfStreamingPrf, EmptySalt) {
+  if (IsFipsModeEnabled()) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
+  crypto::tink::subtle::HashType hash_type = SHA512;
+  const int hash_length = 64;
+  util::SecretData secret = util::SecretDataFromStringView("key0123456");
+  absl::string_view input = "input";
+  int num_bytes = 10;
+
+  std::string prf_empty_salt;
+  std::string prf_zeroed_salt;
+  {
+    auto streaming_prf = HkdfStreamingPrf::New(hash_type, secret, "");
+    ASSERT_THAT(streaming_prf, IsOk());
+    std::unique_ptr<InputStream> stream = (*streaming_prf)->ComputePrf(input);
+    auto result = ReadBytesFromStream(num_bytes, stream.get());
+    ASSERT_THAT(result, IsOk());
+    prf_empty_salt = *result;
+  }
+  {
+    uint8_t zeroedSalt[hash_length];
+    std::fill(std::begin(zeroedSalt), std::end(zeroedSalt), 0);
+    auto streaming_prf = HkdfStreamingPrf::New(hash_type, secret,
+                                               std::string((char*)zeroedSalt));
+    ASSERT_THAT(streaming_prf, IsOk());
+    std::unique_ptr<InputStream> stream = (*streaming_prf)->ComputePrf(input);
+    auto result = ReadBytesFromStream(num_bytes, stream.get());
+    ASSERT_THAT(result, IsOk());
+    prf_zeroed_salt = *result;
+  }
+  EXPECT_THAT(prf_empty_salt, SizeIs(num_bytes));
+  EXPECT_THAT(prf_zeroed_salt, SizeIs(num_bytes));
+  ASSERT_EQ(prf_empty_salt, prf_zeroed_salt);
+}
+
 TEST(HkdfStreamingPrf, DifferentInputsGiveDifferentvalues) {
   if (IsFipsModeEnabled()) {
     GTEST_SKIP() << "Not supported in FIPS-only mode";
@@ -528,12 +569,11 @@
   std::string salt = Random::GetRandomBytes(234);
   std::string info = Random::GetRandomBytes(345);
 
-  auto streaming_result_or = ComputeWithHkdfStreamingPrf(
-      hash, ikm, salt, info, 456);
+  auto streaming_result_or =
+      ComputeWithHkdfStreamingPrf(hash, ikm, salt, info, 456);
   ASSERT_THAT(streaming_result_or, IsOk());
 
-  auto compute_hkdf_result_or =  Hkdf::ComputeHkdf(
-      hash, ikm, salt, info, 456);
+  auto compute_hkdf_result_or = Hkdf::ComputeHkdf(hash, ikm, salt, info, 456);
   ASSERT_THAT(compute_hkdf_result_or, IsOk());
 
   util::SecretData compute_hkdf_result =
diff --git a/cc/subtle/prf/streaming_prf.h b/cc/subtle/prf/streaming_prf.h
index 7ba055d..76eeff8 100644
--- a/cc/subtle/prf/streaming_prf.h
+++ b/cc/subtle/prf/streaming_prf.h
@@ -38,7 +38,7 @@
   virtual std::unique_ptr<InputStream> ComputePrf(
       absl::string_view input) const = 0;
 
-  virtual ~StreamingPrf() {}
+  virtual ~StreamingPrf() = default;
 };
 
 }  // namespace tink
diff --git a/cc/subtle/prf/streaming_prf_wrapper.cc b/cc/subtle/prf/streaming_prf_wrapper.cc
index 85e4a93..fafc370 100644
--- a/cc/subtle/prf/streaming_prf_wrapper.cc
+++ b/cc/subtle/prf/streaming_prf_wrapper.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/prf/streaming_prf_wrapper.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/status/status.h"
@@ -36,7 +37,7 @@
     return streaming_prf_set_->get_primary()->get_primitive().ComputePrf(input);
   }
 
-  ~StreamingPrfSetWrapper() override {}
+  ~StreamingPrfSetWrapper() override = default;
 
  private:
   std::unique_ptr<PrimitiveSet<StreamingPrf>> streaming_prf_set_;
diff --git a/cc/subtle/prf/streaming_prf_wrapper.h b/cc/subtle/prf/streaming_prf_wrapper.h
index 5d8f0c8..82d5dc4 100644
--- a/cc/subtle/prf/streaming_prf_wrapper.h
+++ b/cc/subtle/prf/streaming_prf_wrapper.h
@@ -17,6 +17,8 @@
 #ifndef TINK_SUBTLE_PRF_STREAMING_PRF_WRAPPER_H_
 #define TINK_SUBTLE_PRF_STREAMING_PRF_WRAPPER_H_
 
+#include <memory>
+
 #include "tink/primitive_set.h"
 #include "tink/primitive_wrapper.h"
 #include "tink/subtle/prf/streaming_prf.h"
diff --git a/cc/subtle/prf/streaming_prf_wrapper_test.cc b/cc/subtle/prf/streaming_prf_wrapper_test.cc
index fa8a90c..eb5ee6c 100644
--- a/cc/subtle/prf/streaming_prf_wrapper_test.cc
+++ b/cc/subtle/prf/streaming_prf_wrapper_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/subtle/prf/streaming_prf_wrapper.h"
 
+#include <memory>
+#include <sstream>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
index 4cbc58f..ec1d9bf 100644
--- a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
+++ b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/rsa_ssa_pkcs1_sign_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl_test.cc b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl_test.cc
index dab0d7d..e1ae90b 100644
--- a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl_test.cc
+++ b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl_test.cc
@@ -25,7 +25,7 @@
 #include "openssl/bn.h"
 #include "openssl/crypto.h"
 #include "openssl/rsa.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/internal/rsa_util.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/subtle/rsa_ssa_pkcs1_verify_boringssl.h"
@@ -58,7 +58,7 @@
 };
 
 TEST_F(RsaPkcs1SignBoringsslTest, EncodesPkcs1) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -78,7 +78,7 @@
 }
 
 TEST_F(RsaPkcs1SignBoringsslTest, EncodesPkcs1WithSeparateHashes) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -98,7 +98,7 @@
 }
 
 TEST_F(RsaPkcs1SignBoringsslTest, RejectsUnsafeHash) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -108,7 +108,7 @@
 }
 
 TEST_F(RsaPkcs1SignBoringsslTest, RejectsInvalidCrtParams) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -143,7 +143,7 @@
 
 // FIPS-only mode test
 TEST_F(RsaPkcs1SignBoringsslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -154,7 +154,7 @@
 }
 
 TEST_F(RsaPkcs1SignBoringsslTest, TestRestrictedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
 
@@ -171,7 +171,7 @@
 }
 
 TEST_F(RsaPkcs1SignBoringsslTest, TestAllowedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
 
diff --git a/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.cc b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.cc
index 7d8ee33..8164591 100644
--- a/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.cc
+++ b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/rsa_ssa_pkcs1_verify_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/rsa_ssa_pkcs1_verify_boringssl_test.cc b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl_test.cc
index b4d8d82..a5e560a 100644
--- a/cc/subtle/rsa_ssa_pkcs1_verify_boringssl_test.cc
+++ b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl_test.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/rsa_ssa_pkcs1_verify_boringssl.h"
 
 #include <iostream>
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -26,8 +27,8 @@
 #include "absl/strings/str_cat.h"
 #include "openssl/bn.h"
 #include "include/rapidjson/document.h"
-#include "tink/config/tink_fips.h"
 #include "tink/internal/err_util.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/internal/rsa_util.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/public_key_sign.h"
@@ -86,7 +87,7 @@
     HashType::SHA256};
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, BasicVerify) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -102,7 +103,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, NewErrors) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -132,7 +133,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, Modification) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -257,7 +258,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs12048SHA256) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
   ASSERT_TRUE(TestSignatures("rsa_signature_2048_sha256_test.json",
@@ -265,7 +266,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs13072SHA256) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -274,7 +275,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs13072SHA512) {
-  if (IsFipsModeEnabled() && !FIPS_mode()) {
+  if (internal::IsFipsModeEnabled() && !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test is skipped if kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -283,7 +284,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs14096SHA512) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
   ASSERT_TRUE(TestSignatures("rsa_signature_4096_sha512_test.json",
@@ -292,7 +293,7 @@
 
 // FIPS-only mode test
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -304,7 +305,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, TestAllowedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
 
@@ -323,7 +324,7 @@
 }
 
 TEST_F(RsaSsaPkcs1VerifyBoringSslTest, TestRestrictedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
 
diff --git a/cc/subtle/rsa_ssa_pss_sign_boringssl.cc b/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
index cd873b2..43736a2 100644
--- a/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
+++ b/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/rsa_ssa_pss_sign_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/cc/subtle/rsa_ssa_pss_sign_boringssl_test.cc b/cc/subtle/rsa_ssa_pss_sign_boringssl_test.cc
index dddc52c..1b6feee 100644
--- a/cc/subtle/rsa_ssa_pss_sign_boringssl_test.cc
+++ b/cc/subtle/rsa_ssa_pss_sign_boringssl_test.cc
@@ -22,7 +22,7 @@
 #include "absl/strings/escaping.h"
 #include "openssl/bn.h"
 #include "openssl/rsa.h"
-#include "tink/config/tink_fips.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/internal/rsa_util.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/subtle/rsa_ssa_pss_verify_boringssl.h"
@@ -55,7 +55,7 @@
 };
 
 TEST_F(RsaPssSignBoringsslTest, EncodesPss) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -77,7 +77,7 @@
 }
 
 TEST_F(RsaPssSignBoringsslTest, EncodesPssWithSeparateHashes) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -99,7 +99,7 @@
 }
 
 TEST_F(RsaPssSignBoringsslTest, RejectsInvalidPaddingHash) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -111,7 +111,7 @@
 }
 
 TEST_F(RsaPssSignBoringsslTest, RejectsUnsafePaddingHash) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -123,7 +123,7 @@
 }
 
 TEST_F(RsaPssSignBoringsslTest, RejectsInvalidCrtParams) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
 
@@ -160,7 +160,7 @@
 
 // FIPS-only mode test
 TEST_F(RsaPssSignBoringsslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -173,7 +173,7 @@
 }
 
 TEST_F(RsaPssSignBoringsslTest, TestRestrictedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
   internal::RsaPrivateKey private_key;
@@ -191,7 +191,7 @@
 }
 
 TEST_F(RsaPssSignBoringsslTest, TestAllowedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
   internal::RsaPrivateKey private_key;
diff --git a/cc/subtle/rsa_ssa_pss_verify_boringssl.cc b/cc/subtle/rsa_ssa_pss_verify_boringssl.cc
index f5e5b76..e1dbe02 100644
--- a/cc/subtle/rsa_ssa_pss_verify_boringssl.cc
+++ b/cc/subtle/rsa_ssa_pss_verify_boringssl.cc
@@ -17,8 +17,10 @@
 #include "tink/subtle/rsa_ssa_pss_verify_boringssl.h"
 
 #include <cstdint>
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
diff --git a/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc b/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc
index f654bda..20c8921 100644
--- a/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc
+++ b/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc
@@ -19,6 +19,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
@@ -28,8 +29,8 @@
 #include "absl/strings/string_view.h"
 #include "openssl/bn.h"
 #include "include/rapidjson/document.h"
-#include "tink/config/tink_fips.h"
 #include "tink/internal/err_util.h"
+#include "tink/internal/fips_utils.h"
 #include "tink/internal/rsa_util.h"
 #include "tink/internal/ssl_unique_ptr.h"
 #include "tink/public_key_sign.h"
@@ -64,7 +65,7 @@
   int salt_length;
 };
 
-const NistTestVector GetNistTestVector() {
+NistTestVector GetNistTestVector() {
   NistTestVector test_vector = {
       absl::HexStringToBytes(
           "a47d04e7cacdba4ea26eca8a4c6e14563c2ce03b623b768c0d49868a57121301dbf7"
@@ -98,7 +99,7 @@
 }
 
 TEST(RsaSsaPssVerifyBoringSslTest, BasicVerify) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
   const NistTestVector kNistTestVector = GetNistTestVector();
@@ -118,7 +119,7 @@
 }
 
 TEST(RsaSsaPssVerifyBoringSslTest, NewErrors) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
   const NistTestVector kNistTestVector = GetNistTestVector();
@@ -152,7 +153,7 @@
 }
 
 TEST(RsaSsaPssVerifyBoringSslTest, Modification) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
   const NistTestVector kNistTestVector = GetNistTestVector();
@@ -252,7 +253,7 @@
 
 // Tests signature verification using a test vector.
 TEST_P(RsaSsaPssWycheproofTest, SignatureVerify) {
-  if (IsFipsModeEnabled()) {
+  if (internal::IsFipsModeEnabled()) {
     GTEST_SKIP() << "Test not run in FIPS-only mode";
   }
   RsaSsaPssWycheproofTestVector params = GetParam();
@@ -304,7 +305,7 @@
 
 // FIPS-only mode test
 TEST(RsaSsaPssVerifyBoringSslTest, TestFipsFailWithoutBoringCrypto) {
-  if (!IsFipsModeEnabled() || FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP()
         << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
   }
@@ -320,7 +321,7 @@
 }
 
 TEST(RsaSsaPssVerifyBoringSslTest, TestAllowedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
 
@@ -341,7 +342,7 @@
 }
 
 TEST(RsaSsaPssVerifyBoringSslTest, TestRestrictedFipsModuli) {
-  if (!IsFipsModeEnabled() || !FIPS_mode()) {
+  if (!internal::IsFipsModeEnabled() || !internal::IsFipsEnabledInSsl()) {
     GTEST_SKIP() << "Test assumes kOnlyUseFips and BoringCrypto.";
   }
 
diff --git a/cc/subtle/stateful_cmac_boringssl.cc b/cc/subtle/stateful_cmac_boringssl.cc
index 951e126..09279c4 100644
--- a/cc/subtle/stateful_cmac_boringssl.cc
+++ b/cc/subtle/stateful_cmac_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/stateful_cmac_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/stateful_cmac_boringssl_test.cc b/cc/subtle/stateful_cmac_boringssl_test.cc
index f85d017..946676f 100644
--- a/cc/subtle/stateful_cmac_boringssl_test.cc
+++ b/cc/subtle/stateful_cmac_boringssl_test.cc
@@ -19,6 +19,7 @@
 #include <cstddef>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
diff --git a/cc/subtle/stateful_hmac_boringssl.cc b/cc/subtle/stateful_hmac_boringssl.cc
index 631b630..16c3e54 100644
--- a/cc/subtle/stateful_hmac_boringssl.cc
+++ b/cc/subtle/stateful_hmac_boringssl.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/stateful_hmac_boringssl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/stream_segment_decrypter.h b/cc/subtle/stream_segment_decrypter.h
index 55425e3..6604125 100644
--- a/cc/subtle/stream_segment_decrypter.h
+++ b/cc/subtle/stream_segment_decrypter.h
@@ -72,7 +72,7 @@
   //   segment_overhead = ciphertext_segment_size - get_plaintext_segment_size()
   virtual int get_ciphertext_offset() const = 0;
 
-  virtual ~StreamSegmentDecrypter() {}
+  virtual ~StreamSegmentDecrypter() = default;
 };
 
 }  // namespace subtle
diff --git a/cc/subtle/stream_segment_encrypter.h b/cc/subtle/stream_segment_encrypter.h
index 93f6952..ff8aeba 100644
--- a/cc/subtle/stream_segment_encrypter.h
+++ b/cc/subtle/stream_segment_encrypter.h
@@ -98,7 +98,7 @@
   //   segment_overhead = ciphertext_segment_size - get_plaintext_segment_size()
   virtual int get_ciphertext_offset() const = 0;
 
-  virtual ~StreamSegmentEncrypter() {}
+  virtual ~StreamSegmentEncrypter() = default;
 
  protected:
   // Increments the segment number.
diff --git a/cc/subtle/streaming_aead_decrypting_stream.cc b/cc/subtle/streaming_aead_decrypting_stream.cc
index 01c26b8..c46293b 100644
--- a/cc/subtle/streaming_aead_decrypting_stream.cc
+++ b/cc/subtle/streaming_aead_decrypting_stream.cc
@@ -18,7 +18,9 @@
 
 #include <algorithm>
 #include <cstring>
+#include <memory>
 #include <utility>
+#include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
diff --git a/cc/subtle/streaming_aead_decrypting_stream_test.cc b/cc/subtle/streaming_aead_decrypting_stream_test.cc
index f8579f7..9569caa 100644
--- a/cc/subtle/streaming_aead_decrypting_stream_test.cc
+++ b/cc/subtle/streaming_aead_decrypting_stream_test.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/streaming_aead_decrypting_stream.h"
 
 #include <algorithm>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
diff --git a/cc/subtle/streaming_aead_encrypting_stream.cc b/cc/subtle/streaming_aead_encrypting_stream.cc
index 5fb4741..419bc06 100644
--- a/cc/subtle/streaming_aead_encrypting_stream.cc
+++ b/cc/subtle/streaming_aead_encrypting_stream.cc
@@ -18,7 +18,9 @@
 
 #include <algorithm>
 #include <cstring>
+#include <memory>
 #include <utility>
+#include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
diff --git a/cc/subtle/streaming_aead_encrypting_stream_test.cc b/cc/subtle/streaming_aead_encrypting_stream_test.cc
index aa2a5e8..cba162f 100644
--- a/cc/subtle/streaming_aead_encrypting_stream_test.cc
+++ b/cc/subtle/streaming_aead_encrypting_stream_test.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/streaming_aead_encrypting_stream.h"
 
 #include <algorithm>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
diff --git a/cc/subtle/streaming_aead_test_util.cc b/cc/subtle/streaming_aead_test_util.cc
index 9c21c00..bc4019c 100644
--- a/cc/subtle/streaming_aead_test_util.cc
+++ b/cc/subtle/streaming_aead_test_util.cc
@@ -16,39 +16,30 @@
 #include "tink/subtle/streaming_aead_test_util.h"
 
 #include <algorithm>
+#include <cstring>
+#include <memory>
 #include <sstream>
 #include <string>
 #include <utility>
 
+#include "tink/internal/test_random_access_stream.h"
 #include "tink/random_access_stream.h"
 #include "tink/subtle/test_util.h"
 #include "tink/util/buffer.h"
-#include "tink/util/file_random_access_stream.h"
 #include "tink/util/istream_input_stream.h"
 #include "tink/util/ostream_output_stream.h"
 #include "tink/util/status.h"
-#include "tink/util/test_util.h"
 
 namespace crypto {
 namespace tink {
 
-using ::crypto::tink::test::GetTestFileDescriptor;
+using ::crypto::tink::internal::TestRandomAccessStream;
 using ::crypto::tink::util::IstreamInputStream;
 using ::crypto::tink::util::OstreamOutputStream;
 using ::crypto::tink::util::Status;
 
 namespace {
 
-// Creates a RandomAccessStream with the specified contents.
-std::unique_ptr<RandomAccessStream> GetRandomAccessStreamContaining(
-    absl::string_view contents) {
-  static int index = 1;
-  std::string filename = absl::StrCat("stream_data_file_", index, ".txt");
-  index++;
-  int input_fd = GetTestFileDescriptor(filename, contents);
-  return {absl::make_unique<util::FileRandomAccessStream>(input_fd)};
-}
-
 // Reads up to 'count' bytes from 'ras' starting at position 'pos'
 // and verifies that the read bytes are equal to the corresponding
 // subsequence in 'full_contents'.
@@ -136,7 +127,8 @@
   }
 
   // Prepare a RandomAccessStream with the ciphertext.
-  auto ct_ras = GetRandomAccessStreamContaining(std::string(ct_buf->str()));
+  auto ct_ras =
+      std::make_unique<TestRandomAccessStream>(std::string(ct_buf->str()));
 
   // Decrypt fragments of the ciphertext using the decrypter.
   auto dec_ras_result = decrypter->NewDecryptingRandomAccessStream(
diff --git a/cc/subtle/streaming_mac_impl.cc b/cc/subtle/streaming_mac_impl.cc
index 029c7ec..f200e69 100644
--- a/cc/subtle/streaming_mac_impl.cc
+++ b/cc/subtle/streaming_mac_impl.cc
@@ -17,6 +17,7 @@
 #include "tink/subtle/streaming_mac_impl.h"
 
 #include <algorithm>
+#include <memory>
 #include <string>
 #include <utility>
 
diff --git a/cc/subtle/streaming_mac_impl_test.cc b/cc/subtle/streaming_mac_impl_test.cc
index 0672f12..d7b8f04 100644
--- a/cc/subtle/streaming_mac_impl_test.cc
+++ b/cc/subtle/streaming_mac_impl_test.cc
@@ -16,8 +16,10 @@
 
 #include "tink/subtle/streaming_mac_impl.h"
 
+#include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
@@ -40,8 +42,8 @@
 
 class DummyStatefulMacFactory : public StatefulMacFactory {
  public:
-  DummyStatefulMacFactory() {}
-  ~DummyStatefulMacFactory() override {}
+  DummyStatefulMacFactory() = default;
+  ~DummyStatefulMacFactory() override = default;
 
   // Constructs a StatefulMac using the DummyStatefulMac, which creates
   // returns a MAC of the header concatenated with the plaintext.
diff --git a/cc/subtle/test_util.h b/cc/subtle/test_util.h
index 962bec5..9403b1a 100644
--- a/cc/subtle/test_util.h
+++ b/cc/subtle/test_util.h
@@ -138,7 +138,7 @@
     return ct_offset_;
   }
 
-  ~DummyStreamSegmentEncrypter() override {}
+  ~DummyStreamSegmentEncrypter() override = default;
 
   int get_generated_output_size() {
     return generated_output_size_;
@@ -227,7 +227,7 @@
     return ct_offset_;
   }
 
-  ~DummyStreamSegmentDecrypter() override {}
+  ~DummyStreamSegmentDecrypter() override = default;
 
   int get_generated_output_size() {
     return generated_output_size_;
diff --git a/cc/subtle/wycheproof_util.cc b/cc/subtle/wycheproof_util.cc
index a69a147..6289057 100644
--- a/cc/subtle/wycheproof_util.cc
+++ b/cc/subtle/wycheproof_util.cc
@@ -19,6 +19,7 @@
 #include <fstream>
 #include <iostream>
 #include <memory>
+#include <ostream>
 #include <string>
 
 #include "absl/status/status.h"
@@ -79,8 +80,7 @@
 std::unique_ptr<rapidjson::Document> WycheproofUtil::ReadTestVectors(
     const std::string &filename) {
   std::string test_vectors_path = crypto::tink::internal::RunfilesPath(
-      absl::StrCat(
-          "external/wycheproof/testvectors/", filename));
+      absl::StrCat("testvectors/", filename));
   std::ifstream input_stream;
   input_stream.open(test_vectors_path);
   rapidjson::IStreamWrapper input(input_stream);
diff --git a/cc/testvectors/BUILD.bazel b/cc/testvectors/BUILD.bazel
new file mode 100644
index 0000000..b602435
--- /dev/null
+++ b/cc/testvectors/BUILD.bazel
@@ -0,0 +1,200 @@
+"""Defines a set of genrules to copy test vectors from Wycheproof.
+
+This is needed to assist the transition to using Bazel Modules, in that Bazel
+Modules packages use a different folder naming for dependencies compared to
+WORKSPACE-based packages.
+"""
+
+package(default_visibility = ["//:__subpackages__"])
+
+licenses(["notice"])
+
+genrule(
+    name = "aes_cmac",
+    srcs = ["@wycheproof//testvectors:aes_cmac"],
+    outs = ["aes_cmac_test.json"],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "aes_gcm",
+    srcs = ["@wycheproof//testvectors:aes_gcm"],
+    outs = ["aes_gcm_test.json"],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "aes_gcm_siv",
+    srcs = ["@wycheproof//testvectors:aes_gcm_siv"],
+    outs = ["aes_gcm_siv_test.json"],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "aes_eax",
+    srcs = ["@wycheproof//testvectors:aes_eax"],
+    outs = ["aes_eax_test.json"],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "aes_siv_cmac",
+    srcs = ["@wycheproof//testvectors:aes_siv_cmac"],
+    outs = [
+        "aead_aes_siv_cmac_test.json",
+        "aes_siv_cmac_test.json",
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "chacha20_poly1305",
+    srcs = ["@wycheproof//testvectors:chacha20_poly1305"],
+    outs = [
+        "chacha20_poly1305_test.json",
+        "xchacha20_poly1305_test.json",
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "hmac",
+    srcs = ["@wycheproof//testvectors:hmac"],
+    outs = [
+        "hmac_sha1_test.json",
+        "hmac_sha224_test.json",
+        "hmac_sha256_test.json",
+        "hmac_sha384_test.json",
+        "hmac_sha3_224_test.json",
+        "hmac_sha3_256_test.json",
+        "hmac_sha3_384_test.json",
+        "hmac_sha3_512_test.json",
+        "hmac_sha512_test.json",
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "rsa_pss",
+    srcs = ["@wycheproof//testvectors:rsa_pss"],
+    outs = [
+        "rsa_pss_2048_sha1_mgf1_20_test.json",
+        "rsa_pss_2048_sha256_mgf1_0_test.json",
+        "rsa_pss_2048_sha256_mgf1_32_test.json",
+        "rsa_pss_3072_sha256_mgf1_32_test.json",
+        "rsa_pss_4096_sha256_mgf1_32_test.json",
+        "rsa_pss_4096_sha512_mgf1_32_test.json",
+        "rsa_pss_misc_test.json",
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "rsa_signature",
+    srcs = ["@wycheproof//testvectors:rsa_signature"],
+    outs = [
+        # Signature verification
+        "rsa_signature_2048_sha224_test.json",
+        "rsa_signature_2048_sha256_test.json",
+        "rsa_signature_2048_sha512_test.json",
+        "rsa_signature_3072_sha256_test.json",
+        "rsa_signature_3072_sha384_test.json",
+        "rsa_signature_3072_sha512_test.json",
+        "rsa_signature_4096_sha384_test.json",
+        "rsa_signature_4096_sha512_test.json",
+        "rsa_signature_2048_sha3_224_test.json",
+        "rsa_signature_2048_sha3_256_test.json",
+        "rsa_signature_2048_sha3_384_test.json",
+        "rsa_signature_2048_sha3_512_test.json",
+        "rsa_signature_3072_sha3_256_test.json",
+        "rsa_signature_3072_sha3_384_test.json",
+        "rsa_signature_3072_sha3_512_test.json",
+        "rsa_signature_test.json",
+        # Signature generation
+        "rsa_sig_gen_misc_test.json",
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "ecdsa_webcrypto",
+    srcs = ["@wycheproof//testvectors:ecdsa_webcrypto"],
+    outs = ["ecdsa_webcrypto_test.json"],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "ecdsa",
+    srcs = ["@wycheproof//testvectors:ecdsa"],
+    outs = [
+        "ecdsa_brainpoolP224r1_sha224_test.json",
+        "ecdsa_brainpoolP256r1_sha256_test.json",
+        "ecdsa_brainpoolP320r1_sha384_test.json",
+        "ecdsa_brainpoolP384r1_sha384_test.json",
+        "ecdsa_brainpoolP512r1_sha512_test.json",
+        "ecdsa_secp224r1_sha224_test.json",
+        "ecdsa_secp224r1_sha256_test.json",
+        "ecdsa_secp224r1_sha3_224_test.json",
+        "ecdsa_secp224r1_sha3_256_test.json",
+        "ecdsa_secp224r1_sha3_512_test.json",
+        "ecdsa_secp224r1_sha512_test.json",
+        "ecdsa_secp256k1_sha256_test.json",
+        "ecdsa_secp256k1_sha3_256_test.json",
+        "ecdsa_secp256k1_sha3_512_test.json",
+        "ecdsa_secp256k1_sha512_test.json",
+        "ecdsa_secp256r1_sha256_test.json",
+        "ecdsa_secp256r1_sha3_256_test.json",
+        "ecdsa_secp256r1_sha3_512_test.json",
+        "ecdsa_secp256r1_sha512_test.json",
+        "ecdsa_secp384r1_sha384_test.json",
+        "ecdsa_secp384r1_sha3_384_test.json",
+        "ecdsa_secp384r1_sha3_512_test.json",
+        "ecdsa_secp384r1_sha512_test.json",
+        "ecdsa_secp521r1_sha3_512_test.json",
+        "ecdsa_secp521r1_sha512_test.json",
+        "ecdsa_test.json",  # deprecated: use the files above
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "eddsa",
+    srcs = ["@wycheproof//testvectors:eddsa"],
+    outs = [
+        "ed448_test.json",
+        "eddsa_test.json",
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
+
+genrule(
+    name = "ecdh",
+    srcs = ["@wycheproof//testvectors:ecdh"],
+    outs = [
+        "ecdh_brainpoolP224r1_test.json",
+        "ecdh_brainpoolP256r1_test.json",
+        "ecdh_brainpoolP320r1_test.json",
+        "ecdh_brainpoolP384r1_test.json",
+        "ecdh_brainpoolP512r1_test.json",
+        "ecdh_secp224r1_test.json",
+        "ecdh_secp256k1_test.json",
+        "ecdh_secp256r1_test.json",
+        "ecdh_secp384r1_test.json",
+        "ecdh_secp521r1_test.json",
+        "ecdh_test.json",  # deprecated use the files above
+    ],
+    cmd = "cp $(SRCS) $(@D)/",
+    testonly = 1,
+)
diff --git a/cc/tink_cc_deps.bzl b/cc/tink_cc_deps.bzl
index 9a24734..d1de209 100644
--- a/cc/tink_cc_deps.bzl
+++ b/cc/tink_cc_deps.bzl
@@ -7,14 +7,14 @@
 
     # Basic rules we need to add to bazel.
     if not native.existing_rule("bazel_skylib"):
-        # Release from 2021-09-27.
+        # Release from 2022-09-01: https://github.com/bazelbuild/bazel-skylib/releases/tag/1.3.0
         http_archive(
             name = "bazel_skylib",
             urls = [
-                "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
-                "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+                "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
+                "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
             ],
-            sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d",
+            sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
         )
 
     # -------------------------------------------------------------------------
@@ -27,51 +27,36 @@
     #   * @com_google_protobuf//:java_toolchain
     # This statement defines the @com_google_protobuf repo.
     if not native.existing_rule("com_google_protobuf"):
-        # Release from 2021-06-08.
+        # Release X.21.9 from 2022-10-26.
         http_archive(
             name = "com_google_protobuf",
-            strip_prefix = "protobuf-3.19.3",
-            urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.19.3.zip"],
-            sha256 = "6b6bf5cd8d0cca442745c4c3c9f527c83ad6ef35a405f64db5215889ac779b42",
-        )
-
-    # -------------------------------------------------------------------------
-    # Remote Build Execution (RBE).
-    # -------------------------------------------------------------------------
-    if not native.existing_rule("bazel_toolchains"):
-        # Latest bazel_toolchains package on 2021-10-13.
-        http_archive(
-            name = "bazel_toolchains",
-            sha256 = "179ec02f809e86abf56356d8898c8bd74069f1bd7c56044050c2cd3d79d0e024",
-            strip_prefix = "bazel-toolchains-4.1.0",
-            urls = [
-                "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/4.1.0/bazel-toolchains-4.1.0.tar.gz",
-                "https://github.com/bazelbuild/bazel-toolchains/releases/download/4.1.0/bazel-toolchains-4.1.0.tar.gz",
-            ],
+            strip_prefix = "protobuf-21.9",
+            urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v21.9.zip"],
+            sha256 = "5babb8571f1cceafe0c18e13ddb3be556e87e12ceea3463d6b0d0064e6cc1ac3",
         )
 
     # -------------------------------------------------------------------------
     # Abseil.
     # -------------------------------------------------------------------------
     if not native.existing_rule("com_google_absl"):
-        # Commit from 2021-12-03.
+        # Release from 2023-05-04.
         http_archive(
             name = "com_google_absl",
-            strip_prefix = "abseil-cpp-9336be04a242237cd41a525bedfcf3be1bb55377",
-            url = "https://github.com/abseil/abseil-cpp/archive/9336be04a242237cd41a525bedfcf3be1bb55377.zip",
-            sha256 = "368be019fc8d69a566ac2cf7a75262d5ba8f6409e3ef3cdbcf0106bdeb32e91c",
+            strip_prefix = "abseil-cpp-20230125.3",
+            url = "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230125.3.zip",
+            sha256 = "51d676b6846440210da48899e4df618a357e6e44ecde7106f1e44ea16ae8adc7",
         )
 
     # -------------------------------------------------------------------------
     # BoringSSL.
     # -------------------------------------------------------------------------
     if not native.existing_rule("boringssl"):
-        # Commit from 2022-02-25.
+        # Commit from 2023-02-15.
         http_archive(
             name = "boringssl",
-            strip_prefix = "boringssl-88cdf7dd2dbce1ecb9057c183095103d83373abe",
-            url = "https://github.com/google/boringssl/archive/88cdf7dd2dbce1ecb9057c183095103d83373abe.zip",
-            sha256 = "24092815136f956069fcfa5172166ad4e025166ce6fe500420c9e3e3c4f3da38",
+            strip_prefix = "boringssl-5c22014ca513807ed03c657e8ede076164663979",
+            url = "https://github.com/google/boringssl/archive/5c22014ca513807ed03c657e8ede076164663979.zip",
+            sha256 = "863fc670c456f30923740c1639305132fdfb9d1b25ba385a67ae3862ef12a8af",
         )
 
     # -------------------------------------------------------------------------
diff --git a/cc/util/BUILD.bazel b/cc/util/BUILD.bazel
index f57b17d..f92575e 100644
--- a/cc/util/BUILD.bazel
+++ b/cc/util/BUILD.bazel
@@ -28,8 +28,10 @@
     name = "secret_data_internal",
     hdrs = ["secret_data_internal.h"],
     include_prefix = "tink/util",
+    visibility = ["//visibility:private"],
     deps = [
         "@boringssl//:crypto",
+        "@com_google_absl//absl/base:config",
         "@com_google_absl//absl/base:core_headers",
     ],
 )
@@ -55,7 +57,7 @@
         ":status",
         ":statusor",
         "@com_google_absl//absl/memory",
-        "@com_google_protobuf//:protobuf_lite",
+        "@com_google_protobuf//:protobuf",
     ],
 )
 
@@ -76,6 +78,7 @@
     include_prefix = "tink/util",
     visibility = ["//visibility:public"],
     deps = [
+        ":status",
         ":statusor",
         "//proto:common_cc_proto",
         "//proto:ecdsa_cc_proto",
@@ -88,39 +91,15 @@
 
 cc_library(
     name = "status",
-    srcs = ["status.cc"],
     hdrs = ["status.h"],
-    defines = select({
-        "//config:absl_status_enabled": ["TINK_USE_ABSL_STATUS"],
-        "//conditions:default": [],
-    }),
     include_prefix = "tink/util",
     visibility = ["//visibility:public"],
-    deps = [
-        "@com_google_absl//absl/base:core_headers",
-        "@com_google_absl//absl/status",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
-cc_test(
-    name = "status_test",
-    srcs = ["status_test.cc"],
-    deps = [
-        ":status",
-        "@com_google_absl//absl/status",
-        "@com_google_googletest//:gtest_main",
-    ],
+    deps = ["@com_google_absl//absl/status"],
 )
 
 cc_library(
     name = "statusor",
-    srcs = ["statusor.h"],
     hdrs = ["statusor.h"],
-    defines = select({
-        "//config:absl_statusor_enabled": ["TINK_USE_ABSL_STATUSOR"],
-        "//conditions:default": [],
-    }),
     include_prefix = "tink/util",
     visibility = ["//visibility:public"],
     deps = [
@@ -129,20 +108,6 @@
     ],
 )
 
-cc_test(
-    name = "statusor_test",
-    srcs = ["statusor_test.cc"],
-    deps = [
-        ":status",
-        ":statusor",
-        ":test_matchers",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/status",
-        "@com_google_absl//absl/status:statusor",
-        "@com_google_googletest//:gtest_main",
-    ],
-)
-
 cc_library(
     name = "validation",
     srcs = ["validation.cc"],
@@ -161,13 +126,16 @@
     srcs = ["file_input_stream.cc"],
     hdrs = ["file_input_stream.h"],
     include_prefix = "tink/util",
+    target_compatible_with = select({
+        "@platforms//os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
     visibility = ["//visibility:public"],
     deps = [
         ":errors",
         ":status",
         ":statusor",
         "//:input_stream",
-        "@com_google_absl//absl/memory",
         "@com_google_absl//absl/status",
     ],
 )
@@ -177,6 +145,10 @@
     srcs = ["file_output_stream.cc"],
     hdrs = ["file_output_stream.h"],
     include_prefix = "tink/util",
+    target_compatible_with = select({
+        "@platforms//os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
     visibility = ["//visibility:public"],
     deps = [
         ":errors",
@@ -193,6 +165,10 @@
     srcs = ["file_random_access_stream.cc"],
     hdrs = ["file_random_access_stream.h"],
     include_prefix = "tink/util",
+    target_compatible_with = select({
+        "@platforms//os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
     visibility = ["//visibility:public"],
     deps = [
         ":buffer",
@@ -312,7 +288,7 @@
     name = "protobuf_helper",
     hdrs = ["protobuf_helper.h"],
     include_prefix = "tink/util",
-    deps = ["@com_google_protobuf//:protobuf_lite"],
+    deps = ["@com_google_protobuf//:protobuf"],
 )
 
 cc_library(
@@ -446,10 +422,19 @@
 cc_test(
     name = "file_input_stream_test",
     srcs = ["file_input_stream_test.cc"],
+    target_compatible_with = select({
+        "@platforms//os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
     deps = [
         ":file_input_stream",
+        ":status",
+        ":test_matchers",
         ":test_util",
+        "//internal:test_file_util",
+        "//subtle:random",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -458,9 +443,15 @@
 cc_test(
     name = "file_output_stream_test",
     srcs = ["file_output_stream_test.cc"],
+    target_compatible_with = select({
+        "@platforms//os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
     deps = [
         ":file_output_stream",
+        ":test_matchers",
         ":test_util",
+        "//internal:test_file_util",
         "//subtle:random",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
@@ -471,11 +462,19 @@
 cc_test(
     name = "file_random_access_stream_test",
     srcs = ["file_random_access_stream_test.cc"],
+    target_compatible_with = select({
+        "@platforms//os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
     deps = [
         ":buffer",
         ":file_random_access_stream",
+        ":test_matchers",
         ":test_util",
+        "//internal:test_file_util",
+        "//subtle:random",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -487,8 +486,11 @@
     deps = [
         ":istream_input_stream",
         ":test_util",
+        "//internal:test_file_util",
         "//subtle:random",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -500,6 +502,7 @@
     deps = [
         ":ostream_output_stream",
         ":test_util",
+        "//internal:test_file_util",
         "//subtle:random",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
@@ -533,11 +536,19 @@
     name = "test_util_test",
     srcs = ["test_util_test.cc"],
     deps = [
+        ":buffer",
+        ":ostream_output_stream",
+        ":statusor",
         ":test_matchers",
         ":test_util",
+        "//:output_stream",
+        "//:random_access_stream",
+        "//internal:test_random_access_stream",
         "//proto:aes_gcm_cc_proto",
         "//proto:tink_cc_proto",
         "//subtle",
+        "//subtle:test_util",
+        "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
 )
diff --git a/cc/util/BUILD.gn b/cc/util/BUILD.gn
index 3e0cff4..5bedeca 100644
--- a/cc/util/BUILD.gn
+++ b/cc/util/BUILD.gn
@@ -23,6 +23,7 @@
   configs -= [ "//build/config:no_rtti" ]
   sources = [ "secret_data_internal.h" ]
   public_deps = [
+    "//third_party/abseil-cpp/absl/base:config",
     "//third_party/abseil-cpp/absl/base:core_headers",
     "//third_party/boringssl:crypto",
   ]
@@ -62,6 +63,7 @@
     "enums.h",
   ]
   public_deps = [
+    ":status",
     ":statusor",
     "//third_party/abseil-cpp/absl/status:status",
     "//third_party/abseil-cpp/absl/strings:strings",
@@ -77,15 +79,8 @@
 source_set("status") {
   configs += [ "//build/config:no_rtti" ]
   configs -= [ "//build/config:no_rtti" ]
-  sources = [
-    "status.cc",
-    "status.h",
-  ]
-  public_deps = [
-    "//third_party/abseil-cpp/absl/base:core_headers",
-    "//third_party/abseil-cpp/absl/status:status",
-    "//third_party/abseil-cpp/absl/strings:strings",
-  ]
+  sources = [ "status.h" ]
+  public_deps = [ "//third_party/abseil-cpp/absl/status:status" ]
   public_configs = [ "//third_party/tink:tink_config" ]
 }
 
@@ -93,10 +88,7 @@
 source_set("statusor") {
   configs += [ "//build/config:no_rtti" ]
   configs -= [ "//build/config:no_rtti" ]
-  sources = [
-    "statusor.h",
-    "statusor.h",
-  ]
+  sources = [ "statusor.h" ]
   public_deps = [
     ":status",
     "//third_party/abseil-cpp/absl/status:statusor",
diff --git a/cc/util/CMakeLists.txt b/cc/util/CMakeLists.txt
index 0836633..c906230 100644
--- a/cc/util/CMakeLists.txt
+++ b/cc/util/CMakeLists.txt
@@ -36,6 +36,7 @@
     enums.cc
     enums.h
   DEPS
+    tink::util::status
     tink::util::statusor
     absl::status
     absl::strings
@@ -48,12 +49,9 @@
 tink_cc_library(
   NAME status
   SRCS
-    status.cc
     status.h
   DEPS
-    absl::core_headers
     absl::status
-    absl::strings
   PUBLIC
 )
 
@@ -61,7 +59,6 @@
   NAME statusor
   SRCS
     statusor.h
-    statusor.h
   DEPS
     tink::util::status
     absl::statusor
@@ -89,9 +86,10 @@
     tink::util::errors
     tink::util::status
     tink::util::statusor
-    absl::memory
     absl::status
     tink::core::input_stream
+  TAGS
+    exclude_if_windows
 )
 
 tink_cc_library(
@@ -106,6 +104,8 @@
     absl::memory
     absl::status
     tink::core::output_stream
+  TAGS
+    exclude_if_windows
 )
 
 tink_cc_library(
@@ -121,6 +121,8 @@
     absl::memory
     absl::status
     tink::core::random_access_stream
+  TAGS
+    exclude_if_windows
 )
 
 tink_cc_library(
@@ -203,6 +205,7 @@
     tink::proto::hmac_cc_proto
     tink::proto::tink_cc_proto
     tink::proto::xchacha20_poly1305_cc_proto
+  TESTONLY
 )
 
 tink_cc_library(
@@ -214,6 +217,7 @@
     tink::util::statusor
     gmock
     absl::status
+  TESTONLY
 )
 
 tink_cc_library(
@@ -233,6 +237,7 @@
     absl::memory
     tink::core::keyset_handle
     tink::proto::tink_cc_proto
+  TESTONLY
 )
 
 tink_cc_library(
@@ -303,10 +308,17 @@
     file_input_stream_test.cc
   DEPS
     tink::util::file_input_stream
+    tink::util::status
+    tink::util::test_matchers
     tink::util::test_util
     gmock
     absl::memory
+    absl::status
     absl::strings
+    tink::internal::test_file_util
+    tink::subtle::random
+  TAGS
+    exclude_if_windows
 )
 
 tink_cc_test(
@@ -315,11 +327,15 @@
     file_output_stream_test.cc
   DEPS
     tink::util::file_output_stream
+    tink::util::test_matchers
     tink::util::test_util
     gmock
     absl::memory
     absl::strings
+    tink::internal::test_file_util
     tink::subtle::random
+  TAGS
+    exclude_if_windows
 )
 
 tink_cc_test(
@@ -329,10 +345,16 @@
   DEPS
     tink::util::buffer
     tink::util::file_random_access_stream
+    tink::util::test_matchers
     tink::util::test_util
     gmock
     absl::memory
+    absl::status
     absl::strings
+    tink::internal::test_file_util
+    tink::subtle::random
+  TAGS
+    exclude_if_windows
 )
 
 tink_cc_test(
@@ -344,7 +366,10 @@
     tink::util::test_util
     gmock
     absl::memory
+    absl::status
+    absl::statusor
     absl::strings
+    tink::internal::test_file_util
     tink::subtle::random
 )
 
@@ -358,6 +383,7 @@
     gmock
     absl::memory
     absl::strings
+    tink::internal::test_file_util
     tink::subtle::random
 )
 
@@ -366,10 +392,18 @@
   SRCS
     test_util_test.cc
   DEPS
+    tink::util::buffer
+    tink::util::ostream_output_stream
+    tink::util::statusor
     tink::util::test_matchers
     tink::util::test_util
     gmock
+    absl::strings
+    tink::core::output_stream
+    tink::core::random_access_stream
+    tink::internal::test_random_access_stream
     tink::subtle::subtle
+    tink::subtle::test_util
     tink::proto::aes_gcm_cc_proto
     tink::proto::tink_cc_proto
 )
@@ -404,8 +438,8 @@
   SRCS
     secret_data_internal.h
   DEPS
-    absl::strings
-    absl::base
+    absl::config
+    absl::core_headers
     crypto
 )
 
@@ -471,6 +505,7 @@
     tink::core::kms_client
     tink::core::kms_clients
     tink::aead::aead_key_templates
+  TESTONLY
 )
 
 tink_cc_test(
@@ -489,27 +524,3 @@
     tink::proto::kms_aead_cc_proto
     tink::proto::kms_envelope_cc_proto
 )
-
-tink_cc_test(
-  NAME status_test
-  SRCS
-    status_test.cc
-  DEPS
-    tink::util::status
-    gmock
-    absl::status
-)
-
-tink_cc_test(
-  NAME statusor_test
-  SRCS
-    statusor_test.cc
-  DEPS
-    tink::util::status
-    tink::util::statusor
-    tink::util::test_matchers
-    gmock
-    absl::memory
-    absl::status
-    absl::statusor
-)
diff --git a/cc/util/buffer.cc b/cc/util/buffer.cc
index f98adac..2fa67e5 100644
--- a/cc/util/buffer.cc
+++ b/cc/util/buffer.cc
@@ -16,6 +16,8 @@
 
 #include "tink/util/buffer.h"
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
 #include "tink/util/status.h"
@@ -54,7 +56,7 @@
     return OkStatus();
   }
 
-  ~OwningBuffer() override {}
+  ~OwningBuffer() override = default;
 
  private:
   std::unique_ptr<char[]> owned_mem_block_;
@@ -91,7 +93,7 @@
     return OkStatus();
   }
 
-  ~NonOwningBuffer() override {}
+  ~NonOwningBuffer() override = default;
 
  private:
   char* const mem_block_;
diff --git a/cc/util/buffer.h b/cc/util/buffer.h
index f46de54..3eaa558 100644
--- a/cc/util/buffer.h
+++ b/cc/util/buffer.h
@@ -17,6 +17,8 @@
 #ifndef TINK_UTIL_BUFFER_H_
 #define TINK_UTIL_BUFFER_H_
 
+#include <memory>
+
 #include "absl/memory/memory.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -58,7 +60,7 @@
   // Returns OK iff 0 <= new_size <= allocated_size();
   virtual util::Status set_size(int new_size) = 0;
 
-  virtual ~Buffer() {}
+  virtual ~Buffer() = default;
 };
 
 }  // namespace util
diff --git a/cc/util/enums.cc b/cc/util/enums.cc
index 201ca1b..5100162 100644
--- a/cc/util/enums.cc
+++ b/cc/util/enums.cc
@@ -18,6 +18,7 @@
 
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
+#include "tink/util/status.h"
 #include "proto/common.pb.h"
 #include "proto/ecdsa.pb.h"
 #include "proto/tink.pb.h"
diff --git a/cc/util/errors.h b/cc/util/errors.h
index cbb0ebe..5086ce4 100644
--- a/cc/util/errors.h
+++ b/cc/util/errors.h
@@ -23,17 +23,6 @@
 namespace crypto {
 namespace tink {
 
-#ifndef TINK_USE_ABSL_STATUS
-// Constructs a Status with formatted error message.
-template <typename... Args>
-ABSL_DEPRECATED("Prefer using absl::StatusCode as a first argument.")
-util::Status ToStatusF(util::error::Code code,
-                       const absl::FormatSpec<Args...>& format,
-                       const Args&... args) {
-  return util::Status(code, absl::StrFormat(format, args...));
-}
-#endif
-
 // Constructs a Status with formatted error message using absl::StatusCode.
 template <typename... Args>
 util::Status ToStatusF(absl::StatusCode code,
diff --git a/cc/util/errors_test.cc b/cc/util/errors_test.cc
index 73e74c2..cec75e6 100644
--- a/cc/util/errors_test.cc
+++ b/cc/util/errors_test.cc
@@ -24,49 +24,13 @@
 namespace tink {
 namespace {
 
-#ifndef TINK_USE_ABSL_STATUS
-TEST(ErrorsTest, ToStatusFTest) {
-  const char* const msg1 = "test message 1";
-  const char* const msg2 = "test message %s 2 %d";
-  crypto::tink::util::Status status;
-
-  status = util::Status(crypto::tink::util::error::OK, msg1);
-  EXPECT_TRUE(status.ok());
-  // if status is OK, error message is ignored
-  EXPECT_EQ("", status.message());
-  EXPECT_EQ(crypto::tink::util::error::OK, status.error_code());
-
-  const char* expected_msg2 = "test message asdf 2 42";
-  status = ToStatusF(crypto::tink::util::error::UNKNOWN, msg2, "asdf", 42);
-  EXPECT_FALSE(status.ok());
-  EXPECT_EQ(expected_msg2, status.message());
-  EXPECT_EQ(crypto::tink::util::error::UNKNOWN, status.error_code());
-}
-
-TEST(ErrorsTest, ToAbslStatus) {
-  crypto::tink::util::Status tink_status(util::error::INVALID_ARGUMENT,
-                                         "error");
-  ::absl::Status g3_status(tink_status);
-  EXPECT_FALSE(g3_status.ok());
-  EXPECT_EQ(g3_status.message(), "error");
-
-  EXPECT_EQ(::absl::Status(crypto::tink::util::OkStatus()), ::absl::OkStatus());
-}
-#endif
-
 TEST(ErrorsTest, ToStatusFAbslStatusCodeTest) {
   const char* const msg = "test message %s 2 %d";
   const char* expected_msg = "test message asdf 2 42";
-  crypto::tink::util::Status status =
-      ToStatusF(absl::StatusCode::kUnknown, msg, "asdf", 42);
+  util::Status status = ToStatusF(absl::StatusCode::kUnknown, msg, "asdf", 42);
   EXPECT_FALSE(status.ok());
   EXPECT_EQ(expected_msg, status.message());
   EXPECT_EQ(absl::StatusCode::kUnknown, status.code());
-
-  #ifndef TINK_USE_ABSL_STATUS
-  EXPECT_EQ(expected_msg, status.error_message());
-  EXPECT_EQ(crypto::tink::util::error::UNKNOWN, status.error_code());
-  #endif
 }
 
 }  // namespace
diff --git a/cc/util/fake_kms_client.cc b/cc/util/fake_kms_client.cc
index cb5f9ed..1f7922f 100644
--- a/cc/util/fake_kms_client.cc
+++ b/cc/util/fake_kms_client.cc
@@ -17,6 +17,8 @@
 
 #include <fstream>
 #include <iostream>
+#include <memory>
+#include <ostream>
 #include <sstream>
 #include <string>
 #include <utility>
diff --git a/cc/util/fake_kms_client_test.cc b/cc/util/fake_kms_client_test.cc
index b1039c7..4436915 100644
--- a/cc/util/fake_kms_client_test.cc
+++ b/cc/util/fake_kms_client_test.cc
@@ -31,7 +31,6 @@
 #include "proto/kms_aead.pb.h"
 #include "proto/kms_envelope.pb.h"
 
-using ::crypto::tink::test::IsOk;
 using google::crypto::tink::KeyTemplate;
 using google::crypto::tink::KmsAeadKeyFormat;
 using google::crypto::tink::KmsEnvelopeAeadKeyFormat;
diff --git a/cc/util/file_input_stream.cc b/cc/util/file_input_stream.cc
index 3903f20..695651a 100644
--- a/cc/util/file_input_stream.cc
+++ b/cc/util/file_input_stream.cc
@@ -19,9 +19,7 @@
 #include <unistd.h>
 #include <algorithm>
 
-#include "absl/memory/memory.h"
 #include "absl/status/status.h"
-#include "tink/input_stream.h"
 #include "tink/util/errors.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -29,9 +27,10 @@
 namespace crypto {
 namespace tink {
 namespace util {
-
 namespace {
 
+constexpr int kDefaultBufferSize = 128 * 1024;
+
 // Attempts to close file descriptor fd, while ignoring EINTR.
 // (code borrowed from ZeroCopy-streams)
 int close_ignoring_eintr(int fd) {
@@ -42,7 +41,6 @@
   return result;
 }
 
-
 // Attempts to read 'count' bytes of data data from file descriptor fd
 // to 'buf' while ignoring EINTR.
 int read_ignoring_eintr(int fd, void *buf, size_t count) {
@@ -55,29 +53,27 @@
 
 }  // anonymous namespace
 
-FileInputStream::FileInputStream(int file_descriptor, int buffer_size) :
-    buffer_size_(buffer_size > 0 ? buffer_size : 128 * 1024) {  // 128 KB
-  fd_ = file_descriptor;
-  count_in_buffer_ = 0;
-  count_backedup_ = 0;
-  position_ = 0;
-  buffer_ = absl::make_unique<uint8_t[]>(buffer_size_);
-  buffer_offset_ = 0;
-  status_ = util::OkStatus();
-}
+FileInputStream::FileInputStream(int file_descriptor, int buffer_size)
+    : status_(util::OkStatus()),
+      fd_(file_descriptor),
+      buffer_(buffer_size > 0 ? buffer_size : kDefaultBufferSize) {}
 
-crypto::tink::util::StatusOr<int> FileInputStream::Next(const void** data) {
+util::StatusOr<int> FileInputStream::Next(const void** data) {
+  if (data == nullptr) {
+    return util::Status(absl::StatusCode::kInvalidArgument,
+                        "Data pointer must not be nullptr");
+  }
   if (!status_.ok()) return status_;
   if (count_backedup_ > 0) {  // Return the backed-up bytes.
     buffer_offset_ = buffer_offset_ + (count_in_buffer_ - count_backedup_);
     count_in_buffer_ = count_backedup_;
     count_backedup_ = 0;
-    *data = buffer_.get() + buffer_offset_;
+    *data = buffer_.data() + buffer_offset_;
     position_ = position_ + count_in_buffer_;
     return count_in_buffer_;
   }
   // Read new bytes to buffer_.
-  int read_result = read_ignoring_eintr(fd_, buffer_.get(), buffer_size_);
+  int read_result = read_ignoring_eintr(fd_, buffer_.data(), buffer_.size());
   if (read_result <= 0) {  // EOF or an I/O error.
     if (read_result == 0) {
       status_ = Status(absl::StatusCode::kOutOfRange, "EOF");
@@ -91,7 +87,7 @@
   count_backedup_ = 0;
   count_in_buffer_ = read_result;
   position_ = position_ + count_in_buffer_;
-  *data = buffer_.get();
+  *data = buffer_.data();
   return count_in_buffer_;
 }
 
@@ -102,13 +98,9 @@
   position_ = position_ - actual_count;
 }
 
-FileInputStream::~FileInputStream() {
-  close_ignoring_eintr(fd_);
-}
+FileInputStream::~FileInputStream() { close_ignoring_eintr(fd_); }
 
-int64_t FileInputStream::Position() const {
-  return position_;
-}
+int64_t FileInputStream::Position() const { return position_; }
 
 }  // namespace util
 }  // namespace tink
diff --git a/cc/util/file_input_stream.h b/cc/util/file_input_stream.h
index 5c816b0..f01c362 100644
--- a/cc/util/file_input_stream.h
+++ b/cc/util/file_input_stream.h
@@ -17,7 +17,9 @@
 #ifndef TINK_UTIL_FILE_INPUT_STREAM_H_
 #define TINK_UTIL_FILE_INPUT_STREAM_H_
 
+#include <cstdint>
 #include <memory>
+#include <vector>
 
 #include "tink/input_stream.h"
 #include "tink/util/status.h"
@@ -28,11 +30,13 @@
 namespace util {
 
 // An InputStream that reads from a file descriptor.
+//
+// NOTE: This class in not available when building on Windows.
 class FileInputStream : public crypto::tink::InputStream {
  public:
   // Constructs an InputStream that will read from the file specified
-  // via 'file_descriptor', using a buffer of the specified size, if any
-  // (if no legal 'buffer_size' is given, a reasonable default will be used).
+  // via `file_descriptor`, using a buffer of the specified size, if any
+  // (if no legal `buffer_size` is given, a reasonable default will be used).
   // Takes the ownership of the file, and will close it upon destruction.
   explicit FileInputStream(int file_descriptor, int buffer_size = -1);
 
@@ -45,16 +49,20 @@
   int64_t Position() const override;
 
  private:
-  util::Status status_;
+  // Status of the stream.
+  util::Status status_ = util::OkStatus();
   int fd_;
-  std::unique_ptr<uint8_t[]> buffer_;
-  const int buffer_size_;
-  int64_t position_;     // current position in the file (from the beginning)
+  std::vector<uint8_t> buffer_;
 
+  // Current position in the stream (from the beginning).
+  int64_t position_ = 0;
   // Counters that describe the state of the data in buffer_.
-  int count_in_buffer_;  // # of bytes available in buffer_
-  int count_backedup_;   // # of bytes available in buffer_ that were backed up
-  int buffer_offset_;    // offset at which the returned bytes start in buffer_
+  // # of bytes available in buffer_.
+  int count_in_buffer_ = 0;
+  // # of bytes available in buffer_ that were backed up.
+  int count_backedup_ = 0;
+  // offset at which the returned bytes start in buffer_.
+  int buffer_offset_ = 0;
 };
 
 }  // namespace util
diff --git a/cc/util/file_input_stream_test.cc b/cc/util/file_input_stream_test.cc
index 14ecc1a..2f7a172 100644
--- a/cc/util/file_input_stream_test.cc
+++ b/cc/util/file_input_stream_test.cc
@@ -13,27 +13,56 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
-
 #include "tink/util/file_input_stream.h"
 
+#include <fcntl.h>
+
 #include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iostream>
+#include <ostream>
 #include <string>
 
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
+#include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "tink/internal/test_file_util.h"
+#include "tink/subtle/random.h"
+#include "tink/util/status.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
-// Reads the specified 'input_stream' until no more bytes can be read,
-// and puts the read bytes into 'contents'.
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::IsOkAndHolds;
+using ::crypto::tink::test::StatusIs;
+
+constexpr int kDefaultTestStreamSize = 100 * 1024;  // 100 KB.
+
+// Opens test file `filename` and returns a file descriptor to it.
+util::StatusOr<int> OpenTestFileToRead(absl::string_view filename) {
+  std::string full_filename = absl::StrCat(test::TmpDir(), "/", filename);
+  int fd = open(full_filename.c_str(), O_RDONLY);
+  if (fd == -1) {
+    return util::Status(absl::StatusCode::kInternal,
+                        absl::StrCat("Cannot open file ", full_filename,
+                                     " error: ", std::strerror(errno)));
+  }
+  return fd;
+}
+
+// Reads the specified `input_stream` until no more bytes can be read,
+// and puts the read bytes into `contents`.
 // Returns the status of the last input_stream->Next()-operation.
-util::Status ReadTillEnd(util::FileInputStream* input_stream,
-                         std::string* contents) {
+util::Status ReadAll(util::FileInputStream* input_stream,
+                     std::string* contents) {
   contents->clear();
   const void* buffer;
   auto next_result = input_stream->Next(&buffer);
@@ -44,128 +73,241 @@
   return next_result.status();
 }
 
-class FileInputStreamTest : public ::testing::Test {
-};
+using FileInputStreamTestDefaultBufferSize = testing::TestWithParam<int>;
 
-TEST_F(FileInputStreamTest, testReadingStreams) {
-  for (auto stream_size : {0, 10, 100, 1000, 10000, 100000, 1000000}) {
-    SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
-    EXPECT_EQ(stream_size, file_contents.size());
-    auto input_stream = absl::make_unique<util::FileInputStream>(input_fd);
-    std::string stream_contents;
-    auto status = ReadTillEnd(input_stream.get(), &stream_contents);
-    EXPECT_EQ(absl::StatusCode::kOutOfRange, status.code());
-    EXPECT_EQ("EOF", status.message());
-    EXPECT_EQ(file_contents, stream_contents);
-  }
-}
-
-TEST_F(FileInputStreamTest, testCustomBufferSizes) {
-  int stream_size = 100000;
-  for (auto buffer_size : {1, 10, 100, 1000, 10000}) {
-    SCOPED_TRACE(absl::StrCat("buffer_size = ", buffer_size));
-    std::string file_contents;
-    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
-    EXPECT_EQ(stream_size, file_contents.size());
-    auto input_stream =
-        absl::make_unique<util::FileInputStream>(input_fd, buffer_size);
-    const void* buffer;
-    auto next_result = input_stream->Next(&buffer);
-    EXPECT_TRUE(next_result.ok()) << next_result.status();
-    EXPECT_EQ(buffer_size, next_result.value());
-    EXPECT_EQ(file_contents.substr(0, buffer_size),
-              std::string(static_cast<const char*>(buffer), buffer_size));
-  }
-}
-
-TEST_F(FileInputStreamTest, testBackupAndPosition) {
-  int stream_size = 100000;
-  int buffer_size = 1234;
-  const void* buffer;
-  std::string file_contents;
-  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
-  int input_fd =
-      test::GetTestFileDescriptor(filename, stream_size, &file_contents);
+TEST_P(FileInputStreamTestDefaultBufferSize, ReadAllfFromInputStreamSucceeds) {
+  int stream_size = GetParam();
+  SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
+  std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
+  std::string filename = absl::StrCat(
+      stream_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
+  util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+  ASSERT_THAT(input_fd.status(), IsOk());
   EXPECT_EQ(stream_size, file_contents.size());
+  auto input_stream = absl::make_unique<util::FileInputStream>(*input_fd);
+  std::string stream_contents;
+  auto status = ReadAll(input_stream.get(), &stream_contents);
+  EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange));
+  EXPECT_EQ(status.message(), "EOF");
+  EXPECT_EQ(file_contents, stream_contents);
+}
 
-  // Prepare the stream and do the first call to Next().
+INSTANTIATE_TEST_SUITE_P(FileInputStreamTest,
+                         FileInputStreamTestDefaultBufferSize,
+                         testing::ValuesIn({0, 10, 100, 1000, 10000, 100000,
+                                            1000000}));
+
+using FileInputStreamTestCustomBufferSizes = testing::TestWithParam<int>;
+
+TEST_P(FileInputStreamTestCustomBufferSizes,
+       ReadAllWithCustomBufferSizeSucceeds) {
+  int buffer_size = GetParam();
+  SCOPED_TRACE(absl::StrCat("buffer_size = ", buffer_size));
+  std::string file_contents =
+      subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
+  std::string filename = absl::StrCat(
+      buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
+  util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+  ASSERT_THAT(input_fd.status(), IsOk());
+  EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
   auto input_stream =
-      absl::make_unique<util::FileInputStream>(input_fd, buffer_size);
-  EXPECT_EQ(0, input_stream->Position());
+      absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
+  const void* buffer;
   auto next_result = input_stream->Next(&buffer);
-  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  ASSERT_THAT(next_result, IsOk());
   EXPECT_EQ(buffer_size, next_result.value());
-  EXPECT_EQ(buffer_size, input_stream->Position());
   EXPECT_EQ(file_contents.substr(0, buffer_size),
             std::string(static_cast<const char*>(buffer), buffer_size));
+}
 
-  // BackUp several times, but in total fewer bytes than returned by Next().
-  int total_backup_size = 0;
-  for (auto backup_size : {0, 1, 5, 0, 10, 100, -42, 400, 20, -100}) {
-    SCOPED_TRACE(absl::StrCat("backup_size = ", backup_size));
-    input_stream->BackUp(backup_size);
-    total_backup_size += std::max(0, backup_size);
-    EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
-  }
-  // Call Next(), it should return exactly the backed up bytes.
-  next_result = input_stream->Next(&buffer);
-  EXPECT_TRUE(next_result.ok()) << next_result.status();
-  EXPECT_EQ(total_backup_size, next_result.value());
-  EXPECT_EQ(buffer_size, input_stream->Position());
-  EXPECT_EQ(
-      file_contents.substr(buffer_size - total_backup_size, total_backup_size),
-      std::string(static_cast<const char*>(buffer), total_backup_size));
+INSTANTIATE_TEST_SUITE_P(FileInputStreamTest,
+                         FileInputStreamTestCustomBufferSizes,
+                         testing::ValuesIn({1, 10, 100, 1000, 10000}));
 
-  // BackUp() some bytes, again fewer than returned by Next().
-  total_backup_size = 0;
-  for (auto backup_size : {0, 72, -94, 37, 82}) {
-    SCOPED_TRACE(absl::StrCat("backup_size = ", backup_size));
-    input_stream->BackUp(backup_size);
-    total_backup_size += std::max(0, backup_size);
-    EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
-  }
+TEST(FileInputStreamTest, NextFailsIfFdIsInvalid) {
+  int buffer_size = 4 * 1024;
+  auto input_stream = absl::make_unique<util::FileInputStream>(-1, buffer_size);
+  const void* buffer = nullptr;
+  EXPECT_THAT(input_stream->Next(&buffer).status(),
+              StatusIs(absl::StatusCode::kInternal));
+}
 
-  // Call Next(), it should return exactly the backed up bytes.
-  next_result = input_stream->Next(&buffer);
-  EXPECT_TRUE(next_result.ok()) << next_result.status();
-  EXPECT_EQ(total_backup_size, next_result.value());
-  EXPECT_EQ(buffer_size, input_stream->Position());
-  EXPECT_EQ(
-      file_contents.substr(buffer_size - total_backup_size, total_backup_size),
-      std::string(static_cast<const char*>(buffer), total_backup_size));
+TEST(FileInputStreamTest, NextFailsIfDataIsNull) {
+  int buffer_size = 4 * 1024;
+  std::string file_contents =
+      subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
+  std::string filename = absl::StrCat(
+      buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
+  util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+  ASSERT_THAT(input_fd.status(), IsOk());
+  EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
+  auto input_stream =
+      absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
 
-  // Call Next() again, it should return the second block.
-  next_result = input_stream->Next(&buffer);
-  EXPECT_TRUE(next_result.ok()) << next_result.status();
-  EXPECT_EQ(buffer_size, next_result.value());
-  EXPECT_EQ(2 * buffer_size, input_stream->Position());
-  EXPECT_EQ(file_contents.substr(buffer_size, buffer_size),
-            std::string(static_cast<const char*>(buffer), buffer_size));
+  EXPECT_THAT(input_stream->Next(nullptr).status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
 
-  // BackUp a few times, with total over the returned buffer_size.
-  total_backup_size = 0;
-  for (auto backup_size :
-           {0, 72, -100, buffer_size/2, 200, -25, buffer_size, 42}) {
-    SCOPED_TRACE(absl::StrCat("backup_size = ", backup_size));
-    input_stream->BackUp(backup_size);
-    total_backup_size = std::min(buffer_size,
-                                 total_backup_size + std::max(0, backup_size));
-    EXPECT_EQ(2 * buffer_size - total_backup_size, input_stream->Position());
-  }
+TEST(FileInputStreamTest, NextReadsExactlyOneBlockOfData) {
+  int buffer_size = 4 * 1024;
+  std::string file_contents =
+      subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
+  std::string filename = absl::StrCat(
+      buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
+  util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+  ASSERT_THAT(input_fd.status(), IsOk());
+  EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
+  auto input_stream =
+      absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
 
-  // Call Next() again, it should return the second block.
-  next_result = input_stream->Next(&buffer);
-  EXPECT_TRUE(next_result.ok()) << next_result.status();
-  EXPECT_EQ(buffer_size, next_result.value());
-  EXPECT_EQ(2 * buffer_size, input_stream->Position());
-  EXPECT_EQ(file_contents.substr(buffer_size, buffer_size),
-            std::string(static_cast<const char*>(buffer), buffer_size));
+  auto expected_file_content_block =
+      absl::string_view(file_contents).substr(0, buffer_size);
+  const void* buffer = nullptr;
+  util::StatusOr<int> next_result = input_stream->Next(&buffer);
+  ASSERT_THAT(next_result, IsOkAndHolds(buffer_size));
+  // Check that we advanced of buffer_size bytes.
+  EXPECT_EQ(input_stream->Position(), buffer_size);
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
+            expected_file_content_block);
+}
+
+TEST(FileInputStreamTest, BackupForNegativeOrZeroBytesIsANoop) {
+  int buffer_size = 4 * 1024;
+  std::string file_contents =
+      subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
+  std::string filename = absl::StrCat(
+      buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
+  util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+  ASSERT_THAT(input_fd.status(), IsOk());
+  EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
+  auto input_stream =
+      absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
+  EXPECT_EQ(input_stream->Position(), 0);
+
+  auto expected_file_content_block =
+      absl::string_view(file_contents).substr(0, buffer_size);
+  const void* buffer = nullptr;
+  ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
+  // Check that we advanced of buffer_size bytes.
+  EXPECT_EQ(input_stream->Position(), buffer_size);
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
+            expected_file_content_block);
+
+  // The calls below are noops.
+  input_stream->BackUp(0);
+  EXPECT_EQ(input_stream->Position(), buffer_size);
+  input_stream->BackUp(-12);
+  EXPECT_EQ(input_stream->Position(), buffer_size);
+
+  // A subsequent call to `Next` returns the 2nd block.
+  auto expected_2nd_file_content_block =
+      absl::string_view(file_contents).substr(buffer_size, buffer_size);
+  ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
+  // Check that we advanced of buffer_size bytes.
+  EXPECT_EQ(input_stream->Position(), 2 * buffer_size);
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
+            expected_2nd_file_content_block);
+}
+
+TEST(FileInputStreamTest, BackupForLessThanOneBlockOfData) {
+  int buffer_size = 4 * 1024;
+  std::string file_contents =
+      subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
+  std::string filename = absl::StrCat(
+      buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
+  util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+  ASSERT_THAT(input_fd.status(), IsOk());
+  EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
+  auto input_stream =
+      absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
+
+  auto expected_file_content_block =
+      absl::string_view(file_contents).substr(0, buffer_size);
+  const void* buffer = nullptr;
+  ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
+  // Check that we advanced of buffer_size bytes.
+  EXPECT_EQ(input_stream->Position(), buffer_size);
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
+            expected_file_content_block);
+
+  int64_t position_after_next = input_stream->Position();
+  // Number of bytes that were backed up.
+  int num_backed_up_bytes = 0;
+  input_stream->BackUp(0);  // This should be a noop.
+  EXPECT_EQ(input_stream->Position(), position_after_next);
+  input_stream->BackUp(-12);  // This should be a noop.
+  EXPECT_EQ(input_stream->Position(), position_after_next);
+  input_stream->BackUp(10);
+  num_backed_up_bytes += 10;
+  EXPECT_EQ(input_stream->Position(),
+            position_after_next - num_backed_up_bytes);
+  input_stream->BackUp(5);
+  num_backed_up_bytes += 5;
+  EXPECT_EQ(input_stream->Position(),
+            position_after_next - num_backed_up_bytes);
+
+  // A subsequent call to Next should return only the backed up bytes.
+  auto expected_backed_up_bytes =
+      absl::string_view(file_contents)
+          .substr(buffer_size - num_backed_up_bytes, num_backed_up_bytes);
+  ASSERT_THAT(input_stream->Next(&buffer),
+              IsOkAndHolds(expected_backed_up_bytes.size()));
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer),
+                              expected_backed_up_bytes.size()),
+            expected_backed_up_bytes);
+}
+
+// When backing up of a number of bytes larger than the size of a block, backup
+// of one block.
+TEST(FileInputStreamTest, BackupAtMostOfOneBlock) {
+  int buffer_size = 4 * 1024;
+  std::string file_contents =
+      subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
+  std::string filename = absl::StrCat(
+      buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
+  util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+  ASSERT_THAT(input_fd.status(), IsOk());
+  EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
+  auto input_stream =
+      absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
+
+  // Read two blocks of size buffer_size, then back up of more than buffer_size
+  // bytes.
+  auto expected_1st_file_content_block =
+      absl::string_view(file_contents).substr(0, buffer_size);
+  const void* buffer = nullptr;
+  ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
+  // Check that we advanced of buffer_size bytes.
+  EXPECT_EQ(input_stream->Position(), buffer_size);
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
+            expected_1st_file_content_block);
+
+  auto expected_2nd_file_content_block =
+      absl::string_view(file_contents).substr(buffer_size, buffer_size);
+  ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
+  // Check that we advanced of buffer_size bytes.
+  EXPECT_EQ(input_stream->Position(), 2 * buffer_size);
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
+            expected_2nd_file_content_block);
+
+  int64_t position_after_next = input_stream->Position();
+  EXPECT_EQ(input_stream->Position(), position_after_next);
+  input_stream->BackUp(10);
+  EXPECT_EQ(input_stream->Position(), position_after_next - 10);
+  input_stream->BackUp(buffer_size);
+  EXPECT_EQ(input_stream->Position(), position_after_next - buffer_size);
+
+  // This call to Next is expected to read the second block again.
+  ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
+  EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
+            expected_2nd_file_content_block);
 }
 
 }  // namespace
diff --git a/cc/util/file_output_stream.h b/cc/util/file_output_stream.h
index 43b638a..2a6dd0f 100644
--- a/cc/util/file_output_stream.h
+++ b/cc/util/file_output_stream.h
@@ -28,6 +28,8 @@
 namespace util {
 
 // An OutputStream that writes to a file descriptor.
+//
+// NOTE: This class in not available when building on Windows.
 class FileOutputStream : public crypto::tink::OutputStream {
  public:
   // Constructs an OutputStream that will write to the file specified
diff --git a/cc/util/file_output_stream_test.cc b/cc/util/file_output_stream_test.cc
index dbdf7f2..6e46c7b 100644
--- a/cc/util/file_output_stream_test.cc
+++ b/cc/util/file_output_stream_test.cc
@@ -16,20 +16,42 @@
 
 #include "tink/util/file_output_stream.h"
 
+#include <fcntl.h>
+
 #include <algorithm>
+#include <cstring>
+#include <iostream>
+#include <ostream>
 #include <string>
 
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "tink/internal/test_file_util.h"
 #include "tink/subtle/random.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
+using ::crypto::tink::test::IsOk;
+
+// Opens test file `filename` and returns a file descriptor to it.
+util::StatusOr<int> OpenTestFileToWrite(absl::string_view filename) {
+  std::string full_filename = absl::StrCat(test::TmpDir(), "/", filename);
+  mode_t mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH;
+  int fd = open(full_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
+  if (fd == -1) {
+    return util::Status(absl::StatusCode::kInternal,
+                        absl::StrCat("Cannot open file ", full_filename,
+                                     " error: ", std::strerror(errno)));
+  }
+  return fd;
+}
+
 // Writes 'contents' the specified 'output_stream', and closes the stream.
 // Returns the status of output_stream->Close()-operation, or a non-OK status
 // of a prior output_stream->Next()-operation, if any.
@@ -62,9 +84,12 @@
   for (auto stream_size : {0, 10, 100, 1000, 10000, 100000, 1000000}) {
     SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
     std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
-    std::string filename = absl::StrCat(stream_size, "_writing_test.bin");
-    int output_fd = test::GetTestFileDescriptor(filename);
-    auto output_stream = absl::make_unique<util::FileOutputStream>(output_fd);
+    std::string filename = absl::StrCat(
+        stream_size, internal::GetTestFileNamePrefix(), "_test.bin");
+    ASSERT_THAT(internal::CreateTestFile(filename, stream_contents), IsOk());
+    util::StatusOr<int> output_fd = OpenTestFileToWrite(filename);
+    ASSERT_THAT(output_fd.status(), IsOk());
+    auto output_stream = absl::make_unique<util::FileOutputStream>(*output_fd);
     auto status = WriteToStream(output_stream.get(), stream_contents);
     EXPECT_TRUE(status.ok()) << status;
     std::string file_contents = test::ReadTestFile(filename);
@@ -78,10 +103,13 @@
   std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
   for (auto buffer_size : {1, 10, 100, 1000, 10000, 100000, 1000000}) {
     SCOPED_TRACE(absl::StrCat("buffer_size = ", buffer_size));
-    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
-    int output_fd = test::GetTestFileDescriptor(filename);
+    std::string filename = absl::StrCat(
+        buffer_size, internal::GetTestFileNamePrefix(), "_test.bin");
+    ASSERT_THAT(internal::CreateTestFile(filename, stream_contents), IsOk());
+    util::StatusOr<int> output_fd = OpenTestFileToWrite(filename);
+    ASSERT_THAT(output_fd.status(), IsOk());
     auto output_stream =
-        absl::make_unique<util::FileOutputStream>(output_fd, buffer_size);
+        absl::make_unique<util::FileOutputStream>(*output_fd, buffer_size);
     void* buffer;
     auto next_result = output_stream->Next(&buffer);
     EXPECT_TRUE(next_result.ok()) << next_result.status();
@@ -101,12 +129,15 @@
   int buffer_size = 1234;
   void* buffer;
   std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
-  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
-  int output_fd = test::GetTestFileDescriptor(filename);
+  std::string filename =
+      absl::StrCat(buffer_size, internal::GetTestFileNamePrefix(), "_test.bin");
+  ASSERT_THAT(internal::CreateTestFile(filename, stream_contents), IsOk());
+  util::StatusOr<int> output_fd = OpenTestFileToWrite(filename);
+  ASSERT_THAT(output_fd.status(), IsOk());
 
   // Prepare the stream and do the first call to Next().
   auto output_stream =
-      absl::make_unique<util::FileOutputStream>(output_fd, buffer_size);
+      absl::make_unique<util::FileOutputStream>(*output_fd, buffer_size);
   EXPECT_EQ(0, output_stream->Position());
   auto next_result = output_stream->Next(&buffer);
   EXPECT_TRUE(next_result.ok()) << next_result.status();
diff --git a/cc/util/file_random_access_stream.h b/cc/util/file_random_access_stream.h
index 47bfc13..75966c9 100644
--- a/cc/util/file_random_access_stream.h
+++ b/cc/util/file_random_access_stream.h
@@ -29,6 +29,8 @@
 namespace util {
 
 // An RandomAccessStream that reads from a file descriptor.
+//
+// NOTE: This class in not available when building on Windows.
 class FileRandomAccessStream : public crypto::tink::RandomAccessStream {
  public:
   // Constructs a FileRandomAccessStream that will read from the file specified
diff --git a/cc/util/file_random_access_stream_test.cc b/cc/util/file_random_access_stream_test.cc
index 70c510a..3aebe63 100644
--- a/cc/util/file_random_access_stream_test.cc
+++ b/cc/util/file_random_access_stream_test.cc
@@ -16,15 +16,25 @@
 
 #include "tink/util/file_random_access_stream.h"
 
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <iostream>
+#include <ostream>
 #include <string>
 #include <thread>  // NOLINT(build/c++11)
 #include <utility>
 
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
+#include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "tink/internal/test_file_util.h"
+#include "tink/subtle/random.h"
 #include "tink/util/buffer.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
 namespace crypto {
@@ -32,6 +42,20 @@
 namespace util {
 namespace {
 
+using ::crypto::tink::test::IsOk;
+
+// Opens test file `filename` and returns a file descriptor to it.
+util::StatusOr<int> OpenTestFileToRead(absl::string_view filename) {
+  std::string full_filename = absl::StrCat(test::TmpDir(), "/", filename);
+  int fd = open(full_filename.c_str(), O_RDONLY);
+  if (fd == -1) {
+    return util::Status(absl::StatusCode::kInternal,
+                        absl::StrCat("Cannot open file ", full_filename,
+                                     " error: ", std::strerror(errno)));
+  }
+  return fd;
+}
+
 // Reads the entire 'ra_stream' in chunks of size 'chunk_size',
 // until no more bytes can be read, and puts the read bytes into 'contents'.
 // Returns the status of the last ra_stream->Next()-operation.
@@ -79,15 +103,19 @@
 TEST(FileRandomAccessStreamTest, ReadingStreams) {
   for (auto stream_size : {1, 10, 100, 1000, 10000, 1000000}) {
     SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
+    std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(
+        stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
+        "_file.bin");
+    ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
+                IsOk());
+    util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+    ASSERT_THAT(input_fd.status(), IsOk());
     EXPECT_EQ(stream_size, file_contents.size());
-    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(input_fd);
+    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
     std::string stream_contents;
-    auto status = ReadAll(ra_stream.get(), 1 + (stream_size / 10),
-                          &stream_contents);
+    auto status =
+        ReadAll(ra_stream.get(), 1 + (stream_size / 10), &stream_contents);
     EXPECT_EQ(absl::StatusCode::kOutOfRange, status.code());
     EXPECT_EQ("EOF", status.message());
     EXPECT_EQ(file_contents, stream_contents);
@@ -98,12 +126,16 @@
 TEST(FileRandomAccessStreamTest, ReadingStreamsTillLastByte) {
   for (auto stream_size : {1, 10, 100, 1000, 10000}) {
     SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
+    std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(
+        stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
+        "_file.bin");
+    ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
+                IsOk());
+    util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+    ASSERT_THAT(input_fd.status(), IsOk());
     EXPECT_EQ(stream_size, file_contents.size());
-    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(input_fd);
+    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
     auto buffer = std::move(Buffer::New(stream_size).value());
 
     // Read from the beginning till the last byte.
@@ -116,15 +148,18 @@
   }
 }
 
-
 TEST(FileRandomAccessStreamTest, ConcurrentReads) {
   for (auto stream_size : {100, 1000, 10000, 100000}) {
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
+    std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(
+        stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
+        "_file.bin");
+    ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
+                IsOk());
+    util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+    ASSERT_THAT(input_fd.status(), IsOk());
     EXPECT_EQ(stream_size, file_contents.size());
-    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(input_fd);
+    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
     std::thread read_0(ReadAndVerifyChunk,
         ra_stream.get(), 0, stream_size / 2, file_contents);
     std::thread read_1(ReadAndVerifyChunk,
@@ -142,11 +177,15 @@
 
 TEST(FileRandomAccessStreamTest, NegativeReadPosition) {
   for (auto stream_size : {0, 10, 100, 1000, 10000}) {
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
-    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(input_fd);
+    std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(
+        stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
+        "_file.bin");
+    ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
+                IsOk());
+    util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+    ASSERT_THAT(input_fd.status(), IsOk());
+    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
     int count = 42;
     auto buffer = std::move(Buffer::New(count).value());
     for (auto position : {-100, -10, -1}) {
@@ -161,11 +200,15 @@
 
 TEST(FileRandomAccessStreamTest, NotPositiveReadCount) {
   for (auto stream_size : {0, 10, 100, 1000, 10000}) {
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
-    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(input_fd);
+    std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(
+        stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
+        "_file.bin");
+    ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
+                IsOk());
+    util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+    ASSERT_THAT(input_fd.status(), IsOk());
+    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
     auto buffer = std::move(Buffer::New(42).value());
     int64_t position = 0;
     for (auto count : {-100, -10, -1, 0}) {
@@ -179,11 +222,15 @@
 
 TEST(FileRandomAccessStreamTest, ReadPositionAfterEof) {
   for (auto stream_size : {0, 10, 100, 1000, 10000}) {
-    std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
-    int input_fd =
-        test::GetTestFileDescriptor(filename, stream_size, &file_contents);
-    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(input_fd);
+    std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(
+        stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
+        "_file.bin");
+    ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
+                IsOk());
+    util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
+    ASSERT_THAT(input_fd.status(), IsOk());
+    auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
     int count = 42;
     auto buffer = std::move(Buffer::New(count).value());
     for (auto position : {stream_size + 1, stream_size + 10}) {
diff --git a/cc/util/input_stream_util_test.cc b/cc/util/input_stream_util_test.cc
index 1700588..e54f8a7 100644
--- a/cc/util/input_stream_util_test.cc
+++ b/cc/util/input_stream_util_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/util/input_stream_util.h"
 
+#include <sstream>
 #include <string>
 #include <utility>
 
diff --git a/cc/util/istream_input_stream.cc b/cc/util/istream_input_stream.cc
index 71c642b..45a0449 100644
--- a/cc/util/istream_input_stream.cc
+++ b/cc/util/istream_input_stream.cc
@@ -16,16 +16,17 @@
 
 #include "tink/util/istream_input_stream.h"
 
-#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
 
 #include <algorithm>
 #include <cstring>
 #include <istream>
+#include <memory>
 #include <utility>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
-#include "tink/input_stream.h"
 #include "tink/util/errors.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -85,8 +86,7 @@
   position_ = position_ - actual_count;
 }
 
-IstreamInputStream::~IstreamInputStream() {
-}
+IstreamInputStream::~IstreamInputStream() = default;
 
 int64_t IstreamInputStream::Position() const {
   return position_;
diff --git a/cc/util/istream_input_stream.h b/cc/util/istream_input_stream.h
index 530192a..5ed369a 100644
--- a/cc/util/istream_input_stream.h
+++ b/cc/util/istream_input_stream.h
@@ -17,6 +17,8 @@
 #ifndef TINK_UTIL_ISTREAM_INPUT_STREAM_H_
 #define TINK_UTIL_ISTREAM_INPUT_STREAM_H_
 
+#include <stdint.h>
+
 #include <istream>
 #include <memory>
 
diff --git a/cc/util/istream_input_stream_test.cc b/cc/util/istream_input_stream_test.cc
index 2586348..de5c44f 100644
--- a/cc/util/istream_input_stream_test.cc
+++ b/cc/util/istream_input_stream_test.cc
@@ -16,18 +16,25 @@
 
 #include "tink/util/istream_input_stream.h"
 
-#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
 
 #include <algorithm>
 #include <fstream>
 #include <iostream>
 #include <istream>
+#include <memory>
+#include <ostream>
 #include <string>
 #include <utility>
 
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "tink/internal/test_file_util.h"
 #include "tink/subtle/random.h"
 #include "tink/util/test_util.h"
 
@@ -76,9 +83,10 @@
 };
 
 TEST_F(IstreamInputStreamTest, testReadingStreams) {
-  for (int stream_size : {0, 10, 100, 1000, 10000, 100000, 1000000}) {
+    for (int stream_size : {0, 10, 100, 1000, 10000, 100000, 1000000}) {
     std::string file_contents;
-    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
+    std::string filename = absl::StrCat(
+        stream_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
     auto input = GetTestIstream(filename, stream_size, &file_contents);
     EXPECT_EQ(stream_size, file_contents.size());
     auto input_stream = absl::make_unique<util::IstreamInputStream>(
@@ -95,7 +103,8 @@
   int stream_size = 100000;
   for (int buffer_size : {1, 10, 100, 1000, 10000}) {
     std::string file_contents;
-    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
+    std::string filename = absl::StrCat(
+        buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
     auto input = GetTestIstream(filename, stream_size, &file_contents);
     EXPECT_EQ(stream_size, file_contents.size());
     auto input_stream = absl::make_unique<util::IstreamInputStream>(
@@ -114,7 +123,8 @@
   int buffer_size = 1234;
   const void* buffer;
   std::string file_contents;
-  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
+  std::string filename =
+      absl::StrCat(buffer_size, internal::GetTestFileNamePrefix(), "_file.bin");
   auto input = GetTestIstream(filename, stream_size, &file_contents);
   EXPECT_EQ(stream_size, file_contents.size());
 
diff --git a/cc/util/ostream_output_stream_test.cc b/cc/util/ostream_output_stream_test.cc
index 05db8e54..8a7cf6a 100644
--- a/cc/util/ostream_output_stream_test.cc
+++ b/cc/util/ostream_output_stream_test.cc
@@ -17,9 +17,11 @@
 #include "tink/util/ostream_output_stream.h"
 
 #include <algorithm>
+#include <cstring>
 #include <fstream>
 #include <iostream>
 #include <memory>
+#include <ostream>
 #include <string>
 #include <utility>
 
@@ -27,6 +29,7 @@
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "tink/internal/test_file_util.h"
 #include "tink/subtle/random.h"
 #include "tink/util/test_util.h"
 
@@ -75,7 +78,8 @@
   for (size_t stream_size : {0, 10, 100, 1000, 10000, 100000, 1000000}) {
     SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
     std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
-    std::string filename = absl::StrCat(stream_size, "_writing_test.bin");
+    std::string filename = absl::StrCat(
+        stream_size, internal::GetTestFileNamePrefix(), "_file.bin");
     auto output = GetTestOstream(filename);
     auto output_stream = absl::make_unique<util::OstreamOutputStream>(
         std::move(output));
@@ -92,7 +96,8 @@
   std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
   for (int buffer_size : {1, 10, 100, 1000, 10000, 100000, 1000000}) {
     SCOPED_TRACE(absl::StrCat("buffer_size = ", buffer_size));
-    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
+    std::string filename = absl::StrCat(
+        buffer_size, internal::GetTestFileNamePrefix(), "_file.bin");
     auto output = GetTestOstream(filename);
     auto output_stream = absl::make_unique<util::OstreamOutputStream>(
         std::move(output), buffer_size);
@@ -114,7 +119,8 @@
   int buffer_size = 1234;
   void* buffer;
   std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
-  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
+  std::string filename =
+      absl::StrCat(buffer_size, internal::GetTestFileNamePrefix(), "_file.bin");
   auto output = GetTestOstream(filename);
 
   // Prepare the stream and do the first call to Next().
diff --git a/cc/util/secret_data.h b/cc/util/secret_data.h
index ab87500..54ce234 100644
--- a/cc/util/secret_data.h
+++ b/cc/util/secret_data.h
@@ -28,6 +28,17 @@
 namespace crypto {
 namespace tink {
 namespace util {
+namespace internal {
+
+template <typename T>
+struct SanitizingDeleter {
+  void operator()(T* ptr) {
+    ptr->~T();  // Invoke destructor. Must do this before sanitize.
+    SanitizingAllocator<T>().deallocate(ptr, 1);
+  }
+};
+
+}  // namespace internal
 
 // Stores secret (sensitive) data and makes sure it's marked as such and
 // destroyed in a safe way.
@@ -74,7 +85,7 @@
   using element_type = typename Value::element_type;
   using deleter_type = typename Value::deleter_type;
 
-  SecretUniquePtr() {}
+  SecretUniquePtr() = default;
 
   pointer get() const { return value_.get(); }
   deleter_type& get_deleter() { return value_.get_deleter(); }
diff --git a/cc/util/secret_data_internal.h b/cc/util/secret_data_internal.h
index ddcacd5..7d88b93 100644
--- a/cc/util/secret_data_internal.h
+++ b/cc/util/secret_data_internal.h
@@ -18,7 +18,8 @@
 #define TINK_UTIL_SECRET_DATA_INTERNAL_H_
 
 #include <cstddef>
-#include <memory>
+#include <cstdlib>
+#include <limits>
 #include <new>
 
 #include "absl/base/attributes.h"
@@ -30,15 +31,12 @@
 namespace util {
 namespace internal {
 
-// placeholder for sanitization_functions, please ignore
 inline void SafeZeroMemory(void* ptr, std::size_t size) {
   OPENSSL_cleanse(ptr, size);
 }
 
 template <typename T>
-struct SanitizingAllocator {
-  typedef T value_type;
-
+struct SanitizingAllocatorImpl {
   // If aligned operator new is not supported this only supports under aligned
   // types.
 #ifndef __cpp_aligned_new
@@ -47,12 +45,7 @@
                 "before C++17");
 #endif
 
-  SanitizingAllocator() = default;
-  template <class U>
-  explicit constexpr SanitizingAllocator(
-      const SanitizingAllocator<U>&) noexcept {}
-
-  ABSL_MUST_USE_RESULT T* allocate(std::size_t n) {
+  static T* allocate(std::size_t n) {
     if (n > std::numeric_limits<std::size_t>::max() / sizeof(T)) {
 #ifdef ABSL_HAVE_EXCEPTIONS
       throw std::bad_array_new_length();
@@ -62,14 +55,13 @@
     }
     std::size_t size = n * sizeof(T);
 #ifdef __cpp_aligned_new
-    void* result = ::operator new(size, std::align_val_t(alignof(T)));
+    return static_cast<T*>(::operator new(size, std::align_val_t(alignof(T))));
 #else
-    void* result = ::operator new(size);
+    return static_cast<T*>(::operator new(size));
 #endif
-    return static_cast<T*>(result);
   }
 
-  void deallocate(T* ptr, std::size_t n) noexcept {
+  static void deallocate(void* ptr, std::size_t n) {
     SafeZeroMemory(ptr, n * sizeof(T));
 #ifdef __cpp_aligned_new
     ::operator delete(ptr, std::align_val_t(alignof(T)));
@@ -77,42 +69,39 @@
     ::operator delete(ptr);
 #endif
   }
-
-  // Allocator requirements mandate definition of eq and neq operators
-  bool operator==(const SanitizingAllocator&) { return true; }
-  bool operator!=(const SanitizingAllocator&) { return false; }
 };
 
 // Specialization for malloc-like aligned storage.
 template <>
-struct SanitizingAllocator<void> {
-  typedef void value_type;
+struct SanitizingAllocatorImpl<void> {
+  static void* allocate(std::size_t n) { return std::malloc(n); }
+  static void deallocate(void* ptr, std::size_t n) {
+    SafeZeroMemory(ptr, n);
+    return std::free(ptr);
+  }
+};
+
+template <typename T>
+struct SanitizingAllocator {
+  typedef T value_type;
 
   SanitizingAllocator() = default;
   template <class U>
   explicit constexpr SanitizingAllocator(
       const SanitizingAllocator<U>&) noexcept {}
 
-  ABSL_MUST_USE_RESULT void* allocate(std::size_t n) { return std::malloc(n); }
+  ABSL_MUST_USE_RESULT T* allocate(std::size_t n) {
+    return SanitizingAllocatorImpl<T>::allocate(n);
+  }
 
-  void deallocate(void* ptr, std::size_t n) noexcept {
-    SafeZeroMemory(ptr, n);
-    std::free(ptr);
+  void deallocate(T* ptr, std::size_t n) noexcept {
+    SanitizingAllocatorImpl<T>::deallocate(ptr, n);
   }
 
   // Allocator requirements mandate definition of eq and neq operators
   bool operator==(const SanitizingAllocator&) { return true; }
   bool operator!=(const SanitizingAllocator&) { return false; }
 };
-// placeholder 2 for sanitization_functions, please ignore
-
-template <typename T>
-struct SanitizingDeleter {
-  void operator()(T* ptr) {
-    ptr->~T();  // Invoke destructor. Must do this before sanitize.
-    SanitizingAllocator<T>().deallocate(ptr, 1);
-  }
-};
 
 }  // namespace internal
 }  // namespace util
diff --git a/cc/util/secret_data_test.cc b/cc/util/secret_data_test.cc
index 72f1167..3de92e9 100644
--- a/cc/util/secret_data_test.cc
+++ b/cc/util/secret_data_test.cc
@@ -121,6 +121,7 @@
   SecretValue<int> s(102);
   SecretValue<int> t(std::move(s));
   EXPECT_THAT(t.value(), Eq(102));
+  // NOLINTNEXTLINE(bugprone-use-after-move)
   EXPECT_THAT(s.value(), AnyOf(Eq(0), Eq(102)));
 }
 
@@ -129,6 +130,7 @@
   SecretValue<int> t;
   t = std::move(s);
   EXPECT_THAT(t.value(), Eq(102));
+  // NOLINTNEXTLINE(bugprone-use-after-move)
   EXPECT_THAT(s.value(), AnyOf(Eq(0), Eq(102)));
 }
 
diff --git a/cc/util/secret_proto.h b/cc/util/secret_proto.h
index af4c9f2..ea67556 100644
--- a/cc/util/secret_proto.h
+++ b/cc/util/secret_proto.h
@@ -61,7 +61,7 @@
     return proto;
   }
 
-  SecretProto() {}
+  SecretProto() = default;
 
   SecretProto(const SecretProto& other) { *value_ = *other.value_; }
 
diff --git a/cc/util/status.cc b/cc/util/status.cc
deleted file mode 100644
index bc7b389..0000000
--- a/cc/util/status.cc
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//     http://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 "tink/util/status.h"
-
-#include <sstream>
-#include <string>
-
-#include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
-
-using ::std::ostream;
-
-namespace crypto {
-namespace tink {
-namespace util {
-
-#ifndef TINK_USE_ABSL_STATUS
-namespace {
-
-
-const Status& GetCancelled() {
-  static const Status* status =
-      new Status(::crypto::tink::util::error::CANCELLED, "");
-  return *status;
-}
-
-const Status& GetUnknown() {
-  static const Status* status =
-      new Status(::crypto::tink::util::error::UNKNOWN, "");
-  return *status;
-}
-
-const Status& GetOk() {
-  static const Status* status = new Status;
-  return *status;
-}
-
-}  // namespace
-
-Status::Status(const ::absl::Status& status)
-    : code_(absl::StatusCode::kOk) {
-  if (status.ok()) return;
-  code_ = status.code();
-  message_ = std::string(status.message());
-}
-
-Status::operator ::absl::Status() const {
-  if (ok()) return ::absl::OkStatus();
-  return ::absl::Status(code_, message_);
-}
-
-Status::Status() : code_(absl::StatusCode::kOk), message_("") {
-}
-
-Status::Status(::crypto::tink::util::error::Code error,
-               const std::string& error_message)
-    : code_(static_cast<absl::StatusCode>(error)), message_(error_message) {
-  if (code_ == absl::StatusCode::kOk) {
-    message_.clear();
-  }
-}
-
-Status::Status(absl::StatusCode code, absl::string_view error_message)
-    : code_(code),
-      message_(error_message) {
-  if (code_ == absl::StatusCode::kOk) {
-    message_.clear();
-  }
-}
-
-Status& Status::operator=(const Status& other) {
-  code_ = other.code_;
-  message_ = other.message_;
-  return *this;
-}
-
-const Status& Status::CANCELLED = GetCancelled();
-const Status& Status::UNKNOWN = GetUnknown();
-const Status& Status::OK = GetOk();
-
-std::string Status::ToString() const {
-  if (code_ == absl::StatusCode::kOk) {
-    return "OK";
-  }
-
-  std::ostringstream oss;
-  oss << code_ << ": " << message_;
-  return oss.str();
-}
-
-std::string ErrorCodeString(crypto::tink::util::error::Code error) {
-  switch (error) {
-    case crypto::tink::util::error::OK:
-      return "OK";
-    case crypto::tink::util::error::CANCELLED:
-      return "CANCELLED";
-    case crypto::tink::util::error::UNKNOWN:
-      return "UNKNOWN";
-    case crypto::tink::util::error::INVALID_ARGUMENT:
-      return "INVALID_ARGUMENT";
-    case crypto::tink::util::error::DEADLINE_EXCEEDED:
-      return "DEADLINE_EXCEEDED";
-    case crypto::tink::util::error::NOT_FOUND:
-      return "NOT_FOUND";
-    case crypto::tink::util::error::ALREADY_EXISTS:
-      return "ALREADY_EXISTS";
-    case crypto::tink::util::error::PERMISSION_DENIED:
-      return "PERMISSION_DENIED";
-    case crypto::tink::util::error::RESOURCE_EXHAUSTED:
-      return "RESOURCE_EXHAUSTED";
-    case crypto::tink::util::error::FAILED_PRECONDITION:
-      return "FAILED_PRECONDITION";
-    case crypto::tink::util::error::ABORTED:
-      return "ABORTED";
-    case crypto::tink::util::error::OUT_OF_RANGE:
-      return "OUT_OF_RANGE";
-    case crypto::tink::util::error::UNIMPLEMENTED:
-      return "UNIMPLEMENTED";
-    case crypto::tink::util::error::INTERNAL:
-      return "INTERNAL";
-    case crypto::tink::util::error::UNAVAILABLE:
-      return "UNAVAILABLE";
-    case crypto::tink::util::error::DATA_LOSS:
-      return "DATA_LOSS";
-    case crypto::tink::util::error::UNAUTHENTICATED:
-      return "UNAUTHENTICATED";
-  }
-  // Avoid using a "default" in the switch, so that the compiler can
-  // give us a warning, but still provide a fallback here.
-  return absl::StrCat(error);
-}
-
-extern ostream& operator<<(ostream& os, crypto::tink::util::error::Code code) {
-  os << ErrorCodeString(code);
-  return os;
-}
-
-extern ostream& operator<<(ostream& os, const Status& other) {
-  os << other.ToString();
-  return os;
-}
-
-#endif  // TINK_USE_ABSL_STATUS
-
-
-}  // namespace util
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/util/status.h b/cc/util/status.h
index a130e2e..ef58949 100644
--- a/cc/util/status.h
+++ b/cc/util/status.h
@@ -20,206 +20,16 @@
 #ifndef TINK_UTIL_STATUS_H_
 #define TINK_UTIL_STATUS_H_
 
-#include <ostream>
-#include <string>
-
-#include "absl/base/attributes.h"
 #include "absl/status/status.h"
 
+#define TINK_USE_ABSL_STATUS
+
 namespace crypto {
 namespace tink {
 namespace util {
 
-#ifndef TINK_USE_ABSL_STATUS
-
-namespace error {
-
-// These values match the error codes in the codes.proto file of the original.
-enum ABSL_DEPRECATED("Prefer using absl::StatusCode instead.") Code {
-  // Not an error; returned on success
-  OK = 0,
-
-  // The operation was cancelled (typically by the caller).
-  CANCELLED = 1,
-
-  // Unknown error.
-  UNKNOWN = 2,
-
-  // Client specified an invalid argument.  Note that this differs
-  // from FAILED_PRECONDITION.  INVALID_ARGUMENT indicates arguments
-  // that are problematic regardless of the state of the system
-  // (e.g., a malformed file name).
-  INVALID_ARGUMENT = 3,
-
-  // Deadline expired before operation could complete.
-  DEADLINE_EXCEEDED = 4,
-
-  // Some requested entity (e.g., file or directory) was not found.
-  NOT_FOUND = 5,
-
-  // Some entity that we attempted to create (e.g., file or directory)
-  // already exists.
-  ALREADY_EXISTS = 6,
-
-  // The caller does not have permission to execute the specified
-  // operation.
-  PERMISSION_DENIED = 7,
-
-  // Some resource has been exhausted, perhaps a per-user quota, or
-  // perhaps the entire file system is out of space.
-  RESOURCE_EXHAUSTED = 8,
-
-  // Operation was rejected because the system is not in a state
-  // required for the operation's execution.  For example, directory
-  // to be deleted may be non-empty, an rmdir operation is applied to
-  // a non-directory, etc.
-  //
-  // A litmus test that may help a service implementor in deciding
-  // between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
-  //  (a) Use UNAVAILABLE if the client can retry just the failing call.
-  //  (b) Use ABORTED if the client should retry at a higher-level
-  //      (e.g., restarting a read-modify-write sequence).
-  //  (c) Use FAILED_PRECONDITION if the client should not retry until
-  //      the system state has been explicitly fixed.  E.g., if an "rmdir"
-  //      fails because the directory is non-empty, FAILED_PRECONDITION
-  //      should be returned since the client should not retry unless
-  //      they have first fixed up the directory by deleting files from it.
-  FAILED_PRECONDITION = 9,
-
-  // The operation was aborted, typically due to a concurrency issue
-  // like sequencer check failures, transaction aborts, etc.
-  //
-  // See litmus test above for deciding between FAILED_PRECONDITION,
-  // ABORTED, and UNAVAILABLE.
-  ABORTED = 10,
-
-  // Operation was attempted past the valid range.  E.g., seeking or
-  // reading past end of file.
-  //
-  // Unlike INVALID_ARGUMENT, this error indicates a problem that may
-  // be fixed if the system state changes. For example, a 32-bit file
-  // system will generate INVALID_ARGUMENT if asked to read at an
-  // offset that is not in the range [0,2^32-1], but it will generate
-  // OUT_OF_RANGE if asked to read from an offset past the current
-  // file size.
-  OUT_OF_RANGE = 11,
-
-  // Operation is not implemented or not supported/enabled in this service.
-  UNIMPLEMENTED = 12,
-
-  // Internal errors.  Means some invariants expected by underlying
-  // system has been broken.  If you see one of these errors,
-  // something is very broken.
-  INTERNAL = 13,
-
-  // The service is currently unavailable.  This is a most likely a
-  // transient condition and may be corrected by retrying with
-  // a backoff.
-  //
-  // See litmus test above for deciding between FAILED_PRECONDITION,
-  // ABORTED, and UNAVAILABLE.
-  UNAVAILABLE = 14,
-
-  // Unrecoverable data loss or corruption.
-  DATA_LOSS = 15,
-
-  // Invalid authentication credentials.
-  UNAUTHENTICATED = 16,
-};
-
-}  // namespace error
-
-// TODO(tholenst) Remove this compile time flag in Tink 1.5. This should not be
-// used, except as a temporary measure.
-#ifndef CPP_TINK_TEMPORARY_STATUS_MUST_NOT_USE_RESULT
-class ABSL_MUST_USE_RESULT Status;
-#endif
-
-// A Status is a combination of an error code and a string message (for non-OK
-// error codes).
-class Status {
- public:
-  // Creates an OK status
-  Status();
-
-  // Make a Status from the specified error and message.
-  Status(::crypto::tink::util::error::Code error,
-         const std::string& error_message);
-  // Abseil-compatible constructor from an error and a message
-  Status(absl::StatusCode code, absl::string_view error_message);
-
-  Status(const Status& other) = default;
-
-  Status& operator=(const Status& other);
-
-  // Some pre-defined Status objects
-  ABSL_DEPRECATED("Use OkStatus() instead.")
-  static const Status& OK;  // Identical to 0-arg constructor
-  ABSL_DEPRECATED("Use Status(absl::StatusCode::kCancelled, "") instead.")
-  static const Status& CANCELLED;
-  ABSL_DEPRECATED("Use Status(absl::StatusCode::kUnknown, "") instead.")
-  static const Status& UNKNOWN;
-
-  // Accessors
-  bool ok() const {
-    return code_ == absl::StatusCode::kOk;
-  }
-  ABSL_DEPRECATED("Use its absl-compatible version code() instead.")
-  int error_code() const {
-    return static_cast<int>(code_);
-  }
-  ABSL_DEPRECATED("Use its absl-compatible version code() instead.")
-  ::crypto::tink::util::error::Code CanonicalCode() const {
-    return static_cast<::crypto::tink::util::error::Code>(code_);
-  }
-  ABSL_DEPRECATED("Use its absl-compatible version message() instead.")
-  const std::string& error_message() const { return message_; }
-
-  // Abseil-compatible accessors
-  absl::StatusCode code() const {
-    return static_cast<absl::StatusCode>(code_);
-  }
-  absl::string_view message() const {
-    return message_;
-  }
-
-  bool operator==(const Status& other) const;
-  bool operator!=(const Status& other) const;
-
-  // NoOp
-  void IgnoreError() const {
-  }
-
-  std::string ToString() const;
-
-  Status(const ::absl::Status& status);
-  operator ::absl::Status() const;
-
- private:
-  absl::StatusCode code_;
-  std::string message_;
-};
-
-inline bool Status::operator==(const Status& other) const {
-  return (this->code_ == other.code_) && (this->message_ == other.message_);
-}
-
-inline bool Status::operator!=(const Status& other) const {
-  return !(*this == other);
-}
-
-extern std::string ErrorCodeString(crypto::tink::util::error::Code error);
-
-extern ::std::ostream& operator<<(::std::ostream& os,
-                                  ::crypto::tink::util::error::Code code);
-extern ::std::ostream& operator<<(::std::ostream& os, const Status& other);
-
-#else
-
 using Status = absl::Status;
 
-#endif  // TINK_USE_ABSL_STATUS
-
 // Returns an OK status, equivalent to a default constructed instance.
 inline Status OkStatus() { return Status(); }
 
diff --git a/cc/util/status_test.cc b/cc/util/status_test.cc
deleted file mode 100644
index 4be7542..0000000
--- a/cc/util/status_test.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// 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
-//
-//     http://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 "tink/util/status.h"
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/status/status.h"
-
-namespace crypto {
-namespace tink {
-namespace util {
-namespace {
-
-#ifndef TINK_USE_ABSL_STATUS
-TEST(StatusTest, CreateNonOkStatusWithAbslStatusCode) {
-  Status util_status = Status(error::Code::CANCELLED, "message");
-  Status absl_status = Status(absl::StatusCode::kCancelled, "message");
-  ASSERT_EQ(util_status, absl_status);
-}
-
-TEST(StatusTest, CreateOkStatusWithAbslStatusCode) {
-  Status util_status = Status(error::Code::OK, "message");
-  Status absl_status = Status(absl::StatusCode::kOk, "message");
-  ASSERT_EQ(util_status, absl_status);
-  ASSERT_EQ(absl_status.message(), "");
-}
-
-TEST(StatusTest, ConvertNonOkStatus) {
-  Status util_status = Status(error::Code::RESOURCE_EXHAUSTED, "message");
-  absl::Status absl_status = util_status;
-  ASSERT_EQ(util_status.code(), absl_status.code());
-  ASSERT_EQ(util_status.message(), absl_status.message());
-}
-
-TEST(StatusTest, ConvertOkStatus) {
-  Status util_status = OkStatus();
-  absl::Status absl_status = util_status;
-  ASSERT_TRUE(absl_status.ok());
-  ASSERT_EQ(absl_status.message(), "");
-}
-#endif
-
-}  // namespace
-}  // namespace util
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/util/statusor.h b/cc/util/statusor.h
index 28cc35e..859cf11 100644
--- a/cc/util/statusor.h
+++ b/cc/util/statusor.h
@@ -17,254 +17,18 @@
 #ifndef TINK_UTIL_STATUSOR_H_
 #define TINK_UTIL_STATUSOR_H_
 
-#include <cstdlib>
-#include <iostream>
-#include <utility>
-
 #include "absl/status/statusor.h"
 #include "tink/util/status.h"
 
+#define TINK_USE_ABSL_STATUSOR
+
 namespace crypto {
 namespace tink {
 namespace util {
 
-#ifndef TINK_USE_ABSL_STATUSOR
-
-#ifndef CPP_TINK_TEMPORARY_STATUS_MUST_NOT_USE_RESULT
-template <typename T>
-class ABSL_MUST_USE_RESULT StatusOr;
-#endif
-
-// TODO(b/122292096): Migrate this to absl::StatusOr
-// A StatusOr holds a Status (in the case of an error), or a value T.
-template <typename T>
-class StatusOr {
- public:
-  // StatusOr<T>::value_type
-  //
-  // This instance data provides a generic `value_type` member for use within
-  // generic programming. This usage is analogous to that of
-  // `optional::value_type` in the case of `std::optional`.
-  using value_type = T;
-
-  using type = T;
-  // Has status UNKNOWN.
-  inline StatusOr();
-
-  // Builds from a non-OK status. Crashes if an OK status is specified.
-  inline StatusOr(const ::crypto::tink::util::Status& status);  // NOLINT
-
-  // Builds from the specified value.
-  inline StatusOr(const T& value);  // NOLINT
-  inline StatusOr(T&& value);       // NOLINT
-
-  // Copy constructor.
-  inline StatusOr(const StatusOr& other);
-
-  // Move constructor.
-  inline StatusOr(StatusOr&& other);
-
-  // Conversion copy constructor, T must be copy constructible from U.
-  template <typename U>
-  inline StatusOr(const StatusOr<U>& other);
-
-  // Assignment operator.
-  inline const StatusOr& operator=(const StatusOr& other);
-
-  // Conversion assignment operator, T must be assignable from U
-  template <typename U>
-  inline const StatusOr& operator=(const StatusOr<U>& other);
-
-  // Accessors.
-  inline const ::crypto::tink::util::Status& status() const {
-    return status_;
-  }
-
-  // Shorthand for status().ok().
-  inline bool ok() const {
-    return status_.ok();
-  }
-
-  // Returns value or crashes if ok() is false.
-  inline const T& ValueOrDie() const& {
-    EnsureOk();
-    return *value_;
-  }
-  inline T& ValueOrDie() & {
-    EnsureOk();
-    return *value_;
-  }
-  inline const T&& ValueOrDie() const&& {
-    EnsureOk();
-    return *std::move(value_);
-  }
-  inline T&& ValueOrDie() && {
-    EnsureOk();
-    return *std::move(value_);
-  }
-
-  // Returns value if ok(), otherwise crashes if exceptions are disabled OR
-  // throws if exceptions are enabled.
-  inline const T& value() const& {
-    if (!ok()) AbortWithMessageFrom(status_);
-    return *value_;
-  }
-  inline T& value() & {
-    if (!ok()) AbortWithMessageFrom(status_);
-    return *value_;
-  }
-  inline const T&& value() const&& {
-    if (!ok()) AbortWithMessageFrom(std::move(status_));
-    return *std::move(value_);
-  }
-  inline T&& value() && {
-    if (!ok()) AbortWithMessageFrom(std::move(status_));
-    return *std::move(value_);
-  }
-
-  // Implicitly convertible to absl::StatusOr. Implicit conversions explicitly
-  // allowed by style arbiter waiver in cl/351594378.
-  operator ::absl::StatusOr<T>() const&;  // NOLINT
-  operator ::absl::StatusOr<T>() &&;      // NOLINT
-
-  // Returns value or crashes if ok() is false.
-  inline const T& operator*() const& {
-    EnsureOk();
-    return *value_;
-  }
-
-  inline T& operator*() & {
-    EnsureOk();
-    return *value_;
-  }
-
-  inline T&& operator*() && {
-    EnsureOk();
-    return *std::move(value_);
-  }
-
-  inline const T&& operator*() const&& {
-    EnsureOk();
-    return *std::move(value_);
-  }
-
-  // Returns reference to value or crashes if ok() is false.
-  T* operator->() {
-    EnsureOk();
-    return &(value_.value());
-  }
-
-  const T* operator->() const {
-    EnsureOk();
-    return &(value_.value());
-  }
-
-  template <typename U>
-  friend class StatusOr;
-
- private:
-  void EnsureOk() const {
-    if (ABSL_PREDICT_FALSE(!ok())) {
-      std::cerr << "Attempting to fetch value of non-OK StatusOr\n";
-      std::cerr << status() << std::endl;
-      std::_Exit(1);
-    }
-  }
-
-  void AbortWithMessageFrom(crypto::tink::util::Status status) const {
-    std::cerr << "Attempting to fetch value instead of handling error\n";
-    std::cerr << status.ToString();
-    std::abort();
-  }
-
-
-  Status status_;
-  absl::optional<T> value_;
-};
-
-// Implementation.
-
-template <typename T>
-inline StatusOr<T>::StatusOr()
-    : status_(absl::StatusCode::kUnknown, "") {
-}
-
-template <typename T>
-inline StatusOr<T>::StatusOr(
-    const ::crypto::tink::util::Status& status) : status_(status) {
-  if (status.ok()) {
-    std::cerr << "::crypto::tink::util::OkStatus() "
-              << "is not a valid argument to StatusOr\n";
-    std::_Exit(1);
-  }
-}
-
-template <typename T>
-inline StatusOr<T>::StatusOr(const T& value) : value_(value) {
-}
-
-template <typename T>
-inline StatusOr<T>::StatusOr(T&& value) : value_(std::move(value)) {
-}
-
-template <typename T>
-inline StatusOr<T>::StatusOr(const StatusOr& other)
-    : status_(other.status_), value_(other.value_) {
-}
-
-template <typename T>
-inline StatusOr<T>::StatusOr(StatusOr&& other)
-    : status_(other.status_), value_(std::move(other.value_)) {
-}
-
-template <typename T>
-template <typename U>
-inline StatusOr<T>::StatusOr(const StatusOr<U>& other)
-    : status_(other.status_), value_(other.value_) {
-}
-
-template <typename T>
-inline const StatusOr<T>& StatusOr<T>::operator=(const StatusOr& other) {
-  status_ = other.status_;
-  if (status_.ok()) {
-    value_ = *other.value_;
-  } else {
-    value_ = absl::nullopt;
-  }
-  return *this;
-}
-
-template <typename T>
-template <typename U>
-inline const StatusOr<T>& StatusOr<T>::operator=(const StatusOr<U>& other) {
-  status_ = other.status_;
-  if (status_.ok()) {
-    value_ = *other.value_;
-  } else {
-    value_ = absl::nullopt;
-  }
-  return *this;
-}
-
-template <typename T>
-StatusOr<T>::operator ::absl::StatusOr<T>() const& {
-  if (!ok()) return ::absl::Status(status_);
-  return *value_;
-}
-
-template <typename T>
-StatusOr<T>::operator ::absl::StatusOr<T>() && {
-  if (!ok()) return ::absl::Status(std::move(status_));
-  return std::move(*value_);
-}
-
-#else
-
 template <typename T>
 using StatusOr = absl::StatusOr<T>;
 
-#endif  // TINK_USE_ABSL_STATUSOR
-
 }  // namespace util
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/util/statusor_test.cc b/cc/util/statusor_test.cc
deleted file mode 100644
index 7aa8583..0000000
--- a/cc/util/statusor_test.cc
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// 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
-//
-//     http://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 "tink/util/statusor.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/memory/memory.h"
-#include "absl/status/status.h"
-#include "absl/status/statusor.h"
-#include "tink/util/status.h"
-#include "tink/util/test_matchers.h"
-
-namespace crypto {
-namespace tink {
-namespace util {
-namespace {
-
-using ::crypto::tink::test::IsOk;
-using ::testing::Eq;
-using ::testing::Not;
-using ::testing::Pointee;
-
-TEST(StatusOrTest, ConvertOkToAbsl) {
-  StatusOr<int> instance = 1;
-
-  absl::StatusOr<int> converted = instance;
-  ASSERT_TRUE(converted.ok());
-  EXPECT_EQ(*converted, 1);
-}
-
-TEST(StatusOrTest, ConvertErrorToAbsl) {
-  #ifndef TINK_USE_ABSL_STATUS
-  StatusOr<int> instance{
-      Status(error::Code::INVALID_ARGUMENT, "Error message")};
-  #else
-  StatusOr<int> instance{
-      Status(absl::StatusCode::kInvalidArgument, "Error message")};
-  #endif
-
-  absl::StatusOr<int> converted = instance;
-  ASSERT_FALSE(converted.ok());
-  EXPECT_EQ(converted.status().code(), absl::StatusCode::kInvalidArgument);
-  EXPECT_EQ(converted.status().message(), "Error message");
-}
-
-TEST(StatusOrTest, ConvertUncopyableToAbsl) {
-  StatusOr<std::unique_ptr<int>> instance = absl::make_unique<int>(1);
-
-  absl::StatusOr<std::unique_ptr<int>> converted = std::move(instance);
-  ASSERT_TRUE(converted.ok());
-  EXPECT_THAT(*converted, Pointee(Eq(1)));
-}
-
-class NoDefaultConstructor {
- public:
-  explicit NoDefaultConstructor(int i) {}
-
-  NoDefaultConstructor() = delete;
-  NoDefaultConstructor(const NoDefaultConstructor&) = default;
-  NoDefaultConstructor& operator=(const NoDefaultConstructor&) =
-      default;
-  NoDefaultConstructor(NoDefaultConstructor&&) = default;
-  NoDefaultConstructor& operator=(NoDefaultConstructor&&) = default;
-};
-
-// Tests that we can construct a StatusOr<T> even if there is no default
-// constructor for T.
-TEST(StatusOrTest, WithNoDefaultConstructor) {
-  StatusOr<NoDefaultConstructor> value = NoDefaultConstructor(13);
-  StatusOr<NoDefaultConstructor> error =
-      Status(absl::StatusCode::kInvalidArgument, "Error message");
-}
-
-// This tests that when we assign to something which is previously an error,
-// we create a new optional inside the StatusOr, and do not try to assign to
-// the value of the optional instead.
-TEST(StatusOrTest, AssignToErrorStatus) {
-  StatusOr<std::string> error_initially =
-      Status(absl::StatusCode::kInvalidArgument, "Error message");
-  ASSERT_THAT(error_initially, Not(IsOk()));
-  StatusOr<std::string> ok_initially = std::string("Hi");
-  error_initially = ok_initially;
-  ASSERT_THAT(error_initially, IsOk());
-  ASSERT_THAT(error_initially.value(), Eq("Hi"));
-
-#ifndef TINK_USE_ABSL_STATUSOR
-  ASSERT_THAT(error_initially.ValueOrDie(), Eq("Hi"));
-#endif
-}
-
-// This tests that when we assign to something which is previously an error and
-// at the same time use the implicit conversion operator, we create a new
-// optional inside the StatusOr, and do not try to assign to the value of the
-// optional instead.
-TEST(StatusOrTest, AssignToErrorStatusImplicitConvertible) {
-  StatusOr<std::string> error_initially =
-      Status(absl::StatusCode::kInvalidArgument, "Error message");
-  ASSERT_THAT(error_initially, Not(IsOk()));
-  StatusOr<char const*> ok_initially = "Hi";
-  error_initially = ok_initially;
-  ASSERT_THAT(error_initially, IsOk());
-  ASSERT_THAT(error_initially.value(), Eq("Hi"));
-
-#ifndef TINK_USE_ABSL_STATUSOR
-  ASSERT_THAT(error_initially.ValueOrDie(), Eq("Hi"));
-#endif
-}
-
-#ifndef TINK_USE_ABSL_STATUSOR
-TEST(StatusOrTest, MoveOutMoveOnlyValueOrDie) {
-  StatusOr<std::unique_ptr<int>> status_or_unique_ptr_int =
-      absl::make_unique<int>(10);
-  std::unique_ptr<int> ten = std::move(status_or_unique_ptr_int.ValueOrDie());
-  ASSERT_THAT(*ten, Eq(10));
-}
-#endif
-
-TEST(StatusOrTest, MoveOutMoveOnlyValue) {
-  StatusOr<std::unique_ptr<int>> status_or_unique_ptr_int =
-      absl::make_unique<int>(10);
-  std::unique_ptr<int> ten = std::move(status_or_unique_ptr_int.value());
-  ASSERT_THAT(*ten, Eq(10));
-}
-
-TEST(STatusOrTest, CallValueOnConst) {
-  const StatusOr<int> const_status_or_ten = 10;
-  ASSERT_THAT(const_status_or_ten.value(), Eq(10));
-}
-
-TEST(StatusOrTest, CallValueOnConstTemp) {
-  const StatusOr<int> const_status_or_ten = 10;
-  ASSERT_THAT(std::move(const_status_or_ten).value(), Eq(10));
-}
-
-TEST(StatusOrTest, TestValueConst) {
-  const int kI = 4;
-  const absl::StatusOr<int> thing(kI);
-  EXPECT_EQ(kI, *thing);
-}
-
-TEST(StatusOrTest, TestPointerValue) {
-  const int kI = 0;
-  absl::StatusOr<const int*> thing(&kI);
-  EXPECT_EQ(&kI, *thing);
-}
-
-TEST(StatusOrTest, TestPointerValueConst) {
-  const int kI = 0;
-  const absl::StatusOr<const int*> thing(&kI);
-  EXPECT_EQ(&kI, *thing);
-}
-
-TEST(StatusOrTest, OperatorStarRefQualifiers) {
-  static_assert(
-      std::is_same<const int&,
-                   decltype(*std::declval<const absl::StatusOr<int>&>())>(),
-      "Unexpected ref-qualifiers");
-  static_assert(
-      std::is_same<int&, decltype(*std::declval<absl::StatusOr<int>&>())>(),
-      "Unexpected ref-qualifiers");
-  static_assert(
-      std::is_same<const int&&,
-                   decltype(*std::declval<const absl::StatusOr<int>&&>())>(),
-      "Unexpected ref-qualifiers");
-  static_assert(
-      std::is_same<int&&, decltype(*std::declval<absl::StatusOr<int>&&>())>(),
-      "Unexpected ref-qualifiers");
-}
-
-TEST(StatusOrTest, OperatorStar) {
-  const util::StatusOr<std::string> const_lvalue("hello");
-  EXPECT_EQ("hello", *const_lvalue);
-
-  util::StatusOr<std::string> lvalue("hello");
-  EXPECT_EQ("hello", *lvalue);
-
-  // Note: Recall that std::move() is equivalent to a static_cast to an rvalue
-  // reference type.
-  const util::StatusOr<std::string> const_rvalue("hello");
-  EXPECT_EQ("hello", *std::move(const_rvalue));  // NOLINT
-
-  util::StatusOr<std::string> rvalue("hello");
-  EXPECT_EQ("hello", *std::move(rvalue));
-}
-
-TEST(StatusOrTest, OperatorArrowQualifiers) {
-  static_assert(
-      std::is_same<
-          const int*,
-          decltype(std::declval<const util::StatusOr<int>&>().operator->())>(),
-      "Unexpected qualifiers");
-  static_assert(
-      std::is_same<
-          int*, decltype(std::declval<util::StatusOr<int>&>().operator->())>(),
-      "Unexpected qualifiers");
-  static_assert(
-      std::is_same<
-          const int*,
-          decltype(std::declval<const util::StatusOr<int>&&>().operator->())>(),
-      "Unexpected qualifiers");
-  static_assert(
-      std::is_same<
-          int*, decltype(std::declval<util::StatusOr<int>&&>().operator->())>(),
-      "Unexpected qualifiers");
-}
-
-TEST(StatusOrTest, OperatorArrow) {
-  const util::StatusOr<std::string> const_lvalue("hello");
-  EXPECT_EQ(std::string("hello"), const_lvalue->c_str());
-
-  util::StatusOr<std::string> lvalue("hello");
-  EXPECT_EQ(std::string("hello"), lvalue->c_str());
-}
-
-TEST(StatusOr, ElementType) {
-  static_assert(std::is_same<absl::StatusOr<int>::value_type, int>(), "");
-  static_assert(std::is_same<absl::StatusOr<char>::value_type, char>(), "");
-}
-
-}  // namespace
-
-}  // namespace util
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/util/test_keyset_handle.cc b/cc/util/test_keyset_handle.cc
index f60ca75..dd2de49 100644
--- a/cc/util/test_keyset_handle.cc
+++ b/cc/util/test_keyset_handle.cc
@@ -16,6 +16,7 @@
 
 #include "tink/util/test_keyset_handle.h"
 
+#include <memory>
 #include <utility>
 
 #include "absl/memory/memory.h"
diff --git a/cc/util/test_keyset_handle.h b/cc/util/test_keyset_handle.h
index dc086da..da13136 100644
--- a/cc/util/test_keyset_handle.h
+++ b/cc/util/test_keyset_handle.h
@@ -17,6 +17,8 @@
 #ifndef TINK_UTIL_TEST_KEYSET_HANDLE_H_
 #define TINK_UTIL_TEST_KEYSET_HANDLE_H_
 
+#include <memory>
+
 #include "tink/keyset_handle.h"
 #include "proto/tink.pb.h"
 
diff --git a/cc/util/test_matchers.h b/cc/util/test_matchers.h
index 9f42156..0a83996 100644
--- a/cc/util/test_matchers.h
+++ b/cc/util/test_matchers.h
@@ -17,6 +17,7 @@
 #ifndef TINK_UTIL_TEST_MATCHERS_H_
 #define TINK_UTIL_TEST_MATCHERS_H_
 
+#include <ostream>
 #include <string>
 
 #include "gmock/gmock.h"
@@ -114,7 +115,8 @@
 // Matches a util::StatusOk() value.
 // This is better than EXPECT_TRUE(status.ok())
 // because the error message is a part of the failure messsage.
-MATCHER(IsOk, "is a Status with an OK value") {
+MATCHER(IsOk,
+        absl::StrCat(negation ? "isn't" : "is", " a Status with an OK value")) {
   if (arg.ok()) {
     return true;
   }
@@ -131,26 +133,20 @@
       std::forward<InnerMatcher>(inner_matcher));
 }
 
-// Matches a Status with the specified 'code' as error_code().
-// TODO(lizatretyakova): remove the static_cast and fix the comment above to
-// use code() after all StatusIs usages are migrated to use absl::StatusCode.
+// Matches a Status with the specified 'code' as code().
 MATCHER_P(StatusIs, code,
-          "is a Status with a " +
-              absl::StatusCodeToString(static_cast<absl::StatusCode>(code)) +
-              " code") {
-  if (arg.code() == static_cast<absl::StatusCode>(code)) {
+          "is a Status with a " + absl::StatusCodeToString(code) + " code") {
+  if (arg.code() == code) {
     return true;
   }
   *result_listener << ::testing::PrintToString(arg);
   return false;
 }
 
-// Matches a Status whose error_code() equals 'code', and whose
-// error_message() matches 'message_macher'.
-// TODO(lizatretyakova): remove the static_cast and fix the comment above to
-// use code() after all StatusIs usages are migrated to use absl::StatusCode.
+// Matches a Status whose code() equals 'code', and whose message() matches
+// 'message_macher'.
 MATCHER_P2(StatusIs, code, message_matcher, "") {
-  return (arg.code() == static_cast<absl::StatusCode>(code)) &&
+  return (arg.code() == code) &&
          testing::Matches(message_matcher)(std::string(arg.message()));
 }
 
diff --git a/cc/util/test_util.cc b/cc/util/test_util.cc
index ad6a6d9..2e58dae 100644
--- a/cc/util/test_util.cc
+++ b/cc/util/test_util.cc
@@ -16,15 +16,20 @@
 
 #include "tink/util/test_util.h"
 
-#include <fcntl.h>
 #include <stdarg.h>
 #include <stdlib.h>
-#include <unistd.h>
 
 #include <cmath>
 #include <cstdint>
 #include <cstdlib>
+#include <fstream>
+#include <ios>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <sstream>
 #include <string>
+#include <vector>
 
 #include "absl/memory/memory.h"
 #include "absl/status/status.h"
@@ -59,7 +64,6 @@
 using crypto::tink::util::Status;
 using google::crypto::tink::AesGcmKeyFormat;
 using google::crypto::tink::EcdsaPrivateKey;
-using google::crypto::tink::EcdsaSignatureEncoding;
 using google::crypto::tink::EciesAeadHkdfPrivateKey;
 using google::crypto::tink::Ed25519PrivateKey;
 using google::crypto::tink::Keyset;
@@ -69,80 +73,16 @@
 namespace tink {
 namespace test {
 
-int GetTestFileDescriptor(absl::string_view filename, int size,
-                          std::string* file_contents) {
-  (*file_contents) = subtle::Random::GetRandomBytes(size);
-  return GetTestFileDescriptor(filename, *file_contents);
-}
-
-int GetTestFileDescriptor(
-    absl::string_view filename, absl::string_view file_contents) {
-  std::string full_filename =
-      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
-  mode_t mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH;
-  int fd = open(full_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
-  if (fd == -1) {
-    std::clog << "Cannot create file " << full_filename
-              << " error: " << errno << std::endl;
+std::string ReadTestFile(absl::string_view filename) {
+  std::string full_filename = absl::StrCat(test::TmpDir(), "/", filename);
+  std::ifstream input_stream(full_filename, std::ios::binary);
+  if (!input_stream) {
+    std::clog << "Cannot open file " << full_filename << std::endl;
     exit(1);
   }
-  auto size = file_contents.size();
-  if (write(fd, file_contents.data(), size) != size) {
-    std::clog << "Failed to write " << size << " bytes to file "
-              << full_filename << " error: " << errno << std::endl;
-
-    exit(1);
-  }
-  close(fd);
-  fd = open(full_filename.c_str(), O_RDONLY);
-  if (fd == -1) {
-    std::clog << "Cannot re-open file " << full_filename
-              << " error: " << errno << std::endl;
-    exit(1);
-  }
-  return fd;
-}
-
-
-int GetTestFileDescriptor(absl::string_view filename) {
-  std::string full_filename =
-      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
-  mode_t mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH;
-  int fd = open(full_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
-  if (fd == -1) {
-    std::clog << "Cannot create file " << full_filename
-              << " error: " << errno << std::endl;
-    exit(1);
-  }
-  return fd;
-}
-
-std::string ReadTestFile(std::string filename) {
-  std::string full_filename =
-      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
-  int fd = open(full_filename.c_str(), O_RDONLY);
-  if (fd == -1) {
-    std::clog << "Cannot open file " << full_filename
-              << " error: " << errno << std::endl;
-    exit(1);
-  }
-  std::string contents;
-  int buffer_size = 128 * 1024;
-  auto buffer = absl::make_unique<uint8_t[]>(buffer_size);
-  int read_result = read(fd, buffer.get(), buffer_size);
-  while (read_result > 0) {
-    std::clog << "Read " << read_result << " bytes" << std::endl;
-    contents.append(reinterpret_cast<const char*>(buffer.get()), read_result);
-    read_result = read(fd, buffer.get(), buffer_size);
-  }
-  if (read_result < 0) {
-    std::clog << "Error reading file " << full_filename
-              << " error: " << errno << std::endl;
-    exit(1);
-  }
-  close(fd);
-  std::clog << "Read in total " << contents.length() << " bytes" << std::endl;
-  return contents;
+  std::stringstream buffer;
+  buffer << input_stream.rdbuf();
+  return buffer.str();
 }
 
 util::StatusOr<std::string> HexDecode(absl::string_view hex) {
@@ -184,24 +124,25 @@
 }
 
 std::string TmpDir() {
-  // The Bazel 'test' command sets TEST_TMPDIR.
-  const char* env = getenv("TEST_TMPDIR");
-  if (env && env[0] != '\0') {
-    return env;
+  // Try the following environment variables in order:
+  //  - TEST_TMPDIR: Set by `bazel test`.
+  //  - TMPDIR: Set by some Tink tests.
+  //  - TEMP, TMP: Set on Windows; they contain the tmp dir's path.
+  for (const std::string& tmp_env_variable :
+       {"TEST_TMPDIR", "TMPDIR", "TEMP", "TMP"}) {
+    const char* env = getenv(tmp_env_variable.c_str());
+    if (env && env[0] != '\0') {
+      return env;
+    }
   }
-  env = getenv("TMPDIR");
-  if (env && env[0] != '\0') {
-    return env;
-  }
+  // Tmp dir on Linux/macOS.
   return "/tmp";
 }
 
-void AddKeyData(
-    const google::crypto::tink::KeyData& key_data,
-    uint32_t key_id,
-    google::crypto::tink::OutputPrefixType output_prefix,
-    google::crypto::tink::KeyStatusType key_status,
-    google::crypto::tink::Keyset* keyset) {
+void AddKeyData(const google::crypto::tink::KeyData& key_data, uint32_t key_id,
+                google::crypto::tink::OutputPrefixType output_prefix,
+                google::crypto::tink::KeyStatusType key_status,
+                google::crypto::tink::Keyset* keyset) {
   Keyset::Key* key = keyset->add_key();
   key->set_output_prefix_type(output_prefix);
   key->set_key_id(key_id);
diff --git a/cc/util/test_util.h b/cc/util/test_util.h
index 10de4e3..cba8433 100644
--- a/cc/util/test_util.h
+++ b/cc/util/test_util.h
@@ -18,6 +18,8 @@
 #define TINK_UTIL_TEST_UTIL_H_
 
 #include <limits>
+#include <memory>
+#include <ostream>
 #include <string>
 #include <utility>
 
@@ -62,22 +64,8 @@
 // Various utilities for testing.
 ///////////////////////////////////////////////////////////////////////////////
 
-// Creates a new test file with the specified 'filename', writes 'size' random
-// bytes to the file, and returns a file descriptor for reading from the file.
-// A copy of the bytes written to the file is returned in 'file_contents'.
-int GetTestFileDescriptor(absl::string_view filename, int size,
-                          std::string* file_contents);
-
-// Creates a new test file with the specified 'filename', with contents from
-// 'file_contents', and returns a file descriptor for reading from the file.
-int GetTestFileDescriptor(absl::string_view filename,
-                          absl::string_view file_contents);
-
-// Creates a new test file with the specified 'filename', ready for writing.
-int GetTestFileDescriptor(absl::string_view filename);
-
-// Reads the test file specified by 'filename', and returns its contents.
-std::string ReadTestFile(std::string filename);
+// Reads the test file specified by `filename`, and returns its contents.
+std::string ReadTestFile(absl::string_view filename);
 
 // Converts a hexadecimal string into a string of bytes.
 // Returns a status if the size of the input is odd or if the input contains
@@ -493,40 +481,35 @@
     util::Status status_;
   };  // class DummyDecryptingStream
 
-  // Upon first call to PRead() tries to read from 'ct_source' a header
-  // that is expected to be equal to 'expected_header'.  If this
+  // Upon first call to PRead() tries to read from `ct_source` a header
+  // that is expected to be equal to `expected_header`.  If this
   // header matching succeeds, all subsequent method calls are forwarded
-  // to the corresponding methods of 'cd_source'.
+  // to `ct_source->PRead`.
   class DummyDecryptingRandomAccessStream
       : public crypto::tink::RandomAccessStream {
    public:
     DummyDecryptingRandomAccessStream(
         std::unique_ptr<crypto::tink::RandomAccessStream> ct_source,
         absl::string_view expected_header)
-        : ct_source_(std::move(ct_source)),
-          exp_header_(expected_header),
-          status_(util::Status(absl::StatusCode::kUnavailable,
-                               "not initialized")) {}
+        : ct_source_(std::move(ct_source)), exp_header_(expected_header) {}
 
     crypto::tink::util::Status PRead(
         int64_t position, int count,
         crypto::tink::util::Buffer* dest_buffer) override {
-      {  // Initialize, if not initialized yet.
-        absl::MutexLock lock(&status_mutex_);
-        if (status_.code() == absl::StatusCode::kUnavailable) Initialize();
-        if (!status_.ok()) return status_;
+      util::Status status = CheckHeader();
+      if (!status.ok()) {
+        return status;
       }
-      auto status = dest_buffer->set_size(0);
+      status = dest_buffer->set_size(0);
       if (!status.ok()) return status;
       return ct_source_->PRead(position + exp_header_.size(), count,
                                dest_buffer);
     }
 
     util::StatusOr<int64_t> size() override {
-      {  // Initialize, if not initialized yet.
-        absl::MutexLock lock(&status_mutex_);
-        if (status_.code() == absl::StatusCode::kUnavailable) Initialize();
-        if (!status_.ok()) return status_;
+      util::Status status = CheckHeader();
+      if (!status.ok()) {
+        return status;
       }
       auto ct_size_result = ct_source_->size();
       if (!ct_size_result.ok()) return ct_size_result.status();
@@ -536,25 +519,39 @@
     }
 
    private:
-    void Initialize() ABSL_EXCLUSIVE_LOCKS_REQUIRED(status_mutex_) {
+    util::Status CheckHeader()
+        ABSL_LOCKS_EXCLUDED(header_check_status_mutex_) {
+      absl::MutexLock lock(&header_check_status_mutex_);
+      if (header_check_status_.code() != absl::StatusCode::kUnavailable) {
+        return header_check_status_;
+      }
       auto buf = std::move(util::Buffer::New(exp_header_.size()).value());
-      status_ = ct_source_->PRead(0, exp_header_.size(), buf.get());
-      if (!status_.ok() && status_.code() != absl::StatusCode::kOutOfRange)
-        return;
+      header_check_status_ =
+          ct_source_->PRead(0, exp_header_.size(), buf.get());
+      if (!header_check_status_.ok() &&
+          header_check_status_.code() != absl::StatusCode::kOutOfRange) {
+        return header_check_status_;
+      }
+      // EOF or Ok indicate a valid read has happened.
+      header_check_status_ = util::OkStatus();
+      // Invalid header.
       if (buf->size() < exp_header_.size()) {
-        status_ = util::Status(absl::StatusCode::kInvalidArgument,
+        header_check_status_ = util::Status(absl::StatusCode::kInvalidArgument,
                                "Could not read header");
       } else if (memcmp(buf->get_mem_block(), exp_header_.data(),
                         static_cast<int>(exp_header_.size()))) {
-        status_ = util::Status(absl::StatusCode::kInvalidArgument,
+        header_check_status_ = util::Status(absl::StatusCode::kInvalidArgument,
                                "Corrupted header");
       }
+      return header_check_status_;
     }
 
     std::unique_ptr<crypto::tink::RandomAccessStream> ct_source_;
     std::string exp_header_;
-    mutable absl::Mutex status_mutex_;
-    util::Status status_ ABSL_GUARDED_BY(status_mutex_);
+    mutable absl::Mutex header_check_status_mutex_;
+    util::Status header_check_status_
+        ABSL_GUARDED_BY(header_check_status_mutex_) =
+            util::Status(absl::StatusCode::kUnavailable, "Uninitialized");
   };  // class DummyDecryptingRandomAccessStream
 
  private:
@@ -733,7 +730,7 @@
     return {absl::make_unique<DummyAead>(key_uri)};
   }
 
-  ~DummyKmsClient() override {}
+  ~DummyKmsClient() override = default;
 
  private:
   std::string uri_prefix_;
diff --git a/cc/util/test_util_test.cc b/cc/util/test_util_test.cc
index 4b8fdf9..34171c3 100644
--- a/cc/util/test_util_test.cc
+++ b/cc/util/test_util_test.cc
@@ -15,9 +15,24 @@
 ///////////////////////////////////////////////////////////////////////////////
 #include "tink/util/test_util.h"
 
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
+#include "tink/internal/test_random_access_stream.h"
+#include "tink/output_stream.h"
+#include "tink/random_access_stream.h"
 #include "tink/subtle/random.h"
+#include "tink/subtle/test_util.h"
+#include "tink/util/buffer.h"
+#include "tink/util/ostream_output_stream.h"
+#include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
 #include "proto/aes_gcm.pb.h"
 #include "proto/tink.pb.h"
@@ -27,6 +42,8 @@
 namespace test {
 namespace {
 
+using ::crypto::tink::internal::TestRandomAccessStream;
+using ::crypto::tink::test::StatusIs;
 using ::google::crypto::tink::AesGcmKey;
 using ::google::crypto::tink::KeyData;
 using ::testing::Eq;
@@ -107,6 +124,170 @@
       IsOk());
 }
 
+TEST(DummyStreamingAead, DummyDecryptingStreamPreadAllAtOnceSucceeds) {
+  const int stream_size = 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+
+  auto ostream = std::make_unique<std::ostringstream>();
+  auto string_stream_buffer = ostream->rdbuf();
+  auto output_stream =
+      std::make_unique<util::OstreamOutputStream>(std::move(ostream));
+
+  DummyStreamingAead streaming_aead("Some AEAD");
+  util::StatusOr<std::unique_ptr<OutputStream>> encrypting_output_stream =
+      streaming_aead.NewEncryptingStream(std::move(output_stream), "Some AAD");
+  ASSERT_THAT(encrypting_output_stream.status(), IsOk());
+  ASSERT_THAT(subtle::test::WriteToStream(
+                  encrypting_output_stream.value().get(), stream_content),
+              IsOk());
+
+  std::string ciphertext = string_stream_buffer->str();
+  auto test_random_access_stream =
+      std::make_unique<TestRandomAccessStream>(ciphertext);
+  util::StatusOr<std::unique_ptr<RandomAccessStream>>
+      decrypting_random_access_stream =
+          streaming_aead.NewDecryptingRandomAccessStream(
+              std::move(test_random_access_stream), "Some AAD");
+  ASSERT_THAT(decrypting_random_access_stream.status(), IsOk());
+
+  auto buffer = util::Buffer::New(ciphertext.size());
+  EXPECT_THAT((*decrypting_random_access_stream)
+                  ->PRead(/*position=*/0, ciphertext.size(), buffer->get()),
+              StatusIs(absl::StatusCode::kOutOfRange));
+  EXPECT_EQ(stream_content,
+            std::string((*buffer)->get_mem_block(), (*buffer)->size()));
+}
+
+TEST(DummyStreamingAead, DummyDecryptingStreamPreadInChunksSucceeds) {
+  const int stream_size = 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+
+  auto ostream = std::make_unique<std::ostringstream>();
+  auto string_stream_buffer = ostream->rdbuf();
+  auto output_stream =
+      std::make_unique<util::OstreamOutputStream>(std::move(ostream));
+
+  DummyStreamingAead streaming_aead("Some AEAD");
+  util::StatusOr<std::unique_ptr<OutputStream>> encrypting_output_stream =
+      streaming_aead.NewEncryptingStream(std::move(output_stream), "Some AAD");
+  ASSERT_THAT(encrypting_output_stream.status(), IsOk());
+  ASSERT_THAT(subtle::test::WriteToStream(
+                  encrypting_output_stream.value().get(), stream_content),
+              IsOk());
+
+  std::string ciphertext = string_stream_buffer->str();
+  auto test_random_access_stream =
+      std::make_unique<TestRandomAccessStream>(ciphertext);
+  util::StatusOr<std::unique_ptr<RandomAccessStream>>
+      decrypting_random_access_stream =
+          streaming_aead.NewDecryptingRandomAccessStream(
+              std::move(test_random_access_stream), "Some AAD");
+  ASSERT_THAT(decrypting_random_access_stream.status(), IsOk());
+
+  int chunk_size = 10;
+  auto buffer = util::Buffer::New(chunk_size);
+  std::string plaintext;
+  int64_t position = 0;
+  util::Status status = (*decrypting_random_access_stream)
+                            ->PRead(position, chunk_size, buffer->get());
+  while (status.ok()) {
+    plaintext.append((*buffer)->get_mem_block(), (*buffer)->size());
+    position += (*buffer)->size();
+    status = (*decrypting_random_access_stream)
+                 ->PRead(position, chunk_size, buffer->get());
+  }
+  EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange));
+  plaintext.append((*buffer)->get_mem_block(), (*buffer)->size());
+  EXPECT_EQ(stream_content, plaintext);
+}
+
+TEST(DummyStreamingAead, DummyDecryptingStreamPreadWithSmallerHeaderFails) {
+  const int stream_size = 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+
+  auto ostream = std::make_unique<std::ostringstream>();
+  auto output_stream =
+      std::make_unique<util::OstreamOutputStream>(std::move(ostream));
+
+  constexpr absl::string_view kStreamingAeadName = "Some AEAD";
+  constexpr absl::string_view kStreamingAeadAad = "Some associated data";
+
+  DummyStreamingAead streaming_aead(kStreamingAeadName);
+  util::StatusOr<std::unique_ptr<OutputStream>> encrypting_output_stream =
+      streaming_aead.NewEncryptingStream(std::move(output_stream),
+                                         kStreamingAeadAad);
+  ASSERT_THAT(encrypting_output_stream.status(), IsOk());
+  ASSERT_THAT(subtle::test::WriteToStream(
+                  encrypting_output_stream.value().get(), stream_content),
+              IsOk());
+  // Stream content size is too small; DummyDecryptingStream expects
+  // absl::StrCat(kStreamingAeadName, kStreamingAeadAad).
+  std::string ciphertext = "Invalid header";
+  auto test_random_access_stream =
+      std::make_unique<TestRandomAccessStream>(ciphertext);
+  util::StatusOr<std::unique_ptr<RandomAccessStream>>
+      decrypting_random_access_stream =
+          streaming_aead.NewDecryptingRandomAccessStream(
+              std::move(test_random_access_stream), kStreamingAeadAad);
+  ASSERT_THAT(decrypting_random_access_stream.status(), IsOk());
+
+  int chunk_size = 10;
+  auto buffer = util::Buffer::New(chunk_size);
+  EXPECT_THAT(
+      (*decrypting_random_access_stream)
+          ->PRead(/*position=*/0, chunk_size, buffer->get()),
+      StatusIs(absl::StatusCode::kInvalidArgument, "Could not read header"));
+  EXPECT_THAT(
+      (*decrypting_random_access_stream)
+          ->PRead(/*position=*/0, chunk_size, buffer->get()),
+      StatusIs(absl::StatusCode::kInvalidArgument, "Could not read header"));
+  EXPECT_THAT(
+      (*decrypting_random_access_stream)->size().status(),
+      StatusIs(absl::StatusCode::kInvalidArgument, "Could not read header"));
+}
+
+TEST(DummyStreamingAead, DummyDecryptingStreamPreadWithCorruptedAadFails) {
+  const int stream_size = 1024;
+  std::string stream_content = subtle::Random::GetRandomBytes(stream_size);
+
+  auto ostream = std::make_unique<std::ostringstream>();
+  auto string_stream_buffer = ostream->rdbuf();
+  auto output_stream =
+      std::make_unique<util::OstreamOutputStream>(std::move(ostream));
+
+  constexpr absl::string_view kStreamingAeadName = "Some AEAD";
+  constexpr absl::string_view kStreamingAeadAad = "Some associated data";
+
+  DummyStreamingAead streaming_aead(kStreamingAeadName);
+  util::StatusOr<std::unique_ptr<OutputStream>> encrypting_output_stream =
+      streaming_aead.NewEncryptingStream(std::move(output_stream),
+                                         kStreamingAeadAad);
+  ASSERT_THAT(encrypting_output_stream.status(), IsOk());
+  ASSERT_THAT(subtle::test::WriteToStream(
+                  encrypting_output_stream.value().get(), stream_content),
+              IsOk());
+  // Invalid associated data.
+  std::string ciphertext = string_stream_buffer->str();
+  auto test_random_access_stream =
+      std::make_unique<TestRandomAccessStream>(ciphertext);
+  util::StatusOr<std::unique_ptr<RandomAccessStream>>
+      decrypting_random_access_stream =
+          streaming_aead.NewDecryptingRandomAccessStream(
+              std::move(test_random_access_stream), "Some wrong AAD");
+  ASSERT_THAT(decrypting_random_access_stream.status(), IsOk());
+
+  int chunk_size = 10;
+  auto buffer = util::Buffer::New(chunk_size);
+  EXPECT_THAT((*decrypting_random_access_stream)
+                  ->PRead(/*position=*/0, chunk_size, buffer->get()),
+              StatusIs(absl::StatusCode::kInvalidArgument, "Corrupted header"));
+  EXPECT_THAT((*decrypting_random_access_stream)
+                  ->PRead(/*position=*/0, chunk_size, buffer->get()),
+              StatusIs(absl::StatusCode::kInvalidArgument, "Corrupted header"));
+  EXPECT_THAT((*decrypting_random_access_stream)->size().status(),
+              StatusIs(absl::StatusCode::kInvalidArgument, "Corrupted header"));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace tink
diff --git a/cc/util/validation_test.cc b/cc/util/validation_test.cc
index 1d5b01c..db7d2d2 100644
--- a/cc/util/validation_test.cc
+++ b/cc/util/validation_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/util/validation.h"
 
+#include <limits>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/status/status.h"
diff --git a/cc/version_script.lds b/cc/version_script.lds
index fbee2ab..6684cbf 100644
--- a/cc/version_script.lds
+++ b/cc/version_script.lds
@@ -1,4 +1,4 @@
-VERS_1.7.0 {
+VERS_2.0.0 {
   global:
     *tink*;
     *absl*;
diff --git a/cmake/HttpArchive.cmake b/cmake/HttpArchive.cmake
index d13a103..75fd0d3 100644
--- a/cmake/HttpArchive.cmake
+++ b/cmake/HttpArchive.cmake
@@ -12,21 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-include(ExternalProject)
+include(FetchContent)
 include(CMakeParseArguments)
 
-if (NOT DEFINED TINK_THIRD_PARTY_DIR)
-  set(TINK_THIRD_PARTY_DIR "${CMAKE_CURRENT_BINARY_DIR}/__third_party")
-endif()
-
 # Download, unpack and configure a dependency.
 #
 # The project is added as a subdirectory of Tink, unless DATA_ONLY is
 # specified. This makes all target defined by it available as dependencies.
 #
-# This rule also defines a <NAME>_SOURCE_DIR variable, which points to the
-# root directory of the downloaded package and can be used to reference data in
-# tests, or append extra include/link paths in the Workspace file.
+# This rule also defines two variables:
+#   - <NAME>_SOURCE_DIR points to the root directory of the downloaded package;
+#     it can be used to reference data in tests, or append extra include/link
+#     paths in the Workspace file.
+#   - <NAME>_BINARY_DIR points to the build directory.
 #
 # Parameters:
 #   NAME name of the dependency.
@@ -49,40 +47,29 @@
     "NAME;URL;SHA256;CMAKE_SUBDIR"
     "CMAKE_ARGS"
   )
-
+  FetchContent_Declare(
+    ${http_archive_NAME}
+    URL       ${http_archive_URL}
+    URL_HASH  SHA256=${http_archive_SHA256}
+  )
   message(STATUS "Fetching ${http_archive_NAME}")
-
-  set(http_archive_PREFIX "${TINK_THIRD_PARTY_DIR}/${http_archive_NAME}")
-  set(http_archive_SOURCE_DIR "${http_archive_PREFIX}/src")
-  set(http_archive_BINARY_DIR "${http_archive_PREFIX}/build")
-
-  configure_file(
-    cmake/HttpArchiveDownloader.cmake.in
-    "${http_archive_PREFIX}/CMakeLists.txt")
-
-  execute_process(
-    COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
-    RESULT_VARIABLE errors
-    WORKING_DIRECTORY "${http_archive_PREFIX}")
-
-  if (errors)
-    message(FATAL_ERROR "While configuring ${http_archive_NAME}: ${errors}")
-  endif()
-
-  set(${http_archive_NAME}_SOURCE_DIR "${http_archive_SOURCE_DIR}" PARENT_SCOPE)
-
-  execute_process(
-    COMMAND ${CMAKE_COMMAND} --build .
-    RESULT_VARIABLE errors
-    WORKING_DIRECTORY "${http_archive_PREFIX}")
-
-  if (errors)
-    message(FATAL_ERROR "While fetching ${http_archive_NAME}: ${errors}")
-  endif()
-
-  if (NOT http_archive_DATA_ONLY)
-    add_subdirectory(
-      "${http_archive_SOURCE_DIR}/${http_archive_CMAKE_SUBDIR}"
-      "${http_archive_BINARY_DIR}" EXCLUDE_FROM_ALL)
+  FetchContent_GetProperties(${http_archive_NAME})
+  if(NOT ${http_archive_NAME}_POPULATED)
+    FetchContent_Populate(${http_archive_NAME})
+    if (NOT http_archive_DATA_ONLY)
+      add_subdirectory(
+        ${${http_archive_NAME}_SOURCE_DIR}/${http_archive_CMAKE_SUBDIR}
+        ${${http_archive_NAME}_BINARY_DIR}
+        EXCLUDE_FROM_ALL)
+    endif()
+    # Expose these variables to the caller.
+    set(
+      "${http_archive_NAME}_SOURCE_DIR"
+      "${${http_archive_NAME}_SOURCE_DIR}"
+      PARENT_SCOPE)
+    set(
+      "${http_archive_NAME}_BINARY_DIR"
+      "${${http_archive_NAME}_BINARY_DIR}"
+      PARENT_SCOPE)
   endif()
 endfunction(http_archive)
diff --git a/cmake/HttpArchiveDownloader.cmake.in b/cmake/HttpArchiveDownloader.cmake.in
deleted file mode 100644
index 3aac815..0000000
--- a/cmake/HttpArchiveDownloader.cmake.in
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2019 Google LLC
-#
-# 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
-#
-# http://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.
-
-# This template is used by http_archive() to download, unpack and configure Tink
-# dependencies. You shouldn't need to use it directly.
-
-cmake_minimum_required(VERSION 3.5)
-project(http-archive-${http_archive_NAME})
-
-include(ExternalProject)
-
-ExternalProject_Add(${http_archive_NAME}
-  URL "${http_archive_URL}"
-  URL_HASH SHA256=${http_archive_SHA256}
-  TLS_VERIFY true
-  SOURCE_DIR "${http_archive_SOURCE_DIR}"
-  BINARY_DIR "${http_archive_BINARY_DIR}"
-  SOURCE_SUBDIR "${http_archive_CMAKE_SUBDIR}"
-  CMAKE_ARGS ${http_archive_CMAKE_ARGS}
-  CONFIGURE_COMMAND ""
-  BUILD_COMMAND ""
-  TEST_COMMAND ""
-  INSTALL_COMMAND ""
-  ${http_archive_EXTRA_OPTIONS}
-  EXCLUDE_FROM_ALL
-)
diff --git a/cmake/TinkBuildRules.cmake b/cmake/TinkBuildRules.cmake
index 2a6ef1f..9fd4328 100644
--- a/cmake/TinkBuildRules.cmake
+++ b/cmake/TinkBuildRules.cmake
@@ -43,7 +43,7 @@
 endif()
 
 if (NOT DEFINED TINK_CXX_STANDARD)
-  set(TINK_CXX_STANDARD 11)
+  set(TINK_CXX_STANDARD 14)
   if (DEFINED CMAKE_CXX_STANDARD_REQUIRED AND CMAKE_CXX_STANDARD_REQUIRED AND DEFINED CMAKE_CXX_STANDARD)
     set(TINK_CXX_STANDARD ${CMAKE_CXX_STANDARD})
   endif()
@@ -54,6 +54,7 @@
 set(TINK_IDE_FOLDER "Tink")
 
 set(TINK_TARGET_EXCLUDE_IF_OPENSSL "exclude_if_openssl")
+set(TINK_TARGET_EXCLUDE_IF_WINDOWS "exclude_if_windows")
 
 # Declare the beginning of a new Tink library namespace.
 #
@@ -74,10 +75,12 @@
 # a way to organise code and speed up compilation.
 #
 # Arguments:
-#   NAME base name of the target. See below for target naming conventions.
-#   SRCS list of source files, including headers.
-#   DEPS list of dependency targets.
-#   PUBLIC flag, signal that this target is intended for external use.
+#   NAME      base name of the target. See below for target naming conventions.
+#   SRCS      list of source files, including headers.
+#   DEPS      list of dependency targets.
+#   PUBLIC    flag, signals that this target is intended for external use.
+#   TESTONLY  flag, signals that this target should be ignored if
+#             TINK_BUILD_TESTS=OFF.
 #
 # If SRCS contains only headers, an INTERFACE rule is created. This rule carries
 # include path and link library information, but is not directly buildable.
@@ -94,22 +97,30 @@
 #
 function(tink_cc_library)
   cmake_parse_arguments(PARSE_ARGV 0 tink_cc_library
-    "PUBLIC"
+    "PUBLIC;TESTONLY"
     "NAME"
     "SRCS;DEPS;TAGS"
   )
 
+  if (tink_cc_library_TESTONLY AND NOT TINK_BUILD_TESTS)
+    return()
+  endif()
+
   if (NOT DEFINED TINK_MODULE)
     message(FATAL_ERROR
             "TINK_MODULE not defined, perhaps you are missing a tink_module() statement?")
   endif()
 
-  # Check if this target must be skipped. Currently the only reason for this to
-  # happen is incompatibility with OpenSSL, when used.
+  # Check if this target must be skipped.
   foreach(_tink_cc_library_tag ${tink_cc_library_TAGS})
+    # Exclude if we use OpenSSL.
     if (${_tink_cc_library_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_OPENSSL} AND TINK_USE_SYSTEM_OPENSSL)
       return()
     endif()
+    # Exclude if building on Windows.
+    if (${_tink_cc_library_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_WINDOWS} AND WIN32)
+      return()
+    endif()
   endforeach()
 
   # We replace :: with __ in targets, because :: may not appear in target names.
@@ -157,10 +168,10 @@
 # Declare a Tink test using googletest, with a syntax similar to Bazel.
 #
 # Parameters:
-#   NAME base name of the test.
-#   SRCS list of test source files, headers included.
-#   DEPS list of dependencies, see tink_cc_library above.
-#   DATA list of non-code dependencies, such as test vectors.
+#   NAME  base name of the test.
+#   SRCS  list of test source files, headers included.
+#   DEPS  list of dependencies, see tink_cc_library above.
+#   DATA  list of non-code dependencies, such as test vectors.
 #
 # Tests added with this macro are automatically registered.
 # Each test produces a build target named tink_test_<MODULE>_<NAME>.
@@ -180,12 +191,16 @@
     message(FATAL_ERROR "TINK_MODULE not defined")
   endif()
 
-  # Check if this target must be skipped. Currently the only reason for this to
-  # happen is incompatibility with OpenSSL, when used.
+  # Check if this target must be skipped.
   foreach(_tink_cc_test_tag ${tink_cc_test_TAGS})
+    # Exclude if we use OpenSSL.
     if (${_tink_cc_test_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_OPENSSL} AND TINK_USE_SYSTEM_OPENSSL)
       return()
     endif()
+    # Exclude if building on Windows.
+    if (${_tink_cc_test_tag} STREQUAL ${TINK_TARGET_EXCLUDE_IF_WINDOWS} AND WIN32)
+      return()
+    endif()
   endforeach()
 
   # We replace :: with __ in targets, because :: may not appear in target names.
@@ -276,8 +291,8 @@
 # to group dependencies that are logically related and give them a single name.
 #
 # Parameters:
-#   NAME base name of the target.
-#   DEPS list of dependencies to group.
+#   NAME  base name of the target.
+#   DEPS  list of dependencies to group.
 #
 # Each tink_target_group produces a target named tink_<MODULE>_<NAME>.
 function(tink_target_group)
diff --git a/cmake/TinkWorkspace.cmake b/cmake/TinkWorkspace.cmake
index 2404a5d..f871893 100644
--- a/cmake/TinkWorkspace.cmake
+++ b/cmake/TinkWorkspace.cmake
@@ -50,27 +50,40 @@
 
 set(gtest_force_shared_crt ON CACHE BOOL "Tink dependency override" FORCE)
 
-if (NOT TINK_USE_INSTALLED_GOOGLETEST)
+if (TINK_BUILD_TESTS)
+  if (TINK_USE_INSTALLED_GOOGLETEST)
+    # This uses the CMake's FindGTest module; if successful, this call to
+    # find_package generates the targets GTest::gmock, GTest::gtest and
+    # GTest::gtest_main.
+    find_package(GTest CONFIG REQUIRED)
+    _create_interface_target(gmock GTest::gmock)
+    _create_interface_target(gtest_main GTest::gtest_main)
+  else()
+    http_archive(
+      NAME googletest
+      URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz
+      SHA256 b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5
+    )
+  endif()
+
   http_archive(
-    NAME com_google_googletest
-    URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz
-    SHA256 b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5
+    NAME wycheproof
+    URL https://github.com/google/wycheproof/archive/d8ed1ba95ac4c551db67f410c06131c3bc00a97c.zip
+    SHA256 eb1d558071acf1aa6d677d7f1cabec2328d1cf8381496c17185bd92b52ce7545
+    DATA_ONLY
   )
-else()
-  # This uses the CMake's FindGTest module; if successful, this call to
-  # find_package generates the targets GTest::gmock, GTest::gtest and
-  # GTest::gtest_main.
-  find_package(GTest CONFIG REQUIRED)
-  _create_interface_target(gmock GTest::gmock)
-  _create_interface_target(gtest_main GTest::gtest_main)
+  # Symlink the Wycheproof test data.
+  # Tests expect Wycheproof test vectors to be in a local testvectors/ folder.
+  add_directory_alias("${wycheproof_SOURCE_DIR}/testvectors"
+    "${CMAKE_BINARY_DIR}/testvectors")
 endif()
 
 if (NOT TINK_USE_INSTALLED_ABSEIL)
-  # Commit from 2021-12-03
+  # Release from 2023-05-04.
   http_archive(
-    NAME com_google_absl
-    URL https://github.com/abseil/abseil-cpp/archive/9336be04a242237cd41a525bedfcf3be1bb55377.zip
-    SHA256 368be019fc8d69a566ac2cf7a75262d5ba8f6409e3ef3cdbcf0106bdeb32e91c
+    NAME abseil
+    URL https://github.com/abseil/abseil-cpp/archive/refs/tags/20230125.3.zip
+    SHA256 51d676b6846440210da48899e4df618a357e6e44ecde7106f1e44ea16ae8adc7
   )
 else()
   # This is everything that needs to be done here. Abseil already defines its
@@ -78,31 +91,32 @@
   find_package(absl REQUIRED)
 endif()
 
-http_archive(
-  NAME wycheproof
-  URL https://github.com/google/wycheproof/archive/d8ed1ba95ac4c551db67f410c06131c3bc00a97c.zip
-  SHA256 eb1d558071acf1aa6d677d7f1cabec2328d1cf8381496c17185bd92b52ce7545
-  DATA_ONLY
-)
-
-# Symlink the Wycheproof test data.
-# Paths are hard-coded in tests, which expects wycheproof/ in this location.
-add_directory_alias("${wycheproof_SOURCE_DIR}" "${CMAKE_BINARY_DIR}/external/wycheproof")
-
-if (NOT TINK_USE_SYSTEM_OPENSSL)
-  http_archive(
-    NAME boringssl
-    URL https://github.com/google/boringssl/archive/88cdf7dd2dbce1ecb9057c183095103d83373abe.zip
-    SHA256 24092815136f956069fcfa5172166ad4e025166ce6fe500420c9e3e3c4f3da38
-    CMAKE_SUBDIR src
-  )
-
-  # BoringSSL targets do not carry include directory info, this fixes it.
-  target_include_directories(crypto PUBLIC "${boringssl_SOURCE_DIR}/src/include")
+# Don't fetch BoringSSL or look for OpenSSL if target `crypto` is already
+# defined.
+if (NOT TARGET crypto)
+  if (NOT TINK_USE_SYSTEM_OPENSSL)
+    # Commit from Feb 15, 2023.
+    # NOTE: This is one commit ahead of Bazel; the commit fixes a CMake issue,
+    # which made build fail on CMake 3.10.
+    # See https://github.com/google/boringssl/compare/5c22014...e27ff0e.
+    http_archive(
+      NAME boringssl
+      URL https://github.com/google/boringssl/archive/e27ff0e4312c91357778b36bbd8a7ec7bfc67be3.zip
+      SHA256 11d3c87906bed215a915b0db11cefd0fc7b939ddbec4952a29e343a83ce3bc50
+      CMAKE_SUBDIR src
+    )
+    # BoringSSL targets do not carry include directory info, this fixes it.
+    target_include_directories(crypto PUBLIC
+      "$<BUILD_INTERFACE:${boringssl_SOURCE_DIR}/src/include>")
+  else()
+    # Support for ED25519 was added from 1.1.1.
+    find_package(OpenSSL 1.1.1 REQUIRED)
+    _create_interface_target(crypto OpenSSL::Crypto)
+  endif()
 else()
-  # Support for ED25519 was added from 1.1.1.
-  find_package(OpenSSL 1.1.1 REQUIRED)
-  _create_interface_target(crypto OpenSSL::Crypto)
+  message(STATUS "Using an already declared `crypto` target")
+  get_target_property(crypto_INCLUDE_DIR crypto INTERFACE_INCLUDE_DIRECTORIES)
+  message(STATUS "crypto Include Dir: ${crypto_INCLUDE_DIR}")
 endif()
 
 set(RAPIDJSON_BUILD_DOC OFF CACHE BOOL "Tink dependency override" FORCE)
@@ -114,17 +128,16 @@
   URL https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz
   SHA256 bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e
 )
-
 # Rapidjson is a header-only library with no explicit target. Here we create one.
 add_library(rapidjson INTERFACE)
 target_include_directories(rapidjson INTERFACE "${rapidjson_SOURCE_DIR}")
 
 set(protobuf_BUILD_TESTS OFF CACHE BOOL "Tink dependency override" FORCE)
 set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "Tink dependency override" FORCE)
-
+## Use protobuf X.21.9.
 http_archive(
   NAME com_google_protobuf
-  URL https://github.com/protocolbuffers/protobuf/archive/v3.19.3.zip
-  SHA256 6b6bf5cd8d0cca442745c4c3c9f527c83ad6ef35a405f64db5215889ac779b42
+  URL https://github.com/protocolbuffers/protobuf/archive/v21.9.zip
+  SHA256 5babb8571f1cceafe0c18e13ddb3be556e87e12ceea3463d6b0d0064e6cc1ac3
   CMAKE_SUBDIR cmake
 )
diff --git a/docs/CMAKE-HOWTO.md b/docs/CMAKE-HOWTO.md
index 91e30eb..95d5664 100644
--- a/docs/CMAKE-HOWTO.md
+++ b/docs/CMAKE-HOWTO.md
@@ -13,10 +13,10 @@
 source tree has been copied in the `third_party/tink` directory of your project,
 your top-level CMake script should look like this:
 
-    cmake_minimum_required(VERSION 3.5)
+    cmake_minimum_required(VERSION 3.13)
     project(YourProject CXX)
     set(CMAKE_CXX_STANDARD_REQUIRED ON)
-    set(CMAKE_CXX_STANDARD 11)
+    set(CMAKE_CXX_STANDARD 14)
 
     add_subdirectory(third_party/tink)
 
@@ -25,9 +25,9 @@
 
 NOTES:
 
-*   You need at least CMake 3.5 to build Tink and its dependencies.
+*   You need at least CMake 3.13 to build Tink and its dependencies.
 *   Tink defines the C++ standard to use via the `TINK_CXX_STANDARD` variable,
-    which is `11` by default. If you want to propagate to the value of
+    which is `14` by default. If you want to propagate to the value of
     `CMAKE_CXX_STANDARD` to Tink use `set(CMAKE_CXX_STANDARD_REQUIRED ON)`.
 
 Include Tink headers in `your_app.cc` as follows:
diff --git a/docs/CPP-HOWTO.md b/docs/CPP-HOWTO.md
index 7e237c3..dac6f46 100644
--- a/docs/CPP-HOWTO.md
+++ b/docs/CPP-HOWTO.md
@@ -11,9 +11,9 @@
 
 ### Bazel
 
-Using Tink in projects built with Bazel is straightforward and is the recommended
-approach. For reference, see [the C++
-examples](https://github.com/google/tink/tree/master/examples/cc).
+Using Tink in projects built with Bazel is straightforward and is the
+recommended approach. For reference, see
+[the C++ examples](https://github.com/google/tink/tree/master/cc/examples).
 
 ### CMake
 
diff --git a/docs/GOLANG-HOWTO.md b/docs/GOLANG-HOWTO.md
index 8f25527..15b8935 100644
--- a/docs/GOLANG-HOWTO.md
+++ b/docs/GOLANG-HOWTO.md
@@ -115,36 +115,37 @@
 )
 
 func main() {
-  // Generate a new key.
-  kh1, err := keyset.NewHandle(aead.AES128GCMKeyTemplate())
+  // Generate a new keyset handle.
+  handle1, err := keyset.NewHandle(aead.AES128GCMKeyTemplate())
   if err != nil {
     log.Fatal(err)
   }
 
-  // Fetch the master key from a KMS.
+  // Get the key encryption AEAD from a KMS.
   gcpClient, err := gcpkms.NewClientWithCredentials(keyURI, credentialsPath)
   if err != nil {
     log.Fatal(err)
   }
   registry.RegisterKMSClient(gcpClient)
-  masterKey, err := gcpClient.GetAEAD(keyURI)
+  keyEncryptionAEAD, err := gcpClient.GetAEAD(keyURI)
   if err != nil {
     log.Fatal(err)
   }
 
-  // An io.Reader and io.Writer implementation which simply writes to memory.
-  memKeyset := &keyset.MemReaderWriter{}
-
-  // Write encrypts the keyset handle with the master key and writes to the
-  // io.Writer implementation (memKeyset). We recommend that you encrypt the
-  // keyset handle before persisting it.
-  if err := kh1.Write(memKeyset, masterKey); err != nil {
+  // Serialize and encrypt the keyset handle using the key encryption AEAD.
+  // We strongly recommend that you encrypt the keyset handle before persisting
+  // it.
+  buf := new(bytes.Buffer)
+  writer := keyset.NewBinaryWriter(buf)
+  err = handle1.Write(writer, keyEncryptionAEAD)
+  if err != nil {
     log.Fatal(err)
   }
+  encryptedHandle := buf.Bytes()
 
-  // Read reads the encrypted keyset handle back from the io.Reader
-  // implementation and decrypts it using the master key.
-  kh2, err := keyset.Read(memKeyset, masterKey)
+  // Decrypt and parse the encrypted keyset using the key encryption AEAD.
+  reader := keyset.NewBinaryReader(bytes.NewReader(encryptedHandle))
+  handle2, err := keyset.Read(reader, keyEncryptionAEAD)
   if err != nil {
     log.Fatal(err)
   }
diff --git a/docs/JAVA-HOWTO.md b/docs/JAVA-HOWTO.md
index b502318..21056a0 100644
--- a/docs/JAVA-HOWTO.md
+++ b/docs/JAVA-HOWTO.md
@@ -1,134 +1,23 @@
 # Tink for Java HOW-TO
 
 This document contains instructions and Java code snippets for common tasks in
-[Tink](https://github.com/google/tink).
+[Tink](https://github.com/tink-crypto/tink-java).
 
 If you want to contribute code to the Java implementation, please read the [Java
 hacking guide](JAVA-HACKING.md).
 
 ## Setup instructions
 
-The most recent release is
-[1.7.0](https://github.com/google/tink/releases/tag/v1.7.0), released
-2022-08-09.
-
-In addition to the versioned releases, snapshots of Tink are regularly built
-using the master branch of the Tink GitHub repository.
-
-Tink for Java has two primary build targets specified:
-
-- "tink": the default, for general purpose use
-- "android": which is optimized for use in Android projects
-
-### Maven
-
-You can can include Tink in Java projects projects using
-[Maven](https://maven.apache.org/).
-
-The Maven group ID is `com.google.crypto.tink`, and the artifact ID is `tink`.
-
-You can specify the current release of Tink as a project dependency using the
-following configuration:
-
-```xml
-<dependencies>
-  <dependency>
-    <groupId>com.google.crypto.tink</groupId>
-    <artifactId>tink</artifactId>
-    <version>1.7.0</version>
-  </dependency>
-</dependencies>
-```
-
-You can specify the latest snapshot as a project dependency by using the version
-`HEAD-SNAPSHOT`:
-
-```xml
-<repositories>
-  <repository>
-    <id>sonatype-snapshots</id>
-    <name>sonatype-snapshots</name>
-    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
-    <snapshots>
-      <enabled>true</enabled>
-      <updatePolicy>always</updatePolicy>
-    </snapshots>
-    <releases>
-      <updatePolicy>always</updatePolicy>
-    </releases>
-  </repository>
-</repositories>
-
-...
-
-<dependencies>
-  <dependency>
-    <groupId>com.google.crypto.tink</groupId>
-    <artifactId>tink</artifactId>
-    <version>HEAD-SNAPSHOT</version>
-  </dependency>
-</dependencies>
-```
-
-### AWS/GCP integration
-
-Since 1.3.0 the support for AWS/GCP KMS has been moved to a separate package. To
-use AWS KMS, one should also add dependency on `tink-awskms`, and similarly
-`tink-gcpkms` for GCP KMS.
-
-```xml
-<dependencies>
-  <dependency>
-    <groupId>com.google.crypto.tink</groupId>
-    <artifactId>tink-awskms</artifactId>
-    <version>1.7.0</version>
-  </dependency>
-</dependencies>
-```
-
-```xml
-<dependencies>
-  <dependency>
-    <groupId>com.google.crypto.tink</groupId>
-    <artifactId>tink-gcpkms</artifactId>
-    <version>1.7.0</version>
-  </dependency>
-</dependencies>
-```
-
-### Gradle
-
-You can include Tink in Android projects using [Gradle](https://gradle.org).
-
-You can specify the current release of Tink as a project dependency using the
-following configuration:
-
-```
-dependencies {
-  implementation 'com.google.crypto.tink:tink-android:1.7.0'
-}
-```
-
-You can specify the latest snapshot as a project dependency using the following
-configuration:
-
-```
-repositories {
-    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
-}
-
-dependencies {
-  implementation 'com.google.crypto.tink:tink-android:HEAD-SNAPSHOT'
-}
-```
+See https://developers.devsite.corp.google.com/tink/tink-setup#java for setup
+instructions.
 
 ## API documentation
 
 *   Java:
-    *   [1.7.0](https://google.github.io/tink/javadoc/tink/1.7.0)
+    *   [1.9.0](https://google.github.io/tink/javadoc/tink/1.9.0)
     *   [HEAD-SNAPSHOT](https://google.github.io/tink/javadoc/tink/HEAD-SNAPSHOT)
 *   Android:
-    *   [1.7.0](https://google.github.io/tink/javadoc/tink-android/1.7.0)
+    *   [1.9.0](https://google.github.io/tink/javadoc/tink-android/1.9.0)
     *   [HEAD-SNAPSHOT](https://google.github.io/tink/javadoc/tink-android/HEAD-SNAPSHOT)
 
 ## Important warnings
@@ -188,97 +77,48 @@
 
 Still, if there is a need to generate a KeysetHandle with fresh key material
 directly in Java code, you can use
-[`KeysetHandle`](https://github.com/google/tink/blob/master/java_src/src/main/java/com/google/crypto/tink/KeysetHandle.java).
+[`KeysetHandle`](https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/KeysetHandle.java).
 For example, you can generate a keyset containing a randomly generated
 AES128-GCM key as follows.
 
 ```java
-    import com.google.crypto.tink.KeyTemplates;
     import com.google.crypto.tink.KeysetHandle;
+    import com.google.crypto.tink.aead.PredefinedAeadParameters;
 
     KeysetHandle keysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("AES128_GCM"));
+        PredefinedAeadParameters.AES128_GCM);
 ```
 
-## Storing keysets
+## Serializing keysets
 
-After generating key material, you might want to persist it to a storage system,
-e.g., writing to a file:
+After generating key material, you might want to serialize it in order to
+persist it to a storage system, e.g., writing to a file.
 
 ```java
-    import com.google.crypto.tink.CleartextKeysetHandle;
+    import com.google.crypto.tink.InsecureSecretKeyAccess;
     import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
-    import com.google.crypto.tink.JsonKeysetWriter;
-    import java.io.File;
+    import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
+    import com.google.crypto.tink.aead.PredefinedAeadParameters;
+    import java.nio.Files;
 
     // Generate the key material...
     KeysetHandle keysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("AES128_GCM"));
+        PredefinedAeadParameters.AES128_GCM);
 
-    // and write it to a file.
+    // and serialize it to a string.
     String keysetFilename = "my_keyset.json";
-    CleartextKeysetHandle.write(keysetHandle, JsonKeysetWriter.withFile(
-        new File(keysetFilename)));
+    String serializedKeyset =
+        TinkJsonProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get());
 ```
 
-Storing cleartext keysets on disk is not recommended. Tink supports encrypting
-keysets with master keys stored in remote [key management
-systems](KEY-MANAGEMENT.md).
+Parsing can be done with `TinkJsonProtoKeysetFormat.parseKeyset`. If the keyset
+has no secret key material, the method `serializeKeysetWithoutSecret` can be
+used (which does not require `InsecureSecretKeyAccess`).
 
-For example, you can encrypt the key material with a key stored in Google Cloud
-KMS key as follows:
-
-```java
-    import com.google.crypto.tink.JsonKeysetWriter;
-    import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
-    import com.google.crypto.tink.integration.gcpkms.GcpKmsClient;
-    import java.io.File;
-
-    // Generate the key material...
-    KeysetHandle keysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("AES128_GCM"));
-
-    // and write it to a file...
-    String keysetFilename = "my_keyset.json";
-    // encrypted with the this key in GCP KMS
-    String masterKeyUri = "gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar";
-    keysetHandle.write(JsonKeysetWriter.withFile(new File(keysetFilename)),
-        new GcpKmsClient().getAead(masterKeyUri));
-```
-
-## Loading existing keysets
-
-To load encrypted keysets, use
-[`KeysetHandle`](https://github.com/google/tink/blob/master/java_src/src/main/java/com/google/crypto/tink/KeysetHandle.java):
-
-```java
-    import com.google.crypto.tink.JsonKeysetReader;
-    import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.integration.awskms.AwsKmsClient;
-    import java.io.File;
-
-    String keysetFilename = "my_keyset.json";
-    // The keyset is encrypted with the this key in AWS KMS.
-    String masterKeyUri = "aws-kms://arn:aws:kms:us-east-1:007084425826:key/84a65985-f868-4bfc-83c2-366618acf147";
-    KeysetHandle keysetHandle = KeysetHandle.read(
-        JsonKeysetReader.withFile(new File(keysetFilename)),
-        new AwsKmsClient().getAead(masterKeyUri));
-```
-
-To load cleartext keysets, use
-[`CleartextKeysetHandle`](https://github.com/google/tink/blob/master/java_src/src/main/java/com/google/crypto/tink/CleartextKeysetHandle.java):
-
-```java
-    import com.google.crypto.tink.CleartextKeysetHandle;
-    import com.google.crypto.tink.KeysetHandle;
-    import java.io.File;
-
-    String keysetFilename = "my_keyset.json";
-    KeysetHandle keysetHandle = CleartextKeysetHandle.read(
-        JsonKeysetReader.withFile(new File(keysetFilename)));
-```
+Storing keysets unencrypted on disk is not recommended. Tink supports encrypting
+keysets with master keys stored in remote key management systems, see for
+example
+https://developers.devsite.corp.google.com/tink/client-side-encryption#java.
 
 ## Obtaining and using primitives
 
@@ -305,12 +145,11 @@
 
 ```java
     import com.google.crypto.tink.Aead;
-    import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
+    import com.google.crypto.tink.aead.PredefinedAeadParameters;
 
     // 1. Generate the key material.
     KeysetHandle keysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("AES128_GCM"));
+        PredefinedAeadParameters.AES128_GCM);
 
     // 2. Get the primitive.
     Aead aead = keysetHandle.getPrimitive(Aead.class);
@@ -330,13 +169,12 @@
 encrypt or decrypt data:
 
 ```java
-    import com.google.crypto.tink.DeterministicAead;
+    import com.google.crypto.tink.daead.PredefinedDeterministicAeadParameters;
     import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
 
     // 1. Generate the key material.
     KeysetHandle keysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("AES256_SIV"));
+        PredefinedDeterministicAeadParameters.AES256_SIV);
 
     // 2. Get the primitive.
     DeterministicAead daead =
@@ -351,160 +189,21 @@
 
 ### Symmetric key encryption of streaming data
 
-You can obtain and use a
-[Streaming AEAD](PRIMITIVES.md#streaming-authenticated-encryption-with-associated-data)
-(Streaming Authenticated Encryption with Associated Data) primitive to encrypt
-or decrypt data streams:
-
-```java
-    import com.google.crypto.tink.StreamingAead;
-    import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
-    import java.nio.ByteBuffer;
-    import java.nio.channels.FileChannel;
-    import java.nio.channels.SeekableByteChannel;
-    import java.nio.channels.WritableByteChannel;
-
-    // 1. Generate the key material.
-    KeysetHandle keysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("AES128_GCM_HKDF_1MB"));
-
-    // 2. Get the primitive.
-    StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
-
-    // 3. Use the primitive to encrypt some data and write the ciphertext to a file,
-    FileChannel ciphertextDestination =
-        new FileOutputStream(ciphertextFileName).getChannel();
-    byte[] aad = ...
-    WritableByteChannel encryptingChannel =
-        streamingAead.newEncryptingChannel(ciphertextDestination, aad);
-    ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
-    while ( bufferContainsDataToEncrypt ) {
-      int r = encryptingChannel.write(buffer);
-      // Try to get into buffer more data for encryption.
-    }
-    // Complete the encryption (process the remaining plaintext, if any, and close the channel).
-    encryptingChannel.close();
-
-    // ... or to decrypt an existing ciphertext stream.
-    FileChannel ciphertextSource =
-        new FileInputStream(ciphertextFileName).getChannel();
-    byte[] aad = ...
-    ReadableByteChannel decryptingChannel =
-        s.newDecryptingChannel(ciphertextSource, aad);
-    ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
-    do {
-      buffer.clear();
-      int cnt = decryptingChannel.read(buffer);
-      if (cnt > 0) {
-        // Process cnt bytes of plaintext.
-      } else if (read == -1) {
-        // End of plaintext detected.
-        break;
-      } else if (read == 0) {
-        // No ciphertext is available at the moment.
-      }
-   }
-```
+See
+https://developers.devsite.corp.google.com/tink/encrypt-large-files-or-data-streams#java
 
 ### Message Authentication Code
 
-You can compute or verify a [MAC](PRIMITIVES.md#message-authentication-code)
-(Message Authentication Code):
-
-```java
-    import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
-    import com.google.crypto.tink.Mac;
-
-    // 1. Generate the key material.
-    KeysetHandle keysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("HMAC_SHA256_128BITTAG"));
-
-    // 2. Get the primitive.
-    Mac mac = keysetHandle.getPrimitive(Mac.class);
-
-    // 3. Use the primitive to compute a tag,
-    byte[] tag = mac.computeMac(data);
-
-    // ... or to verify a tag.
-    mac.verifyMac(tag, data);
-```
+See
+https://developers.devsite.corp.google.com/tink/protect-data-from-tampering#java
 
 ### Digital signatures
 
-You can sign or verify a [digital
-signature](PRIMITIVES.md#digital-signatures):
-
-```java
-    import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
-    import com.google.crypto.tink.PublicKeySign;
-    import com.google.crypto.tink.PublicKeyVerify;
-
-    // SIGNING
-
-    // 1. Generate the private key material.
-    KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("ECDSA_P256"));
-
-    // 2. Get the primitive.
-    PublicKeySign signer = privateKeysetHandle.getPrimitive(PublicKeySign.class);
-
-    // 3. Use the primitive to sign.
-    byte[] signature = signer.sign(data);
-
-    // VERIFYING
-
-    // 1. Obtain a handle for the public key material.
-    KeysetHandle publicKeysetHandle =
-        privateKeysetHandle.getPublicKeysetHandle();
-
-    // 2. Get the primitive.
-    PublicKeyVerify verifier = publicKeysetHandle.getPrimitive(PublicKeyVerify.class);
-
-    // 4. Use the primitive to verify.
-    verifier.verify(signature, data);
-```
+See https://developers.devsite.corp.google.com/tink/digitally-sign-data
 
 ### Hybrid encryption
 
-To encrypt or decrypt using [a combination of public key encryption and
-symmetric key encryption](PRIMITIVES.md#hybrid-encryption) one can
-use the following:
-
-```java
-    import com.google.crypto.tink.HybridDecrypt;
-    import com.google.crypto.tink.HybridEncrypt;
-    import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.KeyTemplates;
-
-    // 1. Generate the private key material.
-    KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(
-        KeyTemplates.get("ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_GCM"));
-
-    // Obtain the public key material.
-    KeysetHandle publicKeysetHandle =
-        privateKeysetHandle.getPublicKeysetHandle();
-
-    // ENCRYPTING
-
-    // 2. Get the primitive.
-    HybridEncrypt hybridEncrypt =
-        publicKeysetHandle.getPrimitive(HybridEncrypt.class);
-
-    // 3. Use the primitive.
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
-
-    // DECRYPTING
-
-    // 2. Get the primitive.
-    HybridDecrypt hybridDecrypt = privateKeysetHandle.getPrimitive(
-        HybridDecrypt.class);
-
-    // 3. Use the primitive.
-    byte[] plaintext = hybridDecrypt.decrypt(ciphertext, contextInfo);
-```
+See https://developers.devsite.corp.google.com/tink/exchange-data#java
 
 ### Envelope encryption
 
@@ -545,33 +244,29 @@
 ## Key rotation
 
 Support for key rotation in Tink is provided via the
-[`KeysetManager`](https://github.com/google/tink/blob/master/java_src/src/main/java/com/google/crypto/tink/KeysetManager.java)
+[`KeysetHandle.Builder`](https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/KeysetHandle.java)
 class.
 
 You have to provide a `KeysetHandle`-object that contains the keyset that should
 be rotated, and a specification of the new key via a
-[`KeyTemplate`](https://github.com/google/tink/blob/master/proto/tink.proto#L50)
-message.
+[`Parameters`](https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/Parameters.java)
+object.
 
 ```java
-    import com.google.crypto.tink.KeyTemplate;
-    import com.google.crypto.tink.KeyTemplates;
     import com.google.crypto.tink.KeysetHandle;
     import com.google.crypto.tink.KeysetManager;
 
     KeysetHandle keysetHandle = ...;   // existing keyset
-    KeyTemplate keyTemplate = KeyTemplates.get("AES256_GCM"); // template for the new key
-
-    KeysetHandle rotatedKeysetHandle = KeysetManager
-        .withKeysetHandle(keysetHandle)
-        .rotate(keyTemplate)
-        .getKeysetHandle();
+    KeysetHandle.Builder builder = KeysetHandle.newBuilder(keysetHandle);
+    builder.addEntry(KeysetHandle.generateEntryFromParameters(
+      ChaCha20Poly1305Parameters.create()).withRandomId());
+    KeysetHandle keysetHandleWithAdditionalEntry = builder.build();
 ```
 
 After a successful rotation, the resulting keyset contains a new key generated
-according to the specification in `keyTemplate`, and the new key becomes the
-_primary key_ of the keyset.  For the rotation to succeed the `Registry` must
-contain a key manager for the key type specified in `keyTemplate`.
+according to the specification in the parameters object. For the rotation to
+succeed the `Registry` must contain a key manager for the key type specified in
+`keyTemplate`.
 
 Alternatively, you can use [Tinkey](TINKEY.md) to rotate or manage a keyset.
 
diff --git a/docs/JWT-HOWTO.md b/docs/JWT-HOWTO.md
index fdb947e..417c0ec 100644
--- a/docs/JWT-HOWTO.md
+++ b/docs/JWT-HOWTO.md
@@ -170,7 +170,6 @@
 
 Here are some small examples on how to use Tink's JWT library:
 
-* [examples/cc/jwt](https://github.com/google/tink/tree/master/examples/cc/jwt)
-* [examples/java_src/jwt](https://github.com/google/tink/tree/master/examples/java_src/jwt)
-* [examples/python/jwt](https://github.com/google/tink/tree/master/examples/python/jwt)
-
+* [examples/cc/jwt](https://github.com/google/tink/tree/master/cc/examples/jwt)
+* [examples/java_src/jwt](https://github.com/google/tink/tree/master/java_src/examples/jwt)
+* [examples/python/jwt](https://github.com/google/tink/tree/master/python/examples/jwt)
diff --git a/docs/KNOWN-ISSUES.md b/docs/KNOWN-ISSUES.md
index 7378857..3bbe819 100644
--- a/docs/KNOWN-ISSUES.md
+++ b/docs/KNOWN-ISSUES.md
@@ -1,88 +1,3 @@
 # Known Issues in Tink
 
-This doc lists known issues in Tink. Please report new issues by opening new
-tickets or emailing the maintainers at `tink-users@googlegroups.com`.
-
-## C++
-
-*   Before 1.4.0, AES-CTR-HMAC-AEAD keys and the
-    [EncryptThenAuthenticate](https://github.com/google/tink/blob/master/cc/subtle/encrypt_then_authenticate.cc)
-    subtle implementation may be vulnerable to chosen-ciphertext attacks. An
-    attacker can generate ciphertexts that bypass the HMAC verification if and
-    only if all of the following conditions are true:
-
-    -   Tink C++ is used on systems where `size_t` is a 32-bit integer. This is
-        usually the case on 32-bit machines.
-    -   The attacker can specify long (>= 2^29 bytes ~ 536MB) associated data.
-
-    This issue was reported by Quan Nguyen of Snap security team.
-
-## Java
-
-*   Tink supports Java 8 or newer. Java 7 support was removed since 1.4.0.
-
-*   Tink is built on top of Java security providers, but, via
-    [Project Wycheproof](https://github.com/google/wycheproof), we found many
-    security issues in popular providers. Tink provides countermeasures for most
-    problems, and we've also helped upstream fix many issues. Still, there are
-    some issues in old providers that we cannot fix. We recommend using Tink
-    with the latest version of Conscrypt, Oracle JDK, OpenJDK or Bouncy Castle.
-    If you cannot use the latest version, you might want to avoid using ECDSA
-    (alternative: ED25519) or AES-GCM (alternatives: AES-EAX, AES-CTR-HMAC-AEAD
-    or XChaCha20-Poly1305).
-
-## Android
-
-*   The minimum API level that Tink supports is 19 (Android KitKat). This covers
-    more than 90% of all Android phones. Tink hasn't been tested on older
-    versions. It might or might not work. Drop us a line if you really need to
-    support ancient Android phones.
-
-*   On Android Marshmallow (API level 23) or older, the
-    `newSeekableDecryptingChannel` method in implementations of `StreamingAead`
-    doesn't work. It depends on
-    [SeekableByteChannel](https://developer.android.com/reference/java/nio/channels/SeekableByteChannel.html),
-    which is only available on API level 24 or newer. Users should use
-    `newEncryptingStream` instead.
-
-*   On Android Lollipop (API level 21) or older, `AndroidKeysetManager` does not
-    support wrapping keysets with Android Keystore, but it'd store keysets in
-    cleartext in private preference. This is secure enough for most
-    applications.
-
-*   On Android KitKat (API level 19) without [Google Play
-    Services](https://developers.google.com/android/guides/overview), `AES-GCM`
-    does not work properly because KitKat uses Bouncy Castle 1.48 which doesn't
-    support updateAAD. If Google Play Services is present, `AES-GCM` should work
-    well. If you want to support all Android versions, without depending on
-    Google Play Services, please use `CHACHA20-POLY1305`, `AES-EAX`, or
-    `AES-CTR-HMAC-AEAD`.
-
-## Signature malleability
-
-*   ECDSA signatures are malleable. You probably can ignore this issue, unless
-    you're working on Bitcoin or cryptocurrencies and have to worry about
-    [transaction
-    malleability](https://en.bitcoin.it/wiki/Transaction_malleability). In that
-    case you want to use ED25519 signatures which are non-malleable.
-
-## Envelope encryption - Benign malleability
-
-*   Envelope encryption uses a third-party provider (e.g. GCP, AWS) to encrypt
-    the *data encryption key (DEK)*. It is possible to modify certain parts of
-    the *encrypted DEK* without detection when using *KmsEnvelopeAead* with
-    *AwsKmsAead* or *GcpKmsAead* as the remote provider. This is due to some
-    metadata being included (for instance version numbers) which is not
-    authenticated and modifications are not detected by the provider. Note that
-    this violates the CCA2 property for this interface, although the ciphertext
-    will still decrypt to the correct DEK. When using this interface one should
-    not rely on that for each DEK there only exists a single *encrypted DEK*.
-
-## Streaming AEAD - potential integer overflow issues
-
-*   Streaming AEAD implementations encrypt the plaintext in segments. Tink uses
-    a 4-byte segment counter. When encrypting a stream consisting of more than
-    2^32 segments, the segment counter might overflow and lead to leakage of key
-    material or plaintext. This problem was found in the Java and Go
-    implementations of the AES-GCM-HKDF-Streaming key type, and has been fixed
-    since 1.4.0.
+See https://developers.google.com/tink/known-issues.
diff --git a/docs/PRIMITIVES.md b/docs/PRIMITIVES.md
index 8fb0e23..841562d 100644
--- a/docs/PRIMITIVES.md
+++ b/docs/PRIMITIVES.md
@@ -27,45 +27,44 @@
 
 ### Primitives supported by language
 
-**Primitive**      | **Java** | **C++** | **Objective-C** | **Go** | **Python**
------------------- | :------: | :-----: | :-------------: | :----: | :--------:
-AEAD               | yes      | yes     | yes             | yes    | yes
-Streaming AEAD     | yes      | yes     | **no**          | yes    | yes
-Deterministic AEAD | yes      | yes     | yes             | yes    | yes
-MAC                | yes      | yes     | yes             | yes    | yes
-PRF                | yes      | yes     | **no**          | yes    | yes
-Digital signatures | yes      | yes     | yes             | yes    | yes
-Hybrid encryption  | yes      | yes     | yes             | yes    | yes
+**Primitive**      | **Java** | **C++** | **Objective-C** | **Go** | **Python** | **TypeScript**
+------------------ | :------: | :-----: | :-------------: | :----: | :--------: | :------------:
+AEAD               | yes      | yes     | yes             | yes    | yes        | yes
+Streaming AEAD     | yes      | yes     | **no**          | yes    | yes        | **no**
+Deterministic AEAD | yes      | yes     | yes             | yes    | yes        | **no**
+MAC                | yes      | yes     | yes             | yes    | yes        | **no**
+PRF                | yes      | yes     | **no**          | yes    | yes        | **no**
+Digital signatures | yes      | yes     | yes             | yes    | yes        | yes
+Hybrid encryption  | yes      | yes     | yes             | yes    | yes        | yes
 
-JavaScript is currently under development.
+TypeScript is still under development.
 
 ### Primitive implementations supported by language
 
-| **Primitive**      | **Implementation**                | **Java** | **C++ (BoringSSL)** | **C++ (OpenSSL)** | **Objective-C** | **Go**     | **Python** |
-| ------------------ | --------------------------------- | :------: | :-----------------: | :---------------: | :-------------: | :--------: | :--------: |
-| AEAD               | AES-GCM                           | yes      | yes                 | yes               | yes             | yes        | yes        |
-|                    | AES-GCM-SIV                       | yes      | yes                 | **no**            | **no**          | **no**     | **no**     |
-|                    | AES-CTR-HMAC                      | yes      | yes                 | yes               | yes             | yes        | yes        |
-|                    | AES-EAX                           | yes      | yes                 | yes               | yes             | **no**     | yes        |
-|                    | KMS Envelope                      | yes      | yes                 | yes               | **no**          | yes        | yes        |
-|                    | CHACHA20-POLY1305                 | yes      | **no**              | **no**            | **no**          | yes        | **no**     |
-|                    | XCHACHA20-POLY1305                | yes      | yes                 | **no**            | yes             | yes        | yes        |
-| Streaming AEAD     | AES-GCM-HKDF-STREAMING            | yes      | yes                 | yes               | **no**          | yes        | yes        |
-|                    | AES-CTR-HMAC-STREAMING            | yes      | yes                 | yes               | **no**          | yes        | yes        |
-| Deterministic AEAD | AES-SIV                           | yes      | yes                 | yes               | yes             | yes        | yes        |
-| MAC                | HMAC-SHA2                         | yes      | yes                 | yes               | yes             | yes        | yes        |
-|                    | AES-CMAC                          | yes      | yes                 | yes               | yes             | yes        | yes        |
-| PRF                | HKDF-SHA2                         | yes      | yes                 | yes               | **no**          | yes        | yes        |
-|                    | HMAC-SHA2                         | yes      | yes                 | yes               | **no**          | yes        | yes        |
-|                    | AES-CMAC                          | yes      | yes                 | yes               | **no**          | yes        | yes        |
-| Digital Signatures | ECDSA over NIST curves            | yes      | yes                 | yes \*            | yes             | yes        | yes        |
-|                    | Ed25519                           | yes      | yes                 | yes               | yes             | yes        | yes        |
-|                    | RSA-SSA-PKCS1                     | yes      | yes                 | yes               | yes             | **no**     | yes        |
-|                    | RSA-SSA-PSS                       | yes      | yes                 | yes               | yes             | **no**     | yes        |
-| Hybrid Encryption  | HPKE                              | yes      | yes                 | **no**            | **no**          | yes        | yes        |
-|                    | ECIES with AEAD                   | yes      | yes                 | yes               | yes             | yes        | yes        |
-|                    | ECIES with DeterministicAEAD      | yes      | yes                 | yes               | **no**          | yes        | yes        |
-|                    | HKDF                              | yes      | yes                 | yes               | yes             | yes        | yes        |
+| **Primitive**       | **Implementation**                    | **Java** | **C++ (BoringSSL)** | **C++ (OpenSSL)** | **Objective-C** | **Go** | **Python** | **TypeScript**
+| ------------------- | ------------------------------------- | :------: | :-----------------: | :---------------: | :-------------: | :----: | :--------: | :------------:
+| AEAD                | AES-GCM                               | yes      | yes                 | yes               | yes             | yes    | yes        | yes
+|                     | AES-GCM-SIV                           | yes      | yes                 | **no**            | **no**          | **no** | **no**     | **no**
+|                     | AES-CTR-HMAC                          | yes      | yes                 | yes               | yes             | yes    | yes        | yes
+|                     | AES-EAX                               | yes      | yes                 | yes               | yes             | **no** | yes        | **no**
+|                     | KMS Envelope                          | yes      | yes                 | yes               | **no**          | yes    | yes        | **no**
+|                     | CHACHA20-POLY1305                     | yes      | **no**              | **no**            | **no**          | yes    | **no**     | **no**
+|                     | XCHACHA20-POLY1305                    | yes      | yes                 | **no**            | yes             | yes    | yes        | **no**
+| Streaming AEAD      | AES-GCM-HKDF-STREAMING                | yes      | yes                 | yes               | **no**          | yes    | yes        | **no**
+|                     | AES-CTR-HMAC-STREAMING                | yes      | yes                 | yes               | **no**          | yes    | yes        | **no**
+| Deterministic AEAD  | AES-SIV                               | yes      | yes                 | yes               | yes             | yes    | yes        | **no**
+| MAC                 | HMAC-SHA2                             | yes      | yes                 | yes               | yes             | yes    | yes        | **no**
+|                     | AES-CMAC                              | yes      | yes                 | yes               | yes             | yes    | yes        | **no**
+| PRF                 | HKDF-SHA2                             | yes      | yes                 | yes               | **no**          | yes    | yes        | **no**
+|                     | HMAC-SHA2                             | yes      | yes                 | yes               | **no**          | yes    | yes        | **no**
+|                     | AES-CMAC                              | yes      | yes                 | yes               | **no**          | yes    | yes        | **no**
+| Digital Signatures  | ECDSA over NIST curves                | yes      | yes                 | yes \*            | yes             | yes    | yes        | yes
+|                     | Ed25519                               | yes      | yes                 | yes               | yes             | yes    | yes        | **no**
+|                     | RSA-SSA-PKCS1                         | yes      | yes                 | yes               | yes             | yes    | yes        | **no**
+|                     | RSA-SSA-PSS                           | yes      | yes                 | yes               | yes             | yes    | yes        | **no**
+| Hybrid Encryption   | HPKE                                  | yes      | yes                 | **no**            | **no**          | yes    | yes        | **no**
+|                     | ECIES with AEAD and HKDF              | yes      | yes                 | yes               | yes             | yes    | yes        | yes
+|                     | ECIES with DeterministicAEAD and HKDF | yes      | yes                 | yes               | **no**          | yes    | yes        | **no**
 
 \* EC key creation from seed (`DeriveKey`) is unsupported.
 
diff --git a/docs/TINKEY.md b/docs/TINKEY.md
index 9c4804f..1fc9f17 100644
--- a/docs/TINKEY.md
+++ b/docs/TINKEY.md
@@ -45,17 +45,20 @@
 
 Available commands:
 
+*   `help`: Prints a help message for all available commands.
 *   `add-key`: Generates and adds a new key to a keyset.
 *   `convert-keyset`: Changes format, encrypts, decrypts a keyset.
 *   `create-keyset`: Creates a new keyset.
 *   `create-public-keyset`: Creates a public keyset from a private keyset.
 *   `list-key-templates`: Lists all supported key templates.
 *   `delete-key`: Deletes a specified key in a keyset.
+*   `destroy-key`: Destroys the key material of a specified key in a keyset.
 *   `disable-key`: Disables a specified key in a keyset.
 *   `enable-key`: Enables a specified key in a keyset.
 *   `list-keyset`: Lists keys in a keyset.
 *   `promote-key`: Promotes a specified key to primary.
-*   `rotate-keyset`: Performs a key rotation in a keyset.
+*   `rotate-keyset`: *Deprecated.* Rotate keysets in two steps using the commands
+    `add-key` and later `promote-key`.
 
 To obtain info about arguments available/required for a command, run `tinkey
 <command>` without further arguments.
@@ -66,18 +69,11 @@
 tinkey create-keyset --key-template ECDSA_P256 --out private-keyset.cfg
 ```
 
--   Add a new key to a keyset
+-   Add a new key to a keyset. This does not change the primary key.
 
 ```shell
 tinkey add-key --key-template ECDSA_P512 --in private-keyset.cfg \
---out private-keyset.cfg
-```
-
--   Rotate a keyset by adding a primary key
-
-```shell
-tinkey rotate-keyset --key-template ED25519 --in private-keyset.cfg \
---out private-keyset.cfg
+  --out new-private-keyset.cfg
 ```
 
 -   List metadata of keys in a keyset:
@@ -86,12 +82,25 @@
 tinkey list-keyset --in private-keyset.cfg
 ```
 
+-   Make the key with key ID 1234567890 the primary key. (You can get the key ID
+    using the `list-keyset` command.)
+
+```shell
+tinkey promote-key --in private-keyset.cfg --key-id 1234567890 \
+  --out new-private-keyset.cfg
+```
+
 -   Create a public keyset from a private keyset
 
 ```shell
 tinkey create-public-keyset --in private-keyset.cfg --out public-keyset.cfg
 ```
 
+Note: tinkey does not allow you to overwrite the keyset to prevent accidental
+loss of existing keys. We recommend you always create a new keyset and
+only delete the old keyset when you are certain that the new keyset works.
+
+
 ## Work with Key Management System (KMS)
 
 Tinkey can encrypt or decrypt keysets with master keys residing in remote KMSes.
@@ -176,18 +185,20 @@
 --new-master-key-uri gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar
 ```
 
--   Add a new key to an encrypted keyset, using default credentials
+-   Add a new key to an encrypted keyset, using default credentials. This does
+    not change the primary key.
 
 ```shell
 tinkey add-key --key-template AES256_GCM --in encrypted-keyset.cfg \
---out encrypted-keyset.cfg \
+  --out new-encrypted-keyset.cfg \
 --master-key-uri gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar
 ```
 
--   Rotate an encrypted keyset by adding a primary key
+-   Make the key with key ID 1234567890 the primary key in an encrypted keyset.
+    (You can get the key ID using the `list-keyset` command.)
 
 ```shell
-tinkey rotate-keyset --key-template AES256_GCM --in encrypted-keyset.cfg \
---out encrypted-keyset.cfg \
+tinkey promote-key--in encrypted-keyset.cfg --key-id 1234567890 \
+  --out new-encrypted-keyset.cfg \
 --master-key-uri gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar
 ```
diff --git a/docs/TYPESCRIPT-HOWTO.md b/docs/TYPESCRIPT-HOWTO.md
new file mode 100644
index 0000000..1fbd9ad
--- /dev/null
+++ b/docs/TYPESCRIPT-HOWTO.md
@@ -0,0 +1,143 @@
+# Tink for TypeScript HOW-TO
+
+This document presents instructions and TypeScript code snippets for common
+tasks in [Tink](https://github.com/google/tink).
+
+Depending on the specifics of your build setup, you may need to alter these
+snippets to use a different import syntax. Both ES modules and UMD are
+supported.
+
+WARNING: Tink for TypeScript/JavaScript is still in an alpha state! Breaking
+changes are likely.
+
+## Setup instructions
+
+To add Tink to a TypeScript/JavaScript project, just run:
+
+```sh
+npm install tink-crypto
+```
+
+Or, if you're using Yarn:
+
+```sh
+yarn add tink-crypto
+```
+
+## Generating new keys and keysets
+
+To take advantage of key rotation and other key management features, you usually
+do not work with single keys, but with keysets, which you can use via a wrapper
+called `KeysetHandle`. Keysets are just sets of keys with some additional
+parameters and metadata. You can generate a new keyset and obtain its handle
+using a `KeyTemplate` (example in below code snippet).
+
+To avoid accidental leakage of sensitive key material, you should usually avoid
+mixing keyset generation and usage in code. To support the separation of these
+activities Tink provides a command-line tool, [Tinkey](TINKEY.md), which can be
+used for common key management tasks. Still, if there is a need to generate a
+`KeysetHandle` with fresh key material directly in TypeScript code, you can use
+`generateNewKeysetHandle`:
+
+```javascript
+import {aead, generateNewKeysetHandle} from 'tink-crypto';
+const {aes256GcmKeyTemplate} = aead;
+
+(async () => {
+  const keyTemplate = aes256GcmKeyTemplate()
+  const keysetHandle = await generateNewKeysetHandle(keyTemplate);
+  // use the keyset...
+})();
+```
+
+Currently, key templates are only available for AEAD encryption, digital
+signatures, and hybrid encryption.
+
+| Key       | Key Template                                                     |
+: Template  :                                                                  :
+: Type      :                                                                  :
+| --------- | ---------------------------------------------------------------- |
+| AEAD      | `aead.aes128GcmKeyTemplate()`                                    |
+| AEAD      | `aead.aes256GcmKeyTemplate()`                                    |
+| AEAD      | `aead.aes256GcmNoPrefixKeyTemplate()`                            |
+| Signature | `signature.ecdsaP256KeyTemplate()`                               |
+| Signature | `signature.ecdsaP256IeeeEncodingKeyTemplate()`                   |
+| Signature | `signature.ecdsaP384KeyTemplate()`                               |
+| Signature | `signature.ecdsaP384IeeeEncodingKeyTemplate()`                   |
+| Signature | `signature.ecdsaP521KeyTemplate()`                               |
+| Signature | `signature.ecdsaP521IeeeEncodingKeyTemplate()`                   |
+| Hybrid    | `hybrid.eciesP256HkdfHmacSha256Aes128GcmKeyTemplate()`           |
+| Hybrid    | `hybrid.eciesP256HkdfHmacSha256Aes128CtrHmacSha256KeyTemplate()` |
+
+### Storing and loading existing keysets
+
+After generating key material, you might want to persist it to LocalStorage or
+IndexedDB, or send it to a server to be stored there. The `binary` and
+`binaryInsecure` subpackages can be used to serialize and deserialize keysets to
+and from `UInt8Array`. `binary` handles only public keys; `binaryInsecure` can
+additionally handle private and symmetric keys. With these, you must be careful
+not to leak the raw key material.
+
+```javascript
+import {aead, binaryInsecure, generateNewKeysetHandle} from 'tink-crypto';
+
+const {Aead, register, aes256GcmKeyTemplate} = aead;
+const {deserializeKeyset, serializeKeyset} = binaryInsecure;
+
+register();
+
+(async () => {
+  const keysetHandle = await generateNewKeysetHandle(aes256GcmKeyTemplate());
+  // Serialize keyset to send/store
+  const serializedKeyset = serializeKeyset(keysetHandle);
+
+  const deserializedKeyset = deserializeKeyset(serializedKeyset)
+  const aead = await deserializedKeyset.getPrimitive(Aead);
+  // Use deserialization... (i.e. to decrypt a ciphertext)
+})();
+```
+
+## Obtaining and using primitives
+
+[*Primitives*](PRIMITIVES.md) represent cryptographic operations offered by
+Tink, hence they form the core of Tink API. A primitive is just an interface
+that specifies what operations are offered by the primitive. A primitive can
+have multiple implementations, and you choose a desired implementation by using
+a key of corresponding type (see the
+[this section](KEY-MANAGEMENT.md#key-keyset-and-keysethandle) for details).
+
+A list of primitives and their implementations currently supported by Tink in
+TypeScript/JavaScript can be found [here](PRIMITIVES.md#typescriptjavascript).
+Note that there are currently a few additional limitations:
+
+*   MAC is supported only via the subtle API, not the keyset API.
+*   It's not possible to generate a fresh new asymmetric keyset using the keyset
+    API and then use it without going through the subtle API. (The public key is
+    not directly accessible yet)
+
+### AEAD
+
+AEAD encryption assures the confidentiality and authenticity of the data. This
+primitive is CPA secure.
+
+```javascript
+// See live on StackBlitz: https://stackblitz.com/edit/tink-typescript?file=index.ts
+
+import {aead, generateNewKeysetHandle} from 'tink-crypto';
+
+const {Aead, register, aes256GcmKeyTemplate} = aead;
+
+register();
+
+(async () => {
+  const keysetHandle = await generateNewKeysetHandle(aes256GcmKeyTemplate());
+  const aead = await keysetHandle.getPrimitive(Aead);
+  const ciphertext = await aead.encrypt(
+      new TextEncoder().encode('this data needs to be encrypted'),
+      new TextEncoder().encode('associated data'));
+  const plaintext = new TextDecoder().decode(await aead.decrypt(
+      ciphertext, new TextEncoder().encode('associated data')));
+  console.log('Ciphertext:', ciphertext);
+  console.log('Plaintext:', plaintext);
+})();
+```
diff --git a/docs/WIRE-FORMAT.md b/docs/WIRE-FORMAT.md
index 5b171d1..6765d2c 100644
--- a/docs/WIRE-FORMAT.md
+++ b/docs/WIRE-FORMAT.md
@@ -1,10 +1,5 @@
 # Tink Wire Format
 
-<!--*
-# Document freshness: For more information, see go/fresh-source.
-freshness: { owner: 'tink-dev' reviewed: '2022-04-12' }
-*-->
-
 Please see the
 [developer documentation](https://developers.google.com/tink/wire-format)
 for details on Tink's wire format for keys and primitive output.
diff --git a/examples/.bazelignore b/examples/.bazelignore
deleted file mode 100644
index 70ff329..0000000
--- a/examples/.bazelignore
+++ /dev/null
@@ -1,2 +0,0 @@
-cc
-java_src
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index 5da3b38..0000000
--- a/examples/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# tink-examples
-
-These examples show how to use [Tink](https://github.com/google/tink)
-to perform common crypto tasks. They also show how to add a dependency
-on Tink using Maven, Gradle or Bazel.
-
-Subscribe to our
-[mailing list](https://groups.google.com/forum/#!forum/tink-users)
-if you have any questions.
diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle
deleted file mode 100644
index 6cc4b8e..0000000
--- a/examples/android/helloworld/app/build.gradle
+++ /dev/null
@@ -1,45 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
-    compileSdkVersion 26
-    defaultConfig {
-        applicationId "com.helloworld"
-        minSdkVersion 19
-        targetSdkVersion 26
-        versionCode 1
-        versionName "1.0"
-        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-    }
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-        }
-    }
-   compileOptions {
-        sourceCompatibility 1.8
-        targetCompatibility 1.8
-   }
-}
-
-apply from: "maven_${mavenLocation}.gradle"
-
-dependencies {
-    implementation fileTree(dir: 'libs', include: ['*.jar'])
-    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
-        exclude group: 'com.android.support', module: 'support-annotations'
-        // This is already included in protobuf-lite that Tink depends on.
-        exclude group: 'com.google.code.findbugs'
-    })
-    implementation 'com.android.support:appcompat-v7:26.+'
-    implementation 'com.android.support:design:26.+'
-    testImplementation 'junit:junit:4.12'
-
-    // Tink HEAD-SNAPSHOT for Android.
-    // In production apps, please use a named version, e.g., 1.4.0.
-    implementation 'com.google.crypto.tink:tink-android:HEAD-SNAPSHOT'
-
-    // An artificial dependency to test whether Tink can co-exist with other
-    // protobuf dependencies. Please remove from production apps.
-    implementation 'com.google.protobuf:protobuf-lite:3.0.1'
-}
diff --git a/examples/android/helloworld/app/src/main/java/com/helloworld/TinkApplication.java b/examples/android/helloworld/app/src/main/java/com/helloworld/TinkApplication.java
deleted file mode 100644
index daefd70..0000000
--- a/examples/android/helloworld/app/src/main/java/com/helloworld/TinkApplication.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// 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
-//
-//      http://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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.helloworld;
-
-import android.app.Application;
-import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.Config;
-import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.aead.AeadKeyTemplates;
-import com.google.crypto.tink.config.TinkConfig;
-import com.google.crypto.tink.integration.android.AndroidKeysetManager;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
-/** A custom application that initializes the Tink runtime at application startup. */
-public class TinkApplication extends Application {
-  private static final String TAG = TinkApplication.class.toString();
-  private static final String PREF_FILE_NAME = "hello_world_pref";
-  private static final String TINK_KEYSET_NAME = "hello_world_keyset";
-  private static final String MASTER_KEY_URI = "android-keystore://hello_world_master_key";
-  public Aead aead;
-
-  @Override
-  public final void onCreate() {
-    super.onCreate();
-    try {
-      Config.register(TinkConfig.TINK_1_0_0);
-      aead = getOrGenerateNewKeysetHandle().getPrimitive(Aead.class);
-    } catch (GeneralSecurityException | IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  private KeysetHandle getOrGenerateNewKeysetHandle() throws IOException, GeneralSecurityException {
-    return new AndroidKeysetManager.Builder()
-        .withSharedPref(getApplicationContext(), TINK_KEYSET_NAME, PREF_FILE_NAME)
-        .withKeyTemplate(AeadKeyTemplates.AES256_GCM)
-        .withMasterKeyUri(MASTER_KEY_URI)
-        .build()
-        .getKeysetHandle();
-  }
-}
diff --git a/examples/android/helloworld/build.gradle b/examples/android/helloworld/build.gradle
deleted file mode 100644
index 4959c1c..0000000
--- a/examples/android/helloworld/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
-    repositories {
-        google()
-        mavenCentral()
-    }
-    dependencies {
-        classpath 'com.android.tools.build:gradle:3.6.0'
-
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
-    }
-}
-
-allprojects {
-    repositories {
-        google()
-        mavenCentral()
-    }
-}
-
-task clean(type: Delete) {
-    delete rootProject.buildDir
-}
diff --git a/examples/android/helloworld/gradle.properties b/examples/android/helloworld/gradle.properties
deleted file mode 100644
index f874b2f..0000000
--- a/examples/android/helloworld/gradle.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-
-# Preferred source for Maven packages:
-#   "local": Local Maven repository (i.e. for testing without publishing)
-#   "snapshot": Maven Central snapshot repository (i.e. for testing published
-#       snapshots)
-mavenLocation=snapshot
diff --git a/examples/android/helloworld/gradle/wrapper/gradle-wrapper.jar b/examples/android/helloworld/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 7a3265e..0000000
--- a/examples/android/helloworld/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/examples/android/helloworld/gradle/wrapper/gradle-wrapper.properties b/examples/android/helloworld/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 8205549..0000000
--- a/examples/android/helloworld/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
diff --git a/examples/android/helloworld/gradlew b/examples/android/helloworld/gradlew
deleted file mode 100755
index cccdd3d..0000000
--- a/examples/android/helloworld/gradlew
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/env sh
-
-##############################################################################
-##
-##  Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
-    echo "$*"
-}
-
-die () {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-  NONSTOP* )
-    nonstop=true
-    ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-        # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
-    else
-        JAVACMD="$JAVA_HOME/bin/java"
-    fi
-    if [ ! -x "$JAVACMD" ] ; then
-        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-else
-    JAVACMD="java"
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-    JAVACMD=`cygpath --unix "$JAVACMD"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-# Escape application args
-save () {
-    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
-    echo " "
-}
-APP_ARGS=$(save "$@")
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
-  cd "$(dirname "$0")"
-fi
-
-exec "$JAVACMD" "$@"
diff --git a/examples/android/helloworld/gradlew.bat b/examples/android/helloworld/gradlew.bat
deleted file mode 100644
index e95643d..0000000
--- a/examples/android/helloworld/gradlew.bat
+++ /dev/null
@@ -1,84 +0,0 @@
-@if "%DEBUG%" == "" @echo off

-@rem ##########################################################################

-@rem

-@rem  Gradle startup script for Windows

-@rem

-@rem ##########################################################################

-

-@rem Set local scope for the variables with windows NT shell

-if "%OS%"=="Windows_NT" setlocal

-

-set DIRNAME=%~dp0

-if "%DIRNAME%" == "" set DIRNAME=.

-set APP_BASE_NAME=%~n0

-set APP_HOME=%DIRNAME%

-

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS=

-

-@rem Find java.exe

-if defined JAVA_HOME goto findJavaFromJavaHome

-

-set JAVA_EXE=java.exe

-%JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

-

-echo.

-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:findJavaFromJavaHome

-set JAVA_HOME=%JAVA_HOME:"=%

-set JAVA_EXE=%JAVA_HOME%/bin/java.exe

-

-if exist "%JAVA_EXE%" goto init

-

-echo.

-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:init

-@rem Get command-line arguments, handling Windows variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-

-:execute

-@rem Setup the command line

-

-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

-

-@rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

-

-:end

-@rem End local scope for the variables with windows NT shell

-if "%ERRORLEVEL%"=="0" goto mainEnd

-

-:fail

-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

-rem the _cmd.exe /c_ return code!

-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

-exit /b 1

-

-:mainEnd

-if "%OS%"=="Windows_NT" endlocal

-

-:omega

diff --git a/examples/objc/helloworld/README.md b/examples/objc/helloworld/README.md
deleted file mode 100644
index 901ed51..0000000
--- a/examples/objc/helloworld/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Obj-C Hello World
-
-This is an example iOS application that can encrypt and decrypt text using [AEAD
-(Authenticated Encryption with Associated
-Data)](../../../docs/PRIMITIVES.md#authenticated-encryption-with-associated-data).
-
-It demonstrates the basic steps of using Tink, namely generating key material,
-obtaining a primitive, and using the primitive to do crypto.
-
-The example comes with a Podfile that demonstrates how to install Tink from Cocoapods.
-
-## Build and Run
-
-### Cocoapods
-
-```shell
-git clone https://github.com/google/tink
-cd tink/examples/objc/helloworld
-pod install
-open TinkExampleApp.xcworkspace
-```
-
diff --git a/go/.bazelversion b/go/.bazelversion
index ac14c3d..09b254e 100644
--- a/go/.bazelversion
+++ b/go/.bazelversion
@@ -1 +1 @@
-5.1.1
+6.0.0
diff --git a/go/README.md b/go/README.md
new file mode 100644
index 0000000..5d8c259
--- /dev/null
+++ b/go/README.md
@@ -0,0 +1,7 @@
+# Tink Go
+
+This is the Go implementation of Tink v1.7.0.
+
+> **NOTE**: **This library has migrated to
+> https://github.com/tink-crypto/tink-go
+> ([godoc](https://pkg.go.dev/github.com/tink-crypto/tink-go/tink))**
diff --git a/go/WORKSPACE b/go/WORKSPACE
index 060119d..526c81c 100644
--- a/go/WORKSPACE
+++ b/go/WORKSPACE
@@ -10,6 +10,25 @@
 )
 
 # -------------------------------------------------------------------------
+# Bazel Skylib.
+# -------------------------------------------------------------------------
+# Release from 2023-02-09
+# Protobuf vX.21.9 imports a version of bazel-skylib [1] that is incompatible
+# with the one required by bazel-gazelle, so we make sure we have a newer
+# version [2].
+#
+# [1] https://github.com/protocolbuffers/protobuf/blob/90b73ac3f0b10320315c2ca0d03a5a9b095d2f66/protobuf_deps.bzl#L28
+# [2] https://github.com/bazelbuild/bazel-gazelle/issues/1290#issuecomment-1312809060
+http_archive(
+    name = "bazel_skylib",
+    sha256 = "b8a1527901774180afc798aeb28c4634bdccf19c4d98e7bdd1ce79d1fe9aaad7",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.4.1/bazel-skylib-1.4.1.tar.gz",
+        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.1/bazel-skylib-1.4.1.tar.gz",
+    ],
+)
+
+# -------------------------------------------------------------------------
 # Wycheproof.
 # -------------------------------------------------------------------------
 # Commit from 2019-12-17
@@ -29,12 +48,12 @@
 #   * @com_google_protobuf//:cc_toolchain
 #   * @com_google_protobuf//:java_toolchain
 # This statement defines the @com_google_protobuf repo.
-# Release from 2021-06-08.
+# Release X.21.9 from 2022-10-26.
 http_archive(
     name = "com_google_protobuf",
-    sha256 = "6b6bf5cd8d0cca442745c4c3c9f527c83ad6ef35a405f64db5215889ac779b42",
-    strip_prefix = "protobuf-3.19.3",
-    urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.19.3.zip"],
+    strip_prefix = "protobuf-21.9",
+    urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v21.9.zip"],
+    sha256 = "5babb8571f1cceafe0c18e13ddb3be556e87e12ceea3463d6b0d0064e6cc1ac3",
 )
 
 load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
@@ -42,37 +61,32 @@
 protobuf_deps()
 
 # -------------------------------------------------------------------------
-# Remote Build Execution (RBE).
+# Bazel rules for Go.
 # -------------------------------------------------------------------------
-# Latest bazel_toolchains package on 2021-10-13.
+# Release from 2023-04-20
 http_archive(
-    name = "bazel_toolchains",
-    sha256 = "179ec02f809e86abf56356d8898c8bd74069f1bd7c56044050c2cd3d79d0e024",
-    strip_prefix = "bazel-toolchains-4.1.0",
+    name = "io_bazel_rules_go",
+    sha256 = "6dc2da7ab4cf5d7bfc7c949776b1b7c733f05e56edc4bcd9022bb249d2e2a996",
     urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/4.1.0/bazel-toolchains-4.1.0.tar.gz",
-        "https://github.com/bazelbuild/bazel-toolchains/releases/download/4.1.0/bazel-toolchains-4.1.0.tar.gz",
+        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.39.1/rules_go-v0.39.1.zip",
+        "https://github.com/bazelbuild/rules_go/releases/download/v0.39.1/rules_go-v0.39.1.zip",
     ],
 )
 
-load("@bazel_toolchains//rules:rbe_repo.bzl", "rbe_autoconfig")
-
-rbe_autoconfig(name = "rbe_default")
-
 # -------------------------------------------------------------------------
 # Bazel Gazelle.
 # -------------------------------------------------------------------------
-# Release from 2021-10-11.
+# Release from 2023-01-14
 http_archive(
     name = "bazel_gazelle",
-    sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb",
+    sha256 = "ecba0f04f96b4960a5b250c8e8eeec42281035970aa8852dda73098274d14a1d",
     urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
-        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
+        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
+        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
     ],
 )
 
-load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
 
 # -------------------------------------------------------------------------
 # Tink Go Deps.
@@ -82,34 +96,13 @@
 # gazelle:repository_macro deps.bzl%go_dependencies
 go_dependencies()
 
-# -------------------------------------------------------------------------
-# Bazel rules for Go.
-# -------------------------------------------------------------------------
-# Release from 2022-03-21
-http_archive(
-    name = "io_bazel_rules_go",
-    sha256 = "f2dcd210c7095febe54b804bb1cd3a58fe8435a909db2ec04e31542631cf715c",
-    urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.31.0/rules_go-v0.31.0.zip",
-        "https://github.com/bazelbuild/rules_go/releases/download/v0.31.0/rules_go-v0.31.0.zip",
-    ],
-)
-
 load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
 
-# TODO(b/213404399): Remove after Gazelle issue is fixed.
-go_repository(
-    name = "com_google_cloud_go_compute",
-    importpath = "cloud.google.com/go/compute",
-    sum = "h1:rSUBvAyVwNJ5uQCKNJFMwPtTvJkfN38b6Pvb9zZoqJ8=",
-    version = "v0.1.0",
-)
-
 go_rules_dependencies()
 
 go_register_toolchains(
     nogo = "@//:tink_nogo",
-    version = "1.17.6",
+    version = "1.19.9",
 )
 
 gazelle_dependencies()
diff --git a/go/aead/BUILD.bazel b/go/aead/BUILD.bazel
index 80e9e0b..33ef479 100644
--- a/go/aead/BUILD.bazel
+++ b/go/aead/BUILD.bazel
@@ -27,6 +27,7 @@
         "//core/registry",
         "//internal/internalregistry",
         "//internal/monitoringutil",
+        "//internal/tinkerror",
         "//keyset",
         "//mac/subtle",
         "//monitoring",
@@ -51,12 +52,15 @@
     name = "aead_test",
     srcs = [
         "aead_factory_test.go",
+        "aead_init_test.go",
         "aead_key_templates_test.go",
         "aead_test.go",
         "aes_ctr_hmac_aead_key_manager_test.go",
         "aes_gcm_key_manager_test.go",
         "aes_gcm_siv_key_manager_test.go",
         "chacha20poly1305_key_manager_test.go",
+        "kms_envelope_aead_example_test.go",
+        "kms_envelope_aead_key_manager_test.go",
         "kms_envelope_aead_test.go",
         "xchacha20poly1305_key_manager_test.go",
     ],
@@ -65,13 +69,18 @@
         "//aead/subtle",
         "//core/cryptofmt",
         "//core/registry",
+        "//insecurecleartextkeyset",
         "//internal/internalregistry",
+        "//internal/testing/stubkeymanager",
+        "//internal/tinkerror/tinkerrortest",
         "//keyset",
+        "//mac",
         "//monitoring",
         "//proto/aes_ctr_hmac_aead_go_proto",
         "//proto/aes_gcm_go_proto",
         "//proto/aes_gcm_siv_go_proto",
         "//proto/chacha20_poly1305_go_proto",
+        "//proto/kms_envelope_go_proto",
         "//proto/tink_go_proto",
         "//proto/xchacha20_poly1305_go_proto",
         "//signature",
diff --git a/go/aead/aead.go b/go/aead/aead.go
index 45cd93e..4c313a0 100644
--- a/go/aead/aead.go
+++ b/go/aead/aead.go
@@ -23,6 +23,7 @@
 	"fmt"
 
 	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/internal/internalregistry"
 )
 
 func init() {
@@ -33,6 +34,9 @@
 	if err := registry.RegisterKeyManager(new(aesGCMKeyManager)); err != nil {
 		panic(fmt.Sprintf("aead.init() failed: %v", err))
 	}
+	if err := internalregistry.AllowKeyDerivation(aesGCMTypeURL); err != nil {
+		panic(fmt.Sprintf("aead.init() failed: %v", err))
+	}
 
 	if err := registry.RegisterKeyManager(new(chaCha20Poly1305KeyManager)); err != nil {
 		panic(fmt.Sprintf("aead.init() failed: %v", err))
@@ -41,6 +45,9 @@
 	if err := registry.RegisterKeyManager(new(xChaCha20Poly1305KeyManager)); err != nil {
 		panic(fmt.Sprintf("aead.init() failed: %v", err))
 	}
+	if err := internalregistry.AllowKeyDerivation(xChaCha20Poly1305TypeURL); err != nil {
+		panic(fmt.Sprintf("aead.init() failed: %v", err))
+	}
 
 	if err := registry.RegisterKeyManager(new(kmsEnvelopeAEADKeyManager)); err != nil {
 		panic(fmt.Sprintf("aead.init() failed: %v", err))
diff --git a/go/aead/aead_factory.go b/go/aead/aead_factory.go
index 55bbdb1..bfe8879 100644
--- a/go/aead/aead_factory.go
+++ b/go/aead/aead_factory.go
@@ -21,7 +21,6 @@
 
 	"github.com/google/tink/go/core/cryptofmt"
 	"github.com/google/tink/go/core/primitiveset"
-	"github.com/google/tink/go/core/registry"
 	"github.com/google/tink/go/internal/internalregistry"
 	"github.com/google/tink/go/internal/monitoringutil"
 	"github.com/google/tink/go/keyset"
@@ -30,19 +29,11 @@
 )
 
 // New returns an AEAD primitive from the given keyset handle.
-func New(h *keyset.Handle) (tink.AEAD, error) {
-	return NewWithKeyManager(h, nil /*keyManager*/)
-}
-
-// NewWithKeyManager returns an AEAD primitive from the given keyset handle and custom key manager.
-//
-// Deprecated: Use [New].
-func NewWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.AEAD, error) {
-	ps, err := h.PrimitivesWithKeyManager(km)
+func New(handle *keyset.Handle) (tink.AEAD, error) {
+	ps, err := handle.Primitives()
 	if err != nil {
 		return nil, fmt.Errorf("aead_factory: cannot obtain primitive set: %s", err)
 	}
-
 	return newWrappedAead(ps)
 }
 
@@ -66,32 +57,43 @@
 			}
 		}
 	}
-	wa := &wrappedAead{ps: ps}
-	client := internalregistry.GetMonitoringClient()
-	if client == nil {
-		return wa, nil
-	}
-	keysetInfo, err := monitoringutil.KeysetInfoFromPrimitiveSet(ps)
+	encLogger, decLogger, err := createLoggers(ps)
 	if err != nil {
 		return nil, err
 	}
-	wa.encLogger, err = client.NewLogger(&monitoring.Context{
+	return &wrappedAead{
+		ps:        ps,
+		encLogger: encLogger,
+		decLogger: decLogger,
+	}, nil
+}
+
+func createLoggers(ps *primitiveset.PrimitiveSet) (monitoring.Logger, monitoring.Logger, error) {
+	if len(ps.Annotations) == 0 {
+		return &monitoringutil.DoNothingLogger{}, &monitoringutil.DoNothingLogger{}, nil
+	}
+	client := internalregistry.GetMonitoringClient()
+	keysetInfo, err := monitoringutil.KeysetInfoFromPrimitiveSet(ps)
+	if err != nil {
+		return nil, nil, err
+	}
+	encLogger, err := client.NewLogger(&monitoring.Context{
 		Primitive:   "aead",
 		APIFunction: "encrypt",
 		KeysetInfo:  keysetInfo,
 	})
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	wa.decLogger, err = client.NewLogger(&monitoring.Context{
+	decLogger, err := client.NewLogger(&monitoring.Context{
 		Primitive:   "aead",
 		APIFunction: "decrypt",
 		KeysetInfo:  keysetInfo,
 	})
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	return wa, nil
+	return encLogger, decLogger, nil
 }
 
 // Encrypt encrypts the given plaintext with the given associatedData.
@@ -108,7 +110,13 @@
 		return nil, err
 	}
 	a.encLogger.Log(primary.KeyID, len(plaintext))
-	return append([]byte(primary.Prefix), ct...), nil
+	if len(primary.Prefix) == 0 {
+		return ct, nil
+	}
+	output := make([]byte, 0, len(primary.Prefix)+len(ct))
+	output = append(output, primary.Prefix...)
+	output = append(output, ct...)
+	return output, nil
 }
 
 // Decrypt decrypts the given ciphertext and authenticates it with the given
diff --git a/go/aead/aead_factory_test.go b/go/aead/aead_factory_test.go
index 9716142..7f8f8c3 100644
--- a/go/aead/aead_factory_test.go
+++ b/go/aead/aead_factory_test.go
@@ -18,6 +18,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"strings"
 	"testing"
@@ -26,7 +27,10 @@
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/google/tink/go/aead"
 	"github.com/google/tink/go/core/cryptofmt"
+	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/insecurecleartextkeyset"
 	"github.com/google/tink/go/internal/internalregistry"
+	"github.com/google/tink/go/internal/testing/stubkeymanager"
 	"github.com/google/tink/go/keyset"
 	"github.com/google/tink/go/monitoring"
 	"github.com/google/tink/go/signature"
@@ -37,6 +41,7 @@
 	"github.com/google/tink/go/tink"
 
 	"github.com/google/tink/go/aead/subtle"
+	agpb "github.com/google/tink/go/proto/aes_gcm_go_proto"
 	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
 )
 
@@ -103,9 +108,7 @@
 	}
 }
 
-func validateAEADFactoryCipher(encryptCipher tink.AEAD,
-	decryptCipher tink.AEAD,
-	expectedPrefix string) error {
+func validateAEADFactoryCipher(encryptCipher, decryptCipher tink.AEAD, expectedPrefix string) error {
 	prefixSize := len(expectedPrefix)
 	// regular plaintext
 	pt := random.GetRandomBytes(20)
@@ -170,26 +173,37 @@
 	}
 }
 
-func TestFactoryWithMonitoringPrimitiveLogsEncryptionDecryptionWithPrefix(t *testing.T) {
+func TestPrimitiveFactoryWithMonitoringAnnotationsLogsEncryptionDecryptionWithPrefix(t *testing.T) {
 	defer internalregistry.ClearMonitoringClient()
 	client := fakemonitoring.NewClient("fake-client")
 	if err := internalregistry.RegisterMonitoringClient(client); err != nil {
-		t.Fatalf("registry.RegisterMonitoringClient() err = %v, want nil", err)
+		t.Fatalf("internalregistry.RegisterMonitoringClient() err = %v, want nil", err)
 	}
 	kh, err := keyset.NewHandle(aead.AES128GCMKeyTemplate())
 	if err != nil {
 		t.Fatalf("keyset.NewHandle() err = %v, want nil", err)
 	}
-	p, err := aead.New(kh)
+	// Annotations are only supported throught the `insecurecleartextkeyset` API.
+	buff := &bytes.Buffer{}
+	if err := insecurecleartextkeyset.Write(kh, keyset.NewBinaryWriter(buff)); err != nil {
+		t.Fatalf("insecurecleartextkeyset.Write() err = %v, want nil", err)
+	}
+	annotations := map[string]string{"foo": "bar"}
+	mh, err := insecurecleartextkeyset.Read(keyset.NewBinaryReader(buff), keyset.WithAnnotations(annotations))
+	if err != nil {
+		t.Fatalf("insecurecleartextkeyset.Read() err = %v, want nil", err)
+	}
+	p, err := aead.New(mh)
 	if err != nil {
 		t.Fatalf("aead.New() err = %v, want nil", err)
 	}
 	data := []byte("HELLO_WORLD")
-	ct, err := p.Encrypt(data, nil)
+	ad := []byte("_!")
+	ct, err := p.Encrypt(data, ad)
 	if err != nil {
 		t.Fatalf("p.Encrypt() err = %v, want nil", err)
 	}
-	if _, err := p.Decrypt(ct, nil); err != nil {
+	if _, err := p.Decrypt(ct, ad); err != nil {
 		t.Fatalf("p.Decrypt() err = %v, want nil", err)
 	}
 	failures := client.Failures()
@@ -198,46 +212,57 @@
 	}
 	got := client.Events()
 	wantKeysetInfo := monitoring.NewKeysetInfo(
-		make(map[string]string),
+		annotations,
 		kh.KeysetInfo().GetPrimaryKeyId(),
 		[]*monitoring.Entry{
 			{
-				KeyID:          kh.KeysetInfo().GetPrimaryKeyId(),
-				Status:         monitoring.Enabled,
-				FormatAsString: "type.googleapis.com/google.crypto.tink.AesGcmKey",
+				KeyID:     kh.KeysetInfo().GetPrimaryKeyId(),
+				Status:    monitoring.Enabled,
+				KeyType:   "tink.AesGcmKey",
+				KeyPrefix: "TINK",
 			},
 		},
 	)
 	want := []*fakemonitoring.LogEvent{
 		{
-			KeyID:    kh.KeysetInfo().GetPrimaryKeyId(),
+			KeyID:    mh.KeysetInfo().GetPrimaryKeyId(),
 			NumBytes: len(data),
 			Context:  monitoring.NewContext("aead", "encrypt", wantKeysetInfo),
 		},
 		{
-			KeyID: kh.KeysetInfo().GetPrimaryKeyId(),
+			KeyID: mh.KeysetInfo().GetPrimaryKeyId(),
 			// ciphertext was encrypted with a key that has TINK ouput prefix. This adds a 5 bytes prefix
 			// to the ciphertext. This prefix is not included in `Log` call.
 			NumBytes: len(ct) - cryptofmt.NonRawPrefixSize,
 			Context:  monitoring.NewContext("aead", "decrypt", wantKeysetInfo),
 		},
 	}
-	if !cmp.Equal(got, want) {
-		t.Errorf("got = %v, want = %v", got, want)
+	if cmp.Diff(got, want) != "" {
+		t.Errorf("%v", cmp.Diff(got, want))
 	}
 }
 
-func TestFactoryWithMonitoringPrimitiveLogsEncryptionDecryptionWithoutPrefix(t *testing.T) {
+func TestPrimitiveFactoryWithMonitoringAnnotationsLogsEncryptionDecryptionWithoutPrefix(t *testing.T) {
 	defer internalregistry.ClearMonitoringClient()
 	client := fakemonitoring.NewClient("fake-client")
 	if err := internalregistry.RegisterMonitoringClient(client); err != nil {
-		t.Fatalf("registry.RegisterMonitoringClient() err = %v, want nil", err)
+		t.Fatalf("internalregistry.RegisterMonitoringClient() err = %v, want nil", err)
 	}
 	kh, err := keyset.NewHandle(aead.AES256GCMNoPrefixKeyTemplate())
 	if err != nil {
 		t.Fatalf("keyset.NewHandle() err = %v, want nil", err)
 	}
-	p, err := aead.New(kh)
+	// Annotations are only supported throught the `insecurecleartextkeyset` API.
+	buff := &bytes.Buffer{}
+	if err := insecurecleartextkeyset.Write(kh, keyset.NewBinaryWriter(buff)); err != nil {
+		t.Fatalf("insecurecleartextkeyset.Write() err = %v, want nil", err)
+	}
+	annotations := map[string]string{"foo": "bar"}
+	mh, err := insecurecleartextkeyset.Read(keyset.NewBinaryReader(buff), keyset.WithAnnotations(annotations))
+	if err != nil {
+		t.Fatalf("insecurecleartextkeyset.Read() err = %v, want nil", err)
+	}
+	p, err := aead.New(mh)
 	if err != nil {
 		t.Fatalf("aead.New() err = %v, want nil", err)
 	}
@@ -255,34 +280,35 @@
 	}
 	got := client.Events()
 	wantKeysetInfo := monitoring.NewKeysetInfo(
-		make(map[string]string),
+		annotations,
 		kh.KeysetInfo().GetPrimaryKeyId(),
 		[]*monitoring.Entry{
 			{
-				KeyID:          kh.KeysetInfo().GetPrimaryKeyId(),
-				Status:         monitoring.Enabled,
-				FormatAsString: "type.googleapis.com/google.crypto.tink.AesGcmKey",
+				KeyID:     kh.KeysetInfo().GetPrimaryKeyId(),
+				Status:    monitoring.Enabled,
+				KeyType:   "tink.AesGcmKey",
+				KeyPrefix: "RAW",
 			},
 		},
 	)
 	want := []*fakemonitoring.LogEvent{
 		{
-			KeyID:    kh.KeysetInfo().GetPrimaryKeyId(),
+			KeyID:    mh.KeysetInfo().GetPrimaryKeyId(),
 			NumBytes: len(data),
 			Context:  monitoring.NewContext("aead", "encrypt", wantKeysetInfo),
 		},
 		{
-			KeyID:    kh.KeysetInfo().GetPrimaryKeyId(),
+			KeyID:    mh.KeysetInfo().GetPrimaryKeyId(),
 			NumBytes: len(ct),
 			Context:  monitoring.NewContext("aead", "decrypt", wantKeysetInfo),
 		},
 	}
-	if !cmp.Equal(got, want) {
-		t.Errorf("got = %v, want = %v", got, want)
+	if cmp.Diff(got, want) != "" {
+		t.Errorf("%v", cmp.Diff(got, want))
 	}
 }
 
-func TestFactoryWithMonitoringPrimitiveWithMultipleKeysLogsEncryptionDecryption(t *testing.T) {
+func TestPrimitiveFactoryMonitoringWithAnnotatiosMultipleKeysLogsEncryptionDecryption(t *testing.T) {
 	defer internalregistry.ClearMonitoringClient()
 	client := fakemonitoring.NewClient("fake-client")
 	if err := internalregistry.RegisterMonitoringClient(client); err != nil {
@@ -313,7 +339,17 @@
 	if err != nil {
 		t.Fatalf("manager.Handle() err = %v, want nil", err)
 	}
-	p, err := aead.New(kh)
+	// Annotations are only supported throught the `insecurecleartextkeyset` API.
+	buff := &bytes.Buffer{}
+	if err := insecurecleartextkeyset.Write(kh, keyset.NewBinaryWriter(buff)); err != nil {
+		t.Fatalf("insecurecleartextkeyset.Write() err = %v, want nil", err)
+	}
+	annotations := map[string]string{"foo": "bar"}
+	mh, err := insecurecleartextkeyset.Read(keyset.NewBinaryReader(buff), keyset.WithAnnotations(annotations))
+	if err != nil {
+		t.Fatalf("insecurecleartextkeyset.Read() err = %v, want nil", err)
+	}
+	p, err := aead.New(mh)
 	if err != nil {
 		t.Fatalf("aead.New() err = %v, want nil", err)
 	}
@@ -330,21 +366,24 @@
 		t.Fatalf("p.Decrypt() err = %v, want nil", err)
 	}
 	got := client.Events()
-	wantKeysetInfo := monitoring.NewKeysetInfo(make(map[string]string), keyIDs[1], []*monitoring.Entry{
+	wantKeysetInfo := monitoring.NewKeysetInfo(annotations, keyIDs[1], []*monitoring.Entry{
 		{
-			KeyID:          keyIDs[1],
-			Status:         monitoring.Enabled,
-			FormatAsString: "type.googleapis.com/google.crypto.tink.AesGcmKey",
+			KeyID:     keyIDs[1],
+			Status:    monitoring.Enabled,
+			KeyType:   "tink.AesGcmKey",
+			KeyPrefix: "RAW",
 		},
 		{
-			KeyID:          keyIDs[2],
-			Status:         monitoring.Enabled,
-			FormatAsString: "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey",
+			KeyID:     keyIDs[2],
+			Status:    monitoring.Enabled,
+			KeyType:   "tink.AesCtrHmacAeadKey",
+			KeyPrefix: "TINK",
 		},
 		{
-			KeyID:          keyIDs[3],
-			Status:         monitoring.Enabled,
-			FormatAsString: "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key",
+			KeyID:     keyIDs[3],
+			Status:    monitoring.Enabled,
+			KeyType:   "tink.XChaCha20Poly1305Key",
+			KeyPrefix: "TINK",
 		},
 	})
 	want := []*fakemonitoring.LogEvent{
@@ -376,25 +415,47 @@
 	}
 }
 
-func TestFactoryWithMonitoringPrimitiveEncryptionFailureIsLogged(t *testing.T) {
+func TestPrimitiveFactoryWithMonitoringAnnotationsEncryptionFailureIsLogged(t *testing.T) {
 	defer internalregistry.ClearMonitoringClient()
 	client := &fakemonitoring.Client{Name: ""}
 	if err := internalregistry.RegisterMonitoringClient(client); err != nil {
 		t.Fatalf("internalregistry.RegisterMonitoringClient() err = %v, want nil", err)
 	}
-	kh, err := keyset.NewHandle(aead.AES128GCMKeyTemplate())
+	typeURL := "TestFactoryWithMonitoringPrimitiveEncryptionFailureIsLogged"
+	template := &tinkpb.KeyTemplate{
+		TypeUrl:          typeURL,
+		OutputPrefixType: tinkpb.OutputPrefixType_LEGACY,
+	}
+	km := &stubkeymanager.StubKeyManager{
+		URL:  typeURL,
+		Key:  &agpb.AesGcmKey{},
+		Prim: &testutil.AlwaysFailingAead{Error: errors.New("failed")},
+		KeyData: &tinkpb.KeyData{
+			TypeUrl:         typeURL,
+			KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
+			Value:           []byte("serialized_key"),
+		},
+	}
+	if err := registry.RegisterKeyManager(km); err != nil {
+		t.Fatalf("registry.RegisterKeyManager() err = %v, want nil", err)
+	}
+	kh, err := keyset.NewHandle(template)
 	if err != nil {
 		t.Fatalf("keyset.NewHandle() err = %v, want nil", err)
 	}
-	km := testutil.NewTestKeyManager(
-		&testutil.AlwaysFailingAead{
-			Error: fmt.Errorf("failed"),
-		},
-		testutil.AESGCMTypeURL,
-	)
-	p, err := aead.NewWithKeyManager(kh, km)
+	// Annotations are only supported throught the `insecurecleartextkeyset` API.
+	buff := &bytes.Buffer{}
+	if err := insecurecleartextkeyset.Write(kh, keyset.NewBinaryWriter(buff)); err != nil {
+		t.Fatalf("insecurecleartextkeyset.Write() err = %v, want nil", err)
+	}
+	annotations := map[string]string{"foo": "bar"}
+	mh, err := insecurecleartextkeyset.Read(keyset.NewBinaryReader(buff), keyset.WithAnnotations(annotations))
 	if err != nil {
-		t.Fatalf("aead.NewWithKeyManager() err = %v, want nil", err)
+		t.Fatalf("insecurecleartextkeyset.Read() err = %v, want nil", err)
+	}
+	p, err := aead.New(mh)
+	if err != nil {
+		t.Fatalf("aead.New() err = %v, want nil", err)
 	}
 	if _, err := p.Encrypt(nil, nil); err == nil {
 		t.Fatalf("Encrypt() err = nil, want error")
@@ -406,27 +467,28 @@
 				"aead",
 				"encrypt",
 				monitoring.NewKeysetInfo(
-					make(map[string]string),
+					annotations,
 					kh.KeysetInfo().GetPrimaryKeyId(),
 					[]*monitoring.Entry{
 						{
-							KeyID:          kh.KeysetInfo().GetPrimaryKeyId(),
-							Status:         monitoring.Enabled,
-							FormatAsString: "type.googleapis.com/google.crypto.tink.AesGcmKey",
+							KeyID:     kh.KeysetInfo().GetPrimaryKeyId(),
+							Status:    monitoring.Enabled,
+							KeyType:   typeURL,
+							KeyPrefix: "LEGACY",
 						},
 					},
 				),
 			),
 		},
 	}
-	if !cmp.Equal(got, want) {
-		t.Errorf("got = %v, want = %v", got, want)
+	if cmp.Diff(got, want) != "" {
+		t.Errorf("%v", cmp.Diff(got, want))
 	}
 }
 
-func TestFactoryWithMonitoringPrimitiveDecryptionFailureIsLogged(t *testing.T) {
+func TestPrimitiveFactoryWithMonitoringAnnotationsDecryptionFailureIsLogged(t *testing.T) {
 	defer internalregistry.ClearMonitoringClient()
-	client := &fakemonitoring.Client{Name: ""}
+	client := fakemonitoring.NewClient("fake-client")
 	if err := internalregistry.RegisterMonitoringClient(client); err != nil {
 		t.Fatalf("internalregistry.RegisterMonitoringClient() err = %v, want nil", err)
 	}
@@ -434,7 +496,17 @@
 	if err != nil {
 		t.Fatalf("keyset.NewHandle() err = %v, want nil", err)
 	}
-	p, err := aead.New(kh)
+	// Annotations are only supported throught the `insecurecleartextkeyset` API.
+	buff := &bytes.Buffer{}
+	if err := insecurecleartextkeyset.Write(kh, keyset.NewBinaryWriter(buff)); err != nil {
+		t.Fatalf("insecurecleartextkeyset.Write() err = %v, want nil", err)
+	}
+	annotations := map[string]string{"foo": "bar"}
+	mh, err := insecurecleartextkeyset.Read(keyset.NewBinaryReader(buff), keyset.WithAnnotations(annotations))
+	if err != nil {
+		t.Fatalf("insecurecleartextkeyset.Read() err = %v, want nil", err)
+	}
+	p, err := aead.New(mh)
 	if err != nil {
 		t.Fatalf("aead.New() err = %v, want nil", err)
 	}
@@ -448,21 +520,22 @@
 				"aead",
 				"decrypt",
 				monitoring.NewKeysetInfo(
-					make(map[string]string),
+					annotations,
 					kh.KeysetInfo().GetPrimaryKeyId(),
 					[]*monitoring.Entry{
 						{
-							KeyID:          kh.KeysetInfo().GetPrimaryKeyId(),
-							Status:         monitoring.Enabled,
-							FormatAsString: "type.googleapis.com/google.crypto.tink.AesGcmKey",
+							KeyID:     kh.KeysetInfo().GetPrimaryKeyId(),
+							Status:    monitoring.Enabled,
+							KeyType:   "tink.AesGcmKey",
+							KeyPrefix: "TINK",
 						},
 					},
 				),
 			),
 		},
 	}
-	if !cmp.Equal(got, want) {
-		t.Errorf("got = %v, want = %v", got, want)
+	if cmp.Diff(got, want) != "" {
+		t.Errorf("%v", cmp.Diff(got, want))
 	}
 }
 
@@ -476,7 +549,17 @@
 	if err != nil {
 		t.Fatalf("keyset.NewHandle() err = %v, want nil", err)
 	}
-	p1, err := aead.New(kh1)
+	// Annotations are only supported throught the `insecurecleartextkeyset` API.
+	buff := &bytes.Buffer{}
+	if err := insecurecleartextkeyset.Write(kh1, keyset.NewBinaryWriter(buff)); err != nil {
+		t.Fatalf("insecurecleartextkeyset.Write() err = %v, want nil", err)
+	}
+	annotations := map[string]string{"foo": "bar"}
+	mh1, err := insecurecleartextkeyset.Read(keyset.NewBinaryReader(buff), keyset.WithAnnotations(annotations))
+	if err != nil {
+		t.Fatalf("insecurecleartextkeyset.Read() err = %v, want nil", err)
+	}
+	p1, err := aead.New(mh1)
 	if err != nil {
 		t.Fatalf("aead.New() err = %v, want nil", err)
 	}
@@ -484,7 +567,15 @@
 	if err != nil {
 		t.Fatalf("keyset.NewHandle() err = %v, want nil", err)
 	}
-	p2, err := aead.New(kh2)
+	buff.Reset()
+	if err := insecurecleartextkeyset.Write(kh2, keyset.NewBinaryWriter(buff)); err != nil {
+		t.Fatalf("insecurecleartextkeyset.Write() err = %v, want nil", err)
+	}
+	mh2, err := insecurecleartextkeyset.Read(keyset.NewBinaryReader(buff), keyset.WithAnnotations(annotations))
+	if err != nil {
+		t.Fatalf("insecurecleartextkeyset.Read() err = %v, want nil", err)
+	}
+	p2, err := aead.New(mh2)
 	if err != nil {
 		t.Fatalf("aead.New() err = %v, want nil", err)
 	}
@@ -505,13 +596,14 @@
 				"aead",
 				"encrypt",
 				monitoring.NewKeysetInfo(
-					make(map[string]string),
+					annotations,
 					kh1.KeysetInfo().GetPrimaryKeyId(),
 					[]*monitoring.Entry{
 						{
-							KeyID:          kh1.KeysetInfo().GetPrimaryKeyId(),
-							Status:         monitoring.Enabled,
-							FormatAsString: "type.googleapis.com/google.crypto.tink.AesGcmKey",
+							KeyID:     kh1.KeysetInfo().GetPrimaryKeyId(),
+							Status:    monitoring.Enabled,
+							KeyType:   "tink.AesGcmKey",
+							KeyPrefix: "TINK",
 						},
 					},
 				),
@@ -524,13 +616,14 @@
 				"aead",
 				"encrypt",
 				monitoring.NewKeysetInfo(
-					make(map[string]string),
+					annotations,
 					kh2.KeysetInfo().GetPrimaryKeyId(),
 					[]*monitoring.Entry{
 						{
-							KeyID:          kh2.KeysetInfo().GetPrimaryKeyId(),
-							Status:         monitoring.Enabled,
-							FormatAsString: "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey",
+							KeyID:     kh2.KeysetInfo().GetPrimaryKeyId(),
+							Status:    monitoring.Enabled,
+							KeyType:   "tink.AesCtrHmacAeadKey",
+							KeyPrefix: "TINK",
 						},
 					},
 				),
@@ -541,3 +634,35 @@
 		t.Errorf("got = %v, want = %v, with diff: %v", got, want, cmp.Diff(got, want))
 	}
 }
+
+func TestPrimitiveFactoryEncryptDecryptWithoutAnnotationsDoesNothing(t *testing.T) {
+	defer internalregistry.ClearMonitoringClient()
+	client := fakemonitoring.NewClient("fake-client")
+	if err := internalregistry.RegisterMonitoringClient(client); err != nil {
+		t.Fatalf("internalregistry.RegisterMonitoringClient() err = %v, want nil", err)
+	}
+	kh, err := keyset.NewHandle(aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("keyset.NewHandle() err = %v, want nil", err)
+	}
+	p, err := aead.New(kh)
+	if err != nil {
+		t.Fatalf("aead.New() err = %v, want nil", err)
+	}
+	data := []byte("YELLOW_ORANGE")
+	ct, err := p.Encrypt(data, nil)
+	if err != nil {
+		t.Fatalf("p.Encrypt() err = %v, want nil", err)
+	}
+	if _, err := p.Decrypt(ct, nil); err != nil {
+		t.Fatalf("p.Decrypt() err = %v, want nil", err)
+	}
+	got := client.Events()
+	if len(got) != 0 {
+		t.Errorf("len(client.Events()) = %d, want 0", len(got))
+	}
+	failures := len(client.Failures())
+	if failures != 0 {
+		t.Errorf("len(client.Failures()) = %d, want 0", failures)
+	}
+}
diff --git a/go/aead/aead_init_test.go b/go/aead/aead_init_test.go
new file mode 100644
index 0000000..664ef4b
--- /dev/null
+++ b/go/aead/aead_init_test.go
@@ -0,0 +1,44 @@
+// Copyright 2023 Google LLC
+//
+// 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"testing"
+
+	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/testutil"
+)
+
+func TestAEADInit(t *testing.T) {
+	// Check that the AES-GCM key manager is in the global registry.
+	_, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+
+	// Check that the ChaCha20Poly1305 key manager is in the global registry.
+	_, err = registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+
+	// Check that the XChaCha20Poly1305 key manager is in the global registry.
+	_, err = registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+}
diff --git a/go/aead/aead_key_templates.go b/go/aead/aead_key_templates.go
index b536279..1cbb001 100644
--- a/go/aead/aead_key_templates.go
+++ b/go/aead/aead_key_templates.go
@@ -17,10 +17,14 @@
 package aead
 
 import (
+	"fmt"
+
 	"google.golang.org/protobuf/proto"
+	"github.com/google/tink/go/internal/tinkerror"
 	ctrpb "github.com/google/tink/go/proto/aes_ctr_go_proto"
 	ctrhmacpb "github.com/google/tink/go/proto/aes_ctr_hmac_aead_go_proto"
 	gcmpb "github.com/google/tink/go/proto/aes_gcm_go_proto"
+	gcmsivpb "github.com/google/tink/go/proto/aes_gcm_siv_go_proto"
 	commonpb "github.com/google/tink/go/proto/common_go_proto"
 	hmacpb "github.com/google/tink/go/proto/hmac_go_proto"
 	kmsenvpb "github.com/google/tink/go/proto/kms_envelope_go_proto"
@@ -51,22 +55,43 @@
 	return createAESGCMKeyTemplate(32, tinkpb.OutputPrefixType_RAW)
 }
 
+// AES128GCMSIVKeyTemplate is a KeyTemplate that generates an AES-GCM-SIV key with the following parameters:
+//   - Key size: 16 bytes
+//   - Output prefix type: TINK
+func AES128GCMSIVKeyTemplate() *tinkpb.KeyTemplate {
+	return createAESGCMSIVKeyTemplate(16, tinkpb.OutputPrefixType_TINK)
+}
+
+// AES256GCMSIVKeyTemplate is a KeyTemplate that generates an AES-GCM-SIV key with the following parameters:
+//   - Key size: 32 bytes
+//   - Output prefix type: TINK
+func AES256GCMSIVKeyTemplate() *tinkpb.KeyTemplate {
+	return createAESGCMSIVKeyTemplate(32, tinkpb.OutputPrefixType_TINK)
+}
+
+// AES256GCMSIVNoPrefixKeyTemplate is a KeyTemplate that generates an AES-GCM key with the following parameters:
+//   - Key size: 32 bytes
+//   - Output prefix type: RAW
+func AES256GCMSIVNoPrefixKeyTemplate() *tinkpb.KeyTemplate {
+	return createAESGCMSIVKeyTemplate(32, tinkpb.OutputPrefixType_RAW)
+}
+
 // AES128CTRHMACSHA256KeyTemplate is a KeyTemplate that generates an AES-CTR-HMAC-AEAD key with the following parameters:
-//  - AES key size: 16 bytes
-//  - AES CTR IV size: 16 bytes
-//  - HMAC key size: 32 bytes
-//  - HMAC tag size: 16 bytes
-//  - HMAC hash function: SHA256
+//   - AES key size: 16 bytes
+//   - AES CTR IV size: 16 bytes
+//   - HMAC key size: 32 bytes
+//   - HMAC tag size: 16 bytes
+//   - HMAC hash function: SHA256
 func AES128CTRHMACSHA256KeyTemplate() *tinkpb.KeyTemplate {
 	return createAESCTRHMACAEADKeyTemplate(16, 16, 32, 16, commonpb.HashType_SHA256)
 }
 
 // AES256CTRHMACSHA256KeyTemplate is a KeyTemplate that generates an AES-CTR-HMAC-AEAD key with the following parameters:
-//  - AES key size: 32 bytes
-//  - AES CTR IV size: 16 bytes
-//  - HMAC key size: 32 bytes
-//  - HMAC tag size: 32 bytes
-//  - HMAC hash function: SHA256
+//   - AES key size: 32 bytes
+//   - AES CTR IV size: 16 bytes
+//   - HMAC key size: 32 bytes
+//   - HMAC tag size: 32 bytes
+//   - HMAC hash function: SHA256
 func AES256CTRHMACSHA256KeyTemplate() *tinkpb.KeyTemplate {
 	return createAESCTRHMACAEADKeyTemplate(32, 16, 32, 32, commonpb.HashType_SHA256)
 }
@@ -89,22 +114,65 @@
 	}
 }
 
-// KMSEnvelopeAEADKeyTemplate is a KeyTemplate that generates a KMSEnvelopeAEAD key for
-// a given KEK in remote KMS. Keys generated by this key template uses RAW output prefix
-// to make them compatible with the remote KMS' encrypt/decrypt operations.
-// Unlike other templates, when you generate new keys with this template, Tink does not
-// generate new key material, but only creates a reference to the remote KEK.
-func KMSEnvelopeAEADKeyTemplate(uri string, dekT *tinkpb.KeyTemplate) *tinkpb.KeyTemplate {
+// CreateKMSEnvelopeAEADKeyTemplate returns a key template that generates a
+// KMSEnvelopeAEAD key for a given key encryption key (KEK) in a remote key
+// management service (KMS).
+//
+// When performing encrypt operations, a data encryption key (DEK) is generated
+// for each ciphertext.  The DEK is wrapped by the remote KMS using the KEK and
+// stored alongside the ciphertext.
+//
+// dekTemplate must be a KeyTemplate for any of these Tink AEAD key types (any
+// other key template will be rejected):
+//   - AesCtrHmacAeadKey
+//   - AesGcmKey
+//   - ChaCha20Poly1305Key
+//   - XChaCha20Poly1305
+//   - AesGcmSivKey
+//
+// DEKs generated by this key template use the RAW output prefix to make them
+// compatible with remote KMS encrypt/decrypt operations.
+//
+// Unlike other templates, when you generate new keys with this template, Tink
+// does not generate new key material, but only creates a reference to the
+// remote KEK.
+//
+// If either uri or dekTemplate contain invalid input, an error is returned.
+func CreateKMSEnvelopeAEADKeyTemplate(uri string, dekTemplate *tinkpb.KeyTemplate) (*tinkpb.KeyTemplate, error) {
+	if !isSupporedKMSEnvelopeDEK(dekTemplate.GetTypeUrl()) {
+		return nil, fmt.Errorf("unsupported DEK key type %s. Only Tink AEAD key types are supported", dekTemplate.GetTypeUrl())
+	}
+
 	f := &kmsenvpb.KmsEnvelopeAeadKeyFormat{
 		KekUri:      uri,
-		DekTemplate: dekT,
+		DekTemplate: dekTemplate,
 	}
-	serializedFormat, _ := proto.Marshal(f)
+	serializedFormat, err := proto.Marshal(f)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal key format: %s", err)
+	}
 	return &tinkpb.KeyTemplate{
 		Value:            serializedFormat,
 		TypeUrl:          kmsEnvelopeAEADTypeURL,
 		OutputPrefixType: tinkpb.OutputPrefixType_RAW,
+	}, nil
+}
+
+// KMSEnvelopeAEADKeyTemplate returns a KeyTemplate that generates a
+// KMSEnvelopeAEAD key for a given key encryption key (KEK) in a remote key
+// management service (KMS).
+//
+// If either uri or dekTemplate contain invalid input, program execution will
+// be interrupted.
+//
+// Deprecated: Use [CreateKMSEnvelopeAEADKeyTemplate], which returns an error
+// value instead of interrupting the program.
+func KMSEnvelopeAEADKeyTemplate(uri string, dekTemplate *tinkpb.KeyTemplate) *tinkpb.KeyTemplate {
+	t, err := CreateKMSEnvelopeAEADKeyTemplate(uri, dekTemplate)
+	if err != nil {
+		tinkerror.Fail(err.Error())
 	}
+	return t
 }
 
 // createAESGCMKeyTemplate creates a new AES-GCM key template with the given key
@@ -113,7 +181,10 @@
 	format := &gcmpb.AesGcmKeyFormat{
 		KeySize: keySize,
 	}
-	serializedFormat, _ := proto.Marshal(format)
+	serializedFormat, err := proto.Marshal(format)
+	if err != nil {
+		tinkerror.Fail(fmt.Sprintf("failed to marshal key format: %s", err))
+	}
 	return &tinkpb.KeyTemplate{
 		TypeUrl:          aesGCMTypeURL,
 		Value:            serializedFormat,
@@ -121,6 +192,23 @@
 	}
 }
 
+// createAESGCMSIVKeyTemplate creates a new AES-GCM-SIV key template with the given key
+// size in bytes.
+func createAESGCMSIVKeyTemplate(keySize uint32, outputPrefixType tinkpb.OutputPrefixType) *tinkpb.KeyTemplate {
+	format := &gcmsivpb.AesGcmSivKeyFormat{
+		KeySize: keySize,
+	}
+	serializedFormat, err := proto.Marshal(format)
+	if err != nil {
+		tinkerror.Fail(fmt.Sprintf("failed to marshal key format: %s", err))
+	}
+	return &tinkpb.KeyTemplate{
+		TypeUrl:          aesGCMSIVTypeURL,
+		Value:            serializedFormat,
+		OutputPrefixType: outputPrefixType,
+	}
+}
+
 func createAESCTRHMACAEADKeyTemplate(aesKeySize, ivSize, hmacKeySize, tagSize uint32, hash commonpb.HashType) *tinkpb.KeyTemplate {
 	format := &ctrhmacpb.AesCtrHmacAeadKeyFormat{
 		AesCtrKeyFormat: &ctrpb.AesCtrKeyFormat{
@@ -132,7 +220,10 @@
 			KeySize: hmacKeySize,
 		},
 	}
-	serializedFormat, _ := proto.Marshal(format)
+	serializedFormat, err := proto.Marshal(format)
+	if err != nil {
+		tinkerror.Fail(fmt.Sprintf("failed to marshal key format: %s", err))
+	}
 	return &tinkpb.KeyTemplate{
 		Value:            serializedFormat,
 		TypeUrl:          aesCTRHMACAEADTypeURL,
diff --git a/go/aead/aead_key_templates_test.go b/go/aead/aead_key_templates_test.go
index 3b13523..cf6d6c8 100644
--- a/go/aead/aead_key_templates_test.go
+++ b/go/aead/aead_key_templates_test.go
@@ -23,7 +23,9 @@
 
 	"github.com/google/tink/go/aead"
 	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/internal/tinkerror/tinkerrortest"
 	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/mac"
 	"github.com/google/tink/go/testing/fakekms"
 	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
 )
@@ -43,6 +45,15 @@
 			name:     "AES256_GCM_NO_PREFIX",
 			template: aead.AES256GCMNoPrefixKeyTemplate(),
 		}, {
+			name:     "AES128_GCM_SIV",
+			template: aead.AES128GCMSIVKeyTemplate(),
+		}, {
+			name:     "AES256_GCM_SIV",
+			template: aead.AES256GCMSIVKeyTemplate(),
+		}, {
+			name:     "AES256_GCM_SIV_NO_PREFIX",
+			template: aead.AES256GCMSIVNoPrefixKeyTemplate(),
+		}, {
 			name:     "AES128_CTR_HMAC_SHA256",
 			template: aead.AES128CTRHMACSHA256KeyTemplate(),
 		}, {
@@ -77,16 +88,25 @@
 	if err != nil {
 		t.Fatalf("fakekms.NewKeyURI() failed: %v", err)
 	}
+	fixedKeyTemplate, err := aead.CreateKMSEnvelopeAEADKeyTemplate(fixedKeyURI, aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("CreateKMSEnvelopeAEADKeyTemplate() err = %v", err)
+	}
+	newKeyTemplate, err := aead.CreateKMSEnvelopeAEADKeyTemplate(newKeyURI, aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("CreateKMSEnvelopeAEADKeyTemplate() err = %v", err)
+	}
+
 	var testCases = []struct {
 		name     string
 		template *tinkpb.KeyTemplate
 	}{
 		{
 			name:     "Fixed Fake KMS Envelope AEAD Key with AES128_GCM",
-			template: aead.KMSEnvelopeAEADKeyTemplate(fixedKeyURI, aead.AES128GCMKeyTemplate()),
+			template: fixedKeyTemplate,
 		}, {
 			name:     "New Fake KMS Envelope AEAD Key with AES128_GCM",
-			template: aead.KMSEnvelopeAEADKeyTemplate(newKeyURI, aead.AES128GCMKeyTemplate()),
+			template: newKeyTemplate,
 		},
 	}
 	for _, tc := range testCases {
@@ -101,8 +121,8 @@
 	}
 }
 
-// Tests that two KMSEnvelopeAEAD keys that use the same KEK and DEK template should be able to
-// decrypt each  other's ciphertexts.
+// Tests that two KMSEnvelopeAEAD keys that use the same KEK and DEK template
+// should be able to decrypt each other's ciphertexts.
 func TestKMSEnvelopeAEADKeyTemplateMultipleKeysSameKEK(t *testing.T) {
 	fakeKmsClient, err := fakekms.NewClient("fake-kms://")
 	if err != nil {
@@ -111,7 +131,62 @@
 	registry.RegisterKMSClient(fakeKmsClient)
 
 	fixedKeyURI := "fake-kms://CM2b3_MDElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5EhIaEIK75t5L-adlUwVhWvRuWUwYARABGM2b3_MDIAE"
-	template1 := aead.KMSEnvelopeAEADKeyTemplate(fixedKeyURI, aead.AES128GCMKeyTemplate())
+	template1, err := aead.CreateKMSEnvelopeAEADKeyTemplate(fixedKeyURI, aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("CreateKMSEnvelopeAEADKeyTemplate() err = %v", err)
+	}
+	template2, err := aead.CreateKMSEnvelopeAEADKeyTemplate(fixedKeyURI, aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("CreateKMSEnvelopeAEADKeyTemplate() err = %v", err)
+	}
+
+	handle1, err := keyset.NewHandle(template1)
+	if err != nil {
+		t.Fatalf("keyset.NewHandle(template1) failed: %v", err)
+	}
+	aead1, err := aead.New(handle1)
+	if err != nil {
+		t.Fatalf("aead.New(handle) failed: %v", err)
+	}
+
+	handle2, err := keyset.NewHandle(template2)
+	if err != nil {
+		t.Fatalf("keyset.NewHandle(template2) failed: %v", err)
+	}
+	aead2, err := aead.New(handle2)
+	if err != nil {
+		t.Fatalf("aead.New(handle) failed: %v", err)
+	}
+
+	plaintext := []byte("some data to encrypt")
+	aad := []byte("extra data to authenticate")
+
+	ciphertext, err := aead1.Encrypt(plaintext, aad)
+	if err != nil {
+		t.Fatalf("encryption failed, error: %v", err)
+	}
+	decrypted, err := aead2.Decrypt(ciphertext, aad)
+	if err != nil {
+		t.Fatalf("decryption failed, error: %v", err)
+	}
+	if !bytes.Equal(plaintext, decrypted) {
+		t.Fatalf("decrypted data doesn't match plaintext, got: %q, want: %q", decrypted, plaintext)
+	}
+}
+
+// Testing deprecated function, ignoring GoDeprecated.
+func TestCreateKMSEnvelopeAEADKeyTemplateCompatibleWithKMSEnevelopeAEADKeyTemplate(t *testing.T) {
+	fakeKmsClient, err := fakekms.NewClient("fake-kms://")
+	if err != nil {
+		t.Fatalf("fakekms.NewClient('fake-kms://') failed: %v", err)
+	}
+	registry.RegisterKMSClient(fakeKmsClient)
+
+	fixedKeyURI := "fake-kms://CM2b3_MDElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5EhIaEIK75t5L-adlUwVhWvRuWUwYARABGM2b3_MDIAE"
+	template1, err := aead.CreateKMSEnvelopeAEADKeyTemplate(fixedKeyURI, aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("CreateKMSEnvelopeAEADKeyTemplate() err = %v", err)
+	}
 	template2 := aead.KMSEnvelopeAEADKeyTemplate(fixedKeyURI, aead.AES128GCMKeyTemplate())
 
 	handle1, err := keyset.NewHandle(template1)
@@ -148,6 +223,51 @@
 	}
 }
 
+// Testing deprecated function, ignoring GoDeprecated.
+func TestKMSEnvelopeAEADKeyTemplateFails(t *testing.T) {
+	keyURI, err := fakekms.NewKeyURI()
+	if err != nil {
+		t.Fatalf("fakekms.NewKeyURI() err = %v", err)
+	}
+	invalidTemplate := &tinkpb.KeyTemplate{
+		// String fields cannot contain invalid UTF-8 characters.
+		TypeUrl: "\xff",
+	}
+	var template *tinkpb.KeyTemplate
+	err = tinkerrortest.RecoverFromFail(func() {
+		template = aead.KMSEnvelopeAEADKeyTemplate(keyURI, invalidTemplate)
+	})
+	if err == nil {
+		t.Errorf("aead.KMSEnvelopAEADKeyTemplate() err = nil, want non-nil")
+	}
+	t.Logf("template: %+v", template)
+}
+
+func TestCreateKMSEnvelopeAEADKeyTemplateFails(t *testing.T) {
+	keyURI, err := fakekms.NewKeyURI()
+	if err != nil {
+		t.Fatalf("fakekms.NewKeyURI() err = %v", err)
+	}
+	invalidTemplate := &tinkpb.KeyTemplate{
+		// String fields cannot contain invalid UTF-8 characters.
+		TypeUrl: "\xff",
+	}
+	if _, err := aead.CreateKMSEnvelopeAEADKeyTemplate(keyURI, invalidTemplate); err == nil {
+		t.Errorf("aead.CreateKMSEnvelopAEADKeyTemplate(keyURI, invalidTemplate) err = nil, want non-nil")
+	}
+}
+
+func TestCreateKMSEnvelopeAEADKeyTemplateWithUnsupportedTemplateFails(t *testing.T) {
+	keyURI, err := fakekms.NewKeyURI()
+	if err != nil {
+		t.Fatalf("fakekms.NewKeyURI() err = %v", err)
+	}
+	unsupportedTemplate := mac.HMACSHA256Tag128KeyTemplate()
+	if _, err := aead.CreateKMSEnvelopeAEADKeyTemplate(keyURI, unsupportedTemplate); err == nil {
+		t.Errorf("aead.CreateKMSEnvelopAEADKeyTemplate(keyURI, unsupportedTemplate) err = nil, want non-nil")
+	}
+}
+
 func testEncryptDecrypt(template *tinkpb.KeyTemplate) error {
 	handle, err := keyset.NewHandle(template)
 	if err != nil {
diff --git a/go/aead/aead_test.go b/go/aead/aead_test.go
index 456991a..b0583cc 100644
--- a/go/aead/aead_test.go
+++ b/go/aead/aead_test.go
@@ -16,66 +16,76 @@
 
 package aead_test
 
+// [START aead-example]
+
 import (
-	"encoding/base64"
+	"bytes"
 	"fmt"
 	"log"
-	"testing"
 
 	"github.com/google/tink/go/aead"
-	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/insecurecleartextkeyset"
 	"github.com/google/tink/go/keyset"
-	"github.com/google/tink/go/testutil"
 )
 
 func Example() {
-	kh, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())
-	if err != nil {
-		log.Fatal(err)
-	}
+	// A keyset created with "tinkey create-keyset --key-template=AES256_GCM". Note
+	// that this keyset has the secret key information in cleartext.
+	jsonKeyset := `{
+			"key": [{
+					"keyData": {
+							"keyMaterialType":
+									"SYMMETRIC",
+							"typeUrl":
+									"type.googleapis.com/google.crypto.tink.AesGcmKey",
+							"value":
+									"GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg=="
+					},
+					"keyId": 294406504,
+					"outputPrefixType": "TINK",
+					"status": "ENABLED"
+			}],
+			"primaryKeyId": 294406504
+	}`
 
-	// TODO: save the keyset to a safe location. DO NOT hardcode it in source code.
+	// Create a keyset handle from the cleartext keyset in the previous
+	// step. The keyset handle provides abstract access to the underlying keyset to
+	// limit the exposure of accessing the raw key material. WARNING: In practice,
+	// it is unlikely you will want to use a insecurecleartextkeyset, as it implies
+	// that your key material is passed in cleartext, which is a security risk.
 	// Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault.
 	// See https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets.
-
-	a, err := aead.New(kh)
+	keysetHandle, err := insecurecleartextkeyset.Read(
+		keyset.NewJSONReader(bytes.NewBufferString(jsonKeyset)))
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	msg := []byte("this message needs to be encrypted")
-	aad := []byte("this data needs to be authenticated, but not encrypted")
-	ct, err := a.Encrypt(msg, aad)
+	// Retrieve the AEAD primitive we want to use from the keyset handle.
+	primitive, err := aead.New(keysetHandle)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	pt, err := a.Decrypt(ct, aad)
+	// Use the primitive to encrypt a message. In this case the primary key of the
+	// keyset will be used (which is also the only key in this example).
+	plaintext := []byte("message")
+	associatedData := []byte("associated data")
+	ciphertext, err := primitive.Encrypt(plaintext, associatedData)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	fmt.Printf("Ciphertext: %s\n", base64.StdEncoding.EncodeToString(ct))
-	fmt.Printf("Original  plaintext: %s\n", msg)
-	fmt.Printf("Decrypted Plaintext: %s\n", pt)
+	// Use the primitive to decrypt the message. Decrypt finds the correct key in
+	// the keyset and decrypts the ciphertext. If no key is found or decryption
+	// fails, it returns an error.
+	decrypted, err := primitive.Decrypt(ciphertext, associatedData)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Println(string(decrypted))
+	// Output: message
 }
 
-func TestAEADInit(t *testing.T) {
-	// Check for AES-GCM key manager.
-	_, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-
-	// Check for ChaCha20Poly1305 key manager.
-	_, err = registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-
-	// Check for XChaCha20Poly1305 key manager.
-	_, err = registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-}
+// [END aead-example]
diff --git a/go/aead/aes_gcm_key_manager.go b/go/aead/aes_gcm_key_manager.go
index c74da12..f5142b9 100644
--- a/go/aead/aes_gcm_key_manager.go
+++ b/go/aead/aes_gcm_key_manager.go
@@ -18,6 +18,7 @@
 
 import (
 	"fmt"
+	"io"
 
 	"google.golang.org/protobuf/proto"
 	"github.com/google/tink/go/aead/subtle"
@@ -97,7 +98,7 @@
 	return &tinkpb.KeyData{
 		TypeUrl:         aesGCMTypeURL,
 		Value:           serializedKey,
-		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
+		KeyMaterialType: km.KeyMaterialType(),
 	}, nil
 }
 
@@ -111,10 +112,41 @@
 	return aesGCMTypeURL
 }
 
+// KeyMaterialType returns the key material type of the key manager.
+func (km *aesGCMKeyManager) KeyMaterialType() tinkpb.KeyData_KeyMaterialType {
+	return tinkpb.KeyData_SYMMETRIC
+}
+
+// DeriveKey derives a new key from serializedKeyFormat and pseudorandomness.
+func (km *aesGCMKeyManager) DeriveKey(serializedKeyFormat []byte, pseudorandomness io.Reader) (proto.Message, error) {
+	if len(serializedKeyFormat) == 0 {
+		return nil, errInvalidAESGCMKeyFormat
+	}
+	keyFormat := new(gcmpb.AesGcmKeyFormat)
+	if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
+		return nil, errInvalidAESGCMKeyFormat
+	}
+	if err := km.validateKeyFormat(keyFormat); err != nil {
+		return nil, fmt.Errorf("aes_gcm_key_manager: invalid key format: %s", err)
+	}
+	if err := keyset.ValidateKeyVersion(keyFormat.GetVersion(), aesGCMKeyVersion); err != nil {
+		return nil, fmt.Errorf("aes_gcm_key_manager: invalid key version: %s", err)
+	}
+
+	keyValue := make([]byte, keyFormat.GetKeySize())
+	if _, err := io.ReadFull(pseudorandomness, keyValue); err != nil {
+		return nil, fmt.Errorf("aes_gcm_key_manager: not enough pseudorandomness given")
+	}
+
+	return &gcmpb.AesGcmKey{
+		Version:  aesGCMKeyVersion,
+		KeyValue: keyValue,
+	}, nil
+}
+
 // validateKey validates the given AESGCMKey.
 func (km *aesGCMKeyManager) validateKey(key *gcmpb.AesGcmKey) error {
-	err := keyset.ValidateKeyVersion(key.Version, aesGCMKeyVersion)
-	if err != nil {
+	if err := keyset.ValidateKeyVersion(key.Version, aesGCMKeyVersion); err != nil {
 		return fmt.Errorf("aes_gcm_key_manager: %s", err)
 	}
 	keySize := uint32(len(key.KeyValue))
diff --git a/go/aead/aes_gcm_key_manager_test.go b/go/aead/aes_gcm_key_manager_test.go
index 6ea647e..156b98a 100644
--- a/go/aead/aes_gcm_key_manager_test.go
+++ b/go/aead/aes_gcm_key_manager_test.go
@@ -21,9 +21,11 @@
 	"fmt"
 	"testing"